Cerby · AsyncAPI Specification

Cerby Webhooks

Version

View Spec View on GitHub IdentityAccess ManagementSecurityPassword ManagementProvisioningSCIMIdentity GovernanceNonfederated ApplicationsAutomationWebhooksAsyncAPIWebhooksEvents

AsyncAPI Specification

cerby-webhooks.yml Raw ↑
generated: '2026-08-09'
method: searched
source: https://help.cerby.com/developer-tools/cerby-webhooks/implement-a-webhook-receiver
spec_type: none
spec_note: >-
  Cerby publishes NO AsyncAPI document. Probed https://api.cerby.com/asyncapi.yaml
  (404), https://help.cerby.com/asyncapi.yaml (404) and
  https://developer.cerby.com/asyncapi.json (404). What Cerby does publish is a
  complete prose webhook contract — envelope, headers, signature algorithm,
  event catalog, retry policy — captured here as a webhook catalog. This file is
  a faithful record of that documentation, not a generated spec.
surface: webhooks
docs:
- https://help.cerby.com/developer-tools/cerby-webhooks/implement-a-webhook-receiver
- https://help.cerby.com/setup-and-admin/workspace-settings/webhooks/explore-webhook-notifications
- https://help.cerby.com/setup-and-admin/workspace-settings/webhooks/create-a-webhook
- https://help.cerby.com/setup-and-admin/workspace-settings/webhooks/rotate-a-webhook-signing-key
- https://help.cerby.com/setup-and-admin/workspace-settings/webhooks/test-a-webhook-endpoint

envelope:
  version_field: envelope_version
  version: '1.0'
  content_type: application/json
  fields:
    envelope_version: {type: string, note: Always "1.0" in this release. New fields may be added without a version bump.}
    event_id: {type: string (UUIDv7), note: Unique per event. Stable across all retry attempts. Use as the per-event idempotency key.}
    event_type: {type: string}
    occurred_at: {type: string (RFC 3339, UTC, ms), note: When Cerby recorded the event, not the source-system time.}
    workspace_id: {type: string (UUID)}
    data: {type: object, note: Event-specific payload. Fields vary by event type.}
    error: {type: object, note: 'Present ONLY on failure events — absent, not null, otherwise. Fields: code, message, user_action.'}
    trace_id: {type: 'null', note: Reserved. Always null in this release.}
    correlation_id: {type: 'null', note: Reserved. Always null in this release.}

headers:
- name: X-Cerby-Signature
  description: '<algo>=<base64url(sig)> where <algo> is ed25519 or hmac-sha256. Repeated during a key rotation (new key first, old key second).'
- name: X-Cerby-Signature-Key-Id
  description: UUID of the signing key used. One header per signature row, in the same order as X-Cerby-Signature.
- name: X-Cerby-Signature-Timestamp
  description: RFC 3339 millisecond-precision UTC timestamp, part of the signed message. Reject if more than five minutes from now.
- name: X-Cerby-Delivery
  description: UUID constant across all retry attempts for the same event and webhook. Per-delivery idempotency key.
- name: X-Cerby-Attempt
  description: Attempt number starting at 1, incremented on each retry, up to 6.
- name: Idempotency-Key
  description: Same value as X-Cerby-Delivery, provided as a convenience alias.

security:
  mechanism: request signing
  algorithms: [ed25519, hmac-sha256]
  signed_bytes: '<timestamp_ms>.<raw_body_bytes>'
  canonicalization: none
  clock_skew_tolerance_ms: 300000
  base64: base64url, unpadded (re-pad before decoding)
  reference_implementations: [python, nodejs]
  reference_implementations_note: Kept in sync with Cerby's CI test suite.
  key_retrieval_endpoint: GET /v1/event-webhooks/{webhook_id}/keys
  key_rotation:
    dual_sign_window_hours: 24
    ordering: new key first (primary), old key second (secondary)
    cache_miss_signal: >-
      An unrecognized X-Cerby-Signature-Key-Id means a rotation has started —
      re-fetch both keys before rejecting.

delivery:
  guarantee: at-least-once
  dedup:
    per_delivery: X-Cerby-Delivery + X-Cerby-Attempt
    per_event: event_id
    anti_pattern: Do not deduplicate on payload content, URL, or signature equality.
  retry_policy:
    max_attempts: 6
    initial_interval_seconds: 60
    backoff_multiplier: 4
    approximate_schedule: [1m, 4m, 16m, 64m, 4.3h]
    max_interval_hours: 6
    hard_deadline_hours: 24
  response_handling:
  - receiver_returns: 2xx
    behavior: Delivery complete.
  - receiver_returns: 408, 429, or 503 with Retry-After
    behavior: Retried. Cerby honors Retry-After, clamped to one hour.
  - receiver_returns: any other 4xx
    behavior: Fail-fast. Dead-lettered immediately, not retried.
  - receiver_returns: 5xx, timeout, or TCP error
    behavior: Retried with standard backoff.
  guidance: Respond 2xx quickly and process the event asynchronously.

channels:
- transport: https
  description: Standard JSON envelope delivered to any HTTPS endpoint.
- transport: slack-incoming-webhook
  host_trigger: hooks.slack.com
  description: >-
    Cerby delivers a Slack Block Kit message instead of the standard envelope.
    The Block Kit message is a lossy subset — data.action, data.automation_link,
    and other data fields are omitted.
  severity_colors: {success: '#2EB67D', failure: '#D50200'}

events:
- name: account.created
  domain: account
  data: [account_id, domain, vault_id, requestor_id]
- name: account.deleted
  domain: account
  data: [account_id, requestor_id]
  status: not-delivered-this-release
- name: account.disabled
  domain: account
  data: [account_id, requestor_id]
- name: account.enabled
  domain: account
  data: [account_id, requestor_id]
- name: account.credentials.updated
  domain: account
  data: [account_id, application, credentials_identifier]
  pii: credentials_identifier is the username or email.
- name: account.credentials.rotated
  domain: account
  data: [account_id, application, credentials_identifier]
- name: account.credentials.rotation_failed
  domain: account
  data: [account_id, application, automation_job_id, automation_link]
  carries_error: true
  status: provisional
- name: account.access.shared
  domain: account
  data: [account_id, account_name, new_teams_ids, role]
  pii: account_name
  note: May co-fire with account.access.shared_with_user.
- name: account.access.shared_with_user
  domain: account
  data: [account_id, account_name, new_users_ids, role]
  note: May co-fire with account.access.shared.
- name: account.login.failed
  domain: account
  data: [account_id, application, automation_job_id, automation_link]
  carries_error: true
  status: provisional
- name: account.mfa.enabled
  domain: account
  data: [account_id, application, automation_job_id]
  status: provisional
- name: account.mfa.disabled
  domain: account
  data: [account_id, provider]
- name: account.mfa.setup_failed
  domain: account
  data: [account_id, application, automation_job_id, automation_link]
  carries_error: true
  status: provisional
- name: automation.users.provisioned
  domain: automation
  data: [automation_job_id, application, account_id, automation_link, child_count, child_failure_count]
  note: Rollup — one event per execution group. child counts are null this release.
- name: automation.users.deprovisioned
  domain: automation
  data: [automation_job_id, application, account_id, automation_link, child_count, child_failure_count]
  note: Rollup.
- name: automation.users.role_updated
  domain: automation
  data: [automation_job_id, application, account_id, automation_link, child_count, child_failure_count]
  note: Rollup.
- name: automation.password.rotated
  domain: automation
  data: [automation_job_id, application, account_id, automation_link]
- name: automation.mfa.enabled
  domain: automation
  data: [automation_job_id, application, account_id, automation_link]
- name: automation.universal_logout.completed
  domain: automation
  data: [automation_job_id, application, account_id, automation_link]
  status: provisional
- name: automation.succeeded
  domain: automation
  data: [automation_job_id, application, account_id, automation_link, action]
  note: Always-fire catch-all. Co-fires alongside the matching specific success event.
- name: automation.failed
  domain: automation
  data: [automation_job_id, application, account_id, automation_link, action, remediation]
  carries_error: true
  error_catalog: errors/cerby-automation-failure-codes.yml

reserved_events:
  note: Reserved for a future release. Subscribing currently returns HTTP 400.
  events:
  - account.vault.assigned
  - account.trusted_session.established
  - automation.users.attributes_updated
  - automation.users.synced

limitations_published:
- account.deleted is available for subscription but not delivered in this release.
- Webhooks are not available for Canada-resident workspaces (ca-central-1).
- trace_id and correlation_id are reserved and always null; most events are standalone.
- Several event types are provisional and may not arrive in production.

summary:
  event_types: 21
  reserved_event_types: 4
  domains: [account, automation]
  provisional: 5

x-evidence:
- url: https://help.cerby.com/developer-tools/cerby-webhooks/implement-a-webhook-receiver.md
  http_status: 200
  fetched: '2026-08-09'
- url: https://api.cerby.com/asyncapi.yaml
  http_status: 404
  fetched: '2026-08-09'
- url: https://help.cerby.com/asyncapi.yaml
  http_status: 404
  fetched: '2026-08-09'