Skip to content

Webhooks

Subscribe to XID events and receive signed HTTP payloads when users, sessions, and organizations change.

Event naming

Events follow the pattern <object>.<action>. Each event carries a stable type name, a unique svix-id, and an ISO 8601 timestamp.

Object Actions
user created, updated, deleted
session created, ended, removed, revoked
organization created, updated, deleted
organizationMembership created, updated, deleted
organizationInvitation created, accepted, revoked
organizationDomain created, updated, deleted, verified, verification_failed
authentication password_succeeded, password_failed, passkey_succeeded, passkey_failed, mfa_succeeded, mfa_failed, oauth_succeeded, oauth_failed, sso_succeeded, sso_failed, magic_auth_succeeded, magic_auth_failed, email_verification_succeeded, email_verification_failed, radar_risk_detected
connection activated, deactivated, deleted, saml_certificate_renewed, renewal_required
dsync activated, deleted, user.created, user.updated, user.deleted, group.created, group.updated, group.deleted, group.user_added, group.user_removed
role created, updated, deleted
permission created, updated, deleted
email created (fired when the developer takes over sending)
sms created (fired when the developer takes over sending)
billing subscription.created, subscription.updated, paymentAttempt.succeeded, paymentAttempt.failed

Payload structure

Every webhook delivery is an HTTP POST with Content-Type: application/json. The body contains type, data, and top-level metadata headers.

{
  "type": "user.created",
  "data": {
    "id": "usr_01abc",
    "email_addresses": [{ "email_address": "alice@example.com" }],
    "created_at": 1700000000000
  }
}

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 Base64-encoded HMAC-SHA256 of ${svix-id}.${svix-timestamp}.${raw-body} using the endpoint signing secret.
// Node.js / Cloudflare Workers example
async function verifyWebhook(request, secret) {
  const svixId = request.headers.get('svix-id')
  const svixTimestamp = request.headers.get('svix-timestamp')
  const svixSignature = request.headers.get('svix-signature')
  const body = await request.text()

  // Reject messages older than 5 minutes
  const ts = Number(svixTimestamp)
  if (Math.abs(Date.now() / 1000 - ts) > 300) {
    throw new Error('webhook timestamp out of tolerance')
  }

  const signedContent = `${svixId}.${svixTimestamp}.${body}`
  const keyData = Uint8Array.from(atob(secret), c => c.charCodeAt(0))
  const key = await crypto.subtle.importKey(
    'raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']
  )
  const msgData = new TextEncoder().encode(signedContent)

  // svix-signature may contain multiple comma-separated values
  for (const sig of svixSignature.split(' ')) {
    const prefix = 'v1,'
    if (!sig.startsWith(prefix)) continue
    const sigBytes = Uint8Array.from(atob(sig.slice(prefix.length)), c => c.charCodeAt(0))
    const valid = await crypto.subtle.verify('HMAC', key, sigBytes, msgData)
    if (valid) return JSON.parse(body)
  }
  throw new Error('invalid webhook signature')
}

Retries and dead letters

  • Failed deliveries are retried with exponential backoff. After the maximum retry count, the message is written to the dead-letter store in D1 for manual inspection.
  • 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"]

Manual replay

Use POST /v1/webhooks/:endpointId/replay to replay events by message ID or time range. Replayed events carry fresh svix-id values but preserve the original type and data.

# Replay events from the last hour
curl -X POST https://xid.dev/v1/webhooks/whe_xxx/replay \
  -H 'Authorization: Bearer sk_live_xxx' \
  -H 'Content-Type: application/json' \
  -d '{ "since": "2024-01-01T00:00:00Z", "until": "2024-01-01T01:00:00Z" }'

Events API

In addition to push webhooks, XID exposes an ordered, immutable event stream with cursor pagination at GET /v1/events. Pull the stream to build reliable synchronization without missing events between webhook retries.

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

Type to search...

Use arrow keys to navigateEnter to selectEscape to close