Skip to main content

Webhook Endpoints

Webhook destinations are configured on the organization, not on the flow. An organization owns a registry of endpoints; each endpoint has its own URL, its own event subscriptions, its own flow scope, its own signing secret, and its own payload version.

What changed

A flow used to carry a single webhookUrl, and every event that flow produced went there. That field no longer exists: the flow API neither accepts nor returns webhookUrl. Destinations live at /organizations/{organizationId}/webhooks, and one event can now fan out to several endpoints.

Existing configurations were converted automatically and keep delivering byte-for-byte the payload they received before. See What was migrated for you.

Anatomy of an endpoint

FieldMeaning
nameLabel used in the dashboard and in delivery diagnostics. Up to 120 characters, unique within the organization.
urlHTTPS destination. Up to 2048 characters, re-validated against the public-address rules before every delivery.
enabledDisabling an endpoint stops deliveries immediately, including work already queued.
eventTypesOne or more canonical event types. At least one is required. See Event subscriptions.
flowFilterOWNED_ALL or SELECTED plus an explicit flow list. See Flow scope.
secretModeORGANIZATION (the shared secret) or DEDICATED (a secret only this endpoint uses). See Signing secret.
payloadVersionV2 or LEGACY_V1. See Payload Versions.
secretHintLast characters of the secret in force, so you can tell which key an endpoint signs with. The secret itself is never read back.
managedByFlowIdSet when the endpoint was created by the one-shot migration from a flow's old webhookUrl. Provenance only — the endpoint is fully editable.

Endpoints are per-environment: the sandbox registry and the production registry are separate. Every route accepts the usual ?sandbox=true convention.

Event subscriptions

An endpoint receives only the event types it subscribes to. The canonical vocabulary is resource-specific (verification.dv.completed, flow.completed, transaction.alert.created, …) and is separate from the top-level event field inside the payload, which is coarser and cannot distinguish a DV result from a KYB result.

The full catalog is listed in Statuses and codes, and the deployment's live copy is served by:

GET /organizations/{organizationId}/webhooks/events

Only catalog values are accepted. Wildcards are not: the API returns advisory groups (VERIFICATION, FLOW, TRANSACTION, KYC_PASSPORT) for building a selection UI, but a subscription always stores exact values, so adding an event to the catalog later can never silently widen a subscription you already made.

Flow scope

ScopeReceives
OWNED_ALLEvery flow owned by your organization, including flows created later.
SELECTEDOnly the flows you list. Up to 100 flows per endpoint.

OWNED_ALL deliberately excludes flows that belong to a partner organization: accepting a new KYC Passport partnership never widens an existing endpoint's data feed.

Events that have no source flow — today transaction.alert.created — ignore the flow filter rather than failing it, so a SELECTED endpoint that subscribed to Transaction Monitoring alerts still receives them.

Signing secret

ModeBehaviour
ORGANIZATIONSigns with the organization's shared secret. The default, and what every migrated endpoint uses. Rotating the shared secret re-keys every endpoint in this mode at once.
DEDICATEDSigns with a secret owned and rotated by this endpoint alone.

Rules that follow from that split:

  • A plaintext secret is returned exactly once — by the create, secret-mode-switch, and rotate operations — and can never be read back. Store it when you receive it.
  • ORGANIZATION mode requires the organization to already have a shared secret.
  • POST /{endpointId}/rotate-secret is valid only for DEDICATED endpoints.
  • Rotating the shared secret takes effect immediately for new attempts. The outgoing value stays recorded as acceptable for a one-hour grace window — secretExpiresAt on the organization-secret response — so keep accepting it on your side until then.
  • Changing an endpoint's URL, filters, or payload version never rotates a secret.

Verification itself is unchanged and is documented in Webhook Signature.

Limits

LimitValue
Active endpoints per organization and environment30
Selected flows per endpoint100
Endpoint name120 characters
Endpoint URL2048 characters

Duplicate URLs are allowed: two endpoints may point at the same destination when their filters, secrets, or payload versions differ.

Who can manage endpoints

The Webhooks surface is restricted to Admin (ORG_ADMIN) and Developer (ORG_DEVELOPER). See the role matrix.

API

All routes are under /organizations/{organizationId}/webhooks and are authenticated with a dashboard session.

MethodPathPurpose
GET/List endpoints
POST/Create an endpoint
GET/{endpointId}Read one endpoint
PATCH/{endpointId}Update name, URL, enabled, events, filter, version
DELETE/{endpointId}Delete an endpoint
POST/{endpointId}/rotate-secretRotate a dedicated secret
POST/{endpointId}/secret-modeSwitch between ORGANIZATION and DEDICATED
GET/organization-secretShared-secret status (never the secret itself)
POST/organization-secret/rotateRotate the shared secret
GET/eventsThe canonical event catalog

Create an endpoint

POST /organizations/{organizationId}/webhooks?sandbox=true
Content-Type: application/json
{
"name": "Onboarding results",
"url": "https://api.example.com/webhooks/zyphe",
"enabled": true,
"eventTypes": ["verification.dv.completed", "verification.dv.failed", "verification.dv.review", "flow.completed"],
"flowFilter": {
"scope": "SELECTED",
"flowIds": ["89b37f2c-544e-4c49-bd52-e03208f94cc7"]
},
"secretMode": "ORGANIZATION",
"payloadVersion": "V2"
}

enabled defaults to true, secretMode to ORGANIZATION, flowFilter to OWNED_ALL, and payloadVersion to V2.

The response is the stored endpoint, plus secret when the operation minted a dedicated one:

{
"id": "1f6b0d3c-2a1e-4c58-9d47-0b0f3f6b1e21",
"organizationId": "6b0f6a2e-1f5c-4a70-9d6b-2b1a0e4c9d33",
"name": "Onboarding results",
"url": "https://api.example.com/webhooks/zyphe",
"enabled": true,
"eventTypes": ["verification.dv.completed", "verification.dv.failed", "verification.dv.review", "flow.completed"],
"flowFilter": { "scope": "SELECTED", "flowIds": ["89b37f2c-544e-4c49-bd52-e03208f94cc7"] },
"secretMode": "ORGANIZATION",
"secretHint": "…a91c",
"secretUpdatedAt": "2026-08-20T09:14:02Z",
"payloadVersion": "V2",
"managedByFlowId": null,
"createdAt": "2026-08-26T10:00:00Z",
"updatedAt": "2026-08-26T10:00:00Z"
}

Update an endpoint

PATCH is a partial update: omitted fields are left alone. A supplied eventTypes or flowFilter replaces the previous one wholesale, because a filter is a complete statement of intent.

{ "payloadVersion": "V2", "enabled": true }

secretMode is not patchable here; it has its own route, because changing it mints or retires secret material.

Rotate the shared secret

POST /organizations/{organizationId}/webhooks/organization-secret/rotate
{
"secret": "…the new plaintext, returned once…",
"configured": true,
"secretHint": "…7f3d",
"secretUpdatedAt": "2026-08-26T10:05:00Z",
"secretExpiresAt": "2026-08-26T11:05:00Z",
"affectedEndpointIds": ["1f6b0d3c-2a1e-4c58-9d47-0b0f3f6b1e21"]
}

affectedEndpointIds is the blast radius: every endpoint that now signs with this secret. secretExpiresAt is when the previous secret stops being honoured.

Validation

A write is rejected when:

  • the URL is not HTTPS or does not resolve to a public address;
  • eventTypes is empty or contains a value outside the catalog;
  • flowIds is supplied with OWNED_ALL, or exceeds 100 entries;
  • a selected flow is not owned by the organization (partner flows require an accepted, non-revoked KYC Passport invite and the KYC Passport event type);
  • secretMode is ORGANIZATION but the organization has no shared secret;
  • a LEGACY_V1 endpoint subscribes to an event that has no legacy payload — a subscription that could never fire is refused at write time rather than silently dropped at delivery time. See Payload Versions.

Delivery

PropertyValue
MethodPOST, application/json
Success2xx for V2 endpoints, strictly 200 for LEGACY_V1 endpoints
AttemptsUp to four (one initial plus three retries), exponential backoff from 1s to 8s
Request timeout30 seconds by default
RedirectsNot followed

Each endpoint is an independent delivery: one endpoint failing or being disabled never affects another endpoint's copy of the same event, and each retries on its own.

A retry uses the URL captured when the delivery was created, not the endpoint's current URL — editing an endpoint never re-points work that was already routed.

Deliveries are deliberately skipped, not failed, when the destination was disabled or deleted after queueing, an SDK session closed or expired, or a KYC Passport consent was revoked between routing and sending.

What was migrated for you

A one-shot migration converted every pre-existing configuration. Nothing about a running integration changed: same URL, same secret, same payload bytes, same environment.

Old configurationBecomes
A flow's webhookUrlAn enabled endpoint named Flow: <slug>, scoped SELECTED to that one flow, subscribed to every flow-bound event, LEGACY_V1, signing with the shared organization secret.
Transaction Monitoring URLAn enabled endpoint named Transaction Monitoring (migrated), scoped OWNED_ALL, subscribed to transaction.alert.created, LEGACY_V1, keeping TM's own secret as a DEDICATED one.

Guarantees the migration kept:

  • Secrets were copied, never regenerated. An organization that never configured a secret still receives unsigned deliveries; that state was preserved, not "fixed".
  • Endpoints were never merged by URL. Two flows pointing at the same URL remain two independently editable endpoints.
  • Endpoints created by the migration carry managedByFlowId for provenance, but behave exactly like ones you create by hand: edit, disable, or delete them freely.

You migrate to the new payload on your own schedule, endpoint by endpoint. See Payload Versions.

Extended and legacy DV payload options

The extended DV payload and the legacy data.kyc compatibility object were organization-wide settings, and the migration carried them onto the endpoints it created. They are not settable when you create a new endpoint — a new endpoint always gets the standard payload. If you rely on either and need it on a new endpoint, contact Zyphe support. See Backwards Compatibility.