Skip to content

Webhooks

Subscribe to implemented user, organization, membership, invitation, and SAML certificate events through signed HTTP deliveries.

Event naming

Implemented events follow the pattern <object>.<action>. The xid-webhook-event header and body type carry the exact event name; every delivery also has a unique svix-id.

Object Actions
user created, updated, deleted, restored, banned, unbanned, deactivated
organization created, updated, deleted, restored
organization.auth_policy updated
organization.delivery_channels updated
organization.social_providers updated
organization.outbound_saml_app created, deleted
organization.scim_target created, deleted
organizationMembership created, updated, deleted, restored
organizationInvitation created, accepted, revoked
connection saml_certificate_renewed
user.* Subscription wildcard for all implemented user events.
organization.* Subscription wildcard for all implemented organization events, including dotted sub-events.
organizationMembership.* Subscription wildcard for all implemented organization membership events.
* Subscription wildcard for every implemented event.

Payload structure

Every webhook delivery is an HTTP POST with Content-Type: application/json. The body contains the exact event type and its data; svix metadata is carried in request headers.

{
  "type": "user.created",
  "data": {
    "userId": "user_01abc"
  }
}

Signature verification

XID signs every delivery with HMAC-SHA256. Verify the signature before processing the payload. Reject deliveries older than 5 minutes to prevent replay attacks.

Header Description
svix-id Unique message ID. Use this to deduplicate retried deliveries.
svix-timestamp Unix seconds when the message was sent.
svix-signature The svix-signature header starts with the literal v1, prefix, followed by the Base64-encoded HMAC-SHA256 of ${svix-id}.${svix-timestamp}.${raw-body} using the endpoint signing secret.
import { verifyWebhook } from '@xid-kit/backend'

const result = await verifyWebhook(request, {
  secret: env.XID_WEBHOOK_SECRET,
})
if (!result.ok) {
  return new Response('Invalid webhook', { status: 400 })
}
const { type, data } = result.value.payload

Retries and dead letters

  • Delivery attempts use exponential backoff and end in dead status. Queue-level exhaustion is persisted as an encrypted dead-letter record for Instance Manager inspection and replay.
  • Delivery is decoupled from the authentication path through Cloudflare Queues. A slow or unavailable endpoint does not affect login latency.
  • Use the svix-id header to deduplicate deliveries on your end. Retries carry the same svix-id as the original attempt.
flowchart LR
  XID --> Queue
  Queue -->|HTTPS| Endpoint
  Endpoint -->|2xx| ACK
  Endpoint -->|non-2xx| Retry
  Retry --> Queue
  Retry -->|max_retries| dlq["D1 DLQ"]

Endpoint recovery

Use POST /v1/webhooks/:id/restore to reactivate a deleted endpoint and POST /v1/webhooks/:id/rotate-secret to replace its signing secret. The new signing_secret is returned once. Product-level replay by delivery ID or time range is not implemented.

curl -X POST https://xid.dev/v1/webhooks/webhook_xxx/rotate-secret \
  -H 'Authorization: Bearer sk_live_xxx'

Delivery history boundary

XID does not expose a pull Events API. Use GET /v1/webhooks to manage subscriptions; delivery state and queue dead-letter replay are operational surfaces for Instance Managers, not a tenant event stream.

curl 'https://xid.dev/v1/webhooks?limit=100' \
  -H 'Authorization: Bearer sk_live_xxx'
Navigation

Type to search...

Use arrow keys to navigateEnter to selectEscape to close