Skip to main content

Webhooks

POST/v1/orgs/{org_id}/webhooks

Create Webhook

CI or Admin tokenscope: writeoperation_id: webhooks.create

Authentication

Requires a CI- or admin-level token. Runtime tokens are rejected for mutations.

SDK install

pip install znyx-sdknpm install @znyx/sdk

Path parameters

NameTypeRequiredDescription
org_id#pathstringrequired

Header parameters

NameTypeRequiredDescription
X-API-Key#headerstring | nulloptional
authorization#headerstring | nulloptional

Request bodyrequired

FieldTypeRequiredDescription
urlstringrequired
eventsstring[]required
generate_secretbooleanoptional

Responses

StatusDescription
201Successful Response
422Validation Error

Response schema

idrequiredstring
org_idrequiredstring
urlrequiredstring
eventsrequiredstring[]
is_activerequiredboolean
created_atrequiredstring
has_secretrequiredboolean

Errors & what triggers them

CodeTriggerFix
400Webhook URL resolves to a private / loopback / reserved IP, or hostname is unresolvable.Use a public HTTPS URL. Local testing requires a tunnel (ngrok, Cloudflare Tunnel).
403Caller is not an org admin.
422events list is empty or contains an unknown event name.

Notes & examples

Event taxonomy

Webhooks subscribe to a list of event names. Available events:

  • Bundle lifecyclebundle.published, bundle.activated, bundle.promoted
  • Evaluationevaluation.blocked (fires every time a runtime evaluation returns BLOCK)
  • Policypolicy.updated, sso.configured
  • Teamteam.member_added, team.member_removed
  • Alertsalert.triggered (for paid-tier alert rules)

Signature verification

Every webhook POST to your URL includes an X-Webhook-Signature header of the form sha256=<hex>. Verify it by HMAC-SHA256'ing the raw request body with the shared secret you stored on creation:

import hmac, hashlib

def verify(raw_body: bytes, secret: str, header: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", header)

SSRF protection

The control plane validates your webhook URL on creation. It refuses private / loopback / reserved / AWS-metadata IPs — no localhost, no 10.*, no 169.254.169.254. If you need to test locally, use a tunnel like ngrok.

Reliability

Failed deliveries retry with exponential backoff (5 attempts over ~2h). After that the event is dropped — we don't queue indefinitely. For durable delivery, use webhooks as a nudge and reconcile via the REST API.

  • POST /v1/orgs/{org_id}/webhooks/{id}/test — send a synthetic event to check your endpoint responds.
  • POST /v1/orgs/{org_id}/webhooks/{id}/rotate-secret — rotate the HMAC secret.

Request

curl -X POST 'https://api.znyx.ai/v1/orgs/00000000-0000-0000-0000-000000000000/webhooks' \
  -H 'Authorization: Bearer $ZNYX_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
  "url": "string",
  "events": [
    "string"
  ],
  "generate_secret": true
}'

Response

application/json

Successful Response

{
  "id": "string",
  "org_id": "string",
  "url": "string",
  "events": [
    "string"
  ],
  "is_active": false,
  "created_at": "string",
  "has_secret": false
}

Schema: object