iOS 16 and iOS 17 support ends late July 2026. Update your device to iOS 18.0 or later to continue receiving Cerby app updates.
For the complete documentation index, see llms.txt. This page is also available as Markdown.

Implement a webhook receiver

This article describes the webhook delivery format, HTTP headers, signature verification, event types, and error codes for developers building webhook receiver integrations.

This article describes the key components you need as a developer to implement a webhook receiver that accepts Cerby event deliveries. The following sections are covered:

For an overview of how webhooks work and how to create a webhook endpoint in Cerby, refer to Explore webhook notifications and Create a webhook.

Envelope format

Every webhook request body is a JSON object with a fixed top-level structure. The error field is present only on failure events. It is absent (not null) on all other event types.

The following is an example of a success event delivery:

{
    "envelope_version": "1.0",
    "event_id": "01977f2e-9c1a-7d3b-8f00-2b9a4c5d6e7f",
    "event_type": "account.credentials.rotated",
    "occurred_at": "2026-06-09T20:12:01.342Z",
    "workspace_id": "9f8e7d6c-5b4a-4f3e-9d2c-1b0a99887766",
    "data": { "account_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "application": "Salesforce", "credentials_identifier": "j.doe@acme.com" },
    "trace_id": null,
    "correlation_id": null
}

The following is an example of a failure event delivery (automation.failed):

The following table describes each field in the JSON object:

Field
Type
Description

envelope_version

string

Always "1.0" in this release. New fields may be added without a version bump.

event_id

string (UUIDv7)

Unique per event. Stable across all retry attempts for the same event. Use as the per-event idempotency key.

event_type

string

The event type string. See Event types.

occurred_at

string (RFC 3339, UTC, ms)

When Cerby recorded the event. Not the source-system time.

workspace_id

string (UUID)

The Cerby workspace where the event occurred.

data

object

Event-specific payload. Fields vary by event type.

error

object

Present only on failure events. Contains { "code": string, "message": string | null, "user_action": string | null }. Absent (not present) on all other event types.

trace_id

null

Reserved. Always null in this release.

correlation_id

null

Reserved. Always null in this release.

Forward compatibility

Apply the following forward compatibility rules when processing deliveries:

  • Ignore unknown fields. New top-level keys may appear without a version bump.

  • Tolerate null on trace_id, correlation_id, error.message, and error.user_action.

  • Do not assume field ordering.

  • Treat unknown error.code and error.user_action values as opaque strings. Do not reject events because of an unrecognized value.

HTTP headers

Every webhook request includes the following headers:

Header
Description

X-Cerby-Signature

<algo>=<base64url(sig)> where <algo> is ed25519 or hmac-sha256. Repeated during a key rotation (new key first, old key second).

X-Cerby-Signature-Key-Id

UUID of the signing key used. One header per signature row, in the same order as X-Cerby-Signature.

X-Cerby-Signature-Timestamp

RFC 3339 millisecond-precision UTC timestamp. The timestamp is part of the signed message. Reject requests where it is more than five minutes from the current time.

X-Cerby-Delivery

UUID constant across all retry attempts for the same event and webhook. Use as the per-delivery idempotency key.

X-Cerby-Attempt

Attempt number starting at 1, incremented on each retry, up to 6.

Idempotency-Key

Same value as X-Cerby-Delivery. Provided as a convenience alias.

Signature verification

Verify the signature on every incoming request before processing the event. A request that fails verification must be rejected with a 4xx response.

How the signature is constructed

Cerby signs the following byte sequence:

Where <timestamp_ms> is the X-Cerby-Signature-Timestamp header converted to an integer number of milliseconds since the Unix epoch, . is a literal period, and <raw_body_bytes> is the unmodified request body as received. There is no JSON canonicalization.

Verification algorithm

To verify an incoming request, complete the following steps:

  1. Parse X-Cerby-Signature-Timestamp to milliseconds. Reject the request if |now_ms - ts_ms| exceeds 300,000 (five minutes).

  2. Build the signed byte sequence: b"{ts_ms}." + raw_body.

  3. For each (X-Cerby-Signature, X-Cerby-Signature-Key-Id) pair:

    • Skip rows whose algorithm prefix you do not handle.

    • Look up the public key or shared secret for that key ID.

    • Decode the signature value: strip the ed25519= or hmac-sha256= prefix, then base64url-decode. The value is unpadded. Re-pad to the next multiple of four before decoding (== for Ed25519, = for HMAC-SHA256). Do not hard-code == across both algorithms.

    • Verify. Accept on the first row that validates. Refer to Key rotation for how this applies during a key change.

  4. If no row validates, reject the request.

Reference implementations

The following implementations are kept in sync with the CI test suite. Adapt the input plumbing to your framework before use.

Python (Ed25519)

The following is the CI-tested reference implementation in Python.

Node.js (Ed25519, Node 16+)

The following is the CI-tested reference implementation for Node.js 16 and later.

Key rotation

Retrieve your public key from GET /v1/event-webhooks/{webhook_id}/keys. During a rotation, this endpoint returns both the primary and secondary keys. Key your lookup by X-Cerby-Signature-Key-Id so each row resolves to the correct key.

When Cerby rotates a key, it dual-signs every request for 24 hours with both the old key (secondary) and the new key (primary), new key first. Accept the request if either row validates. After 24 hours, the old key expires and only the new key is used.

Cache-miss as the rotation signal. If an incoming request carries a X-Cerby-Signature-Key-Id you do not recognize, treat that as a signal that a rotation has started: re-fetch both public keys from the /keys endpoint, update your local lookup, then verify. Do not reject on an unknown key ID before refreshing.

Retries of a delivery that started before a rotation and complete after it arrive signed under the new key. Because you verify by key ID and accept on any valid row, this is transparent.

Event types

This section lists the event types available for subscription, grouped by domain. Each subsection describes the data fields specific to that event type, as defined in the Envelope format.

Account events

The following table lists account event types available for subscription:

Event type

data fields

Notes

account.created

account_id, domain, vault_id, requestor_id

account.deleted

account_id, requestor_id

Not delivered in this release.

account.disabled

account_id, requestor_id

account.enabled

account_id, requestor_id

account.credentials.updated

account_id, application, credentials_identifier

credentials_identifier is the username or email (PII).

account.credentials.rotated

account_id, application, credentials_identifier

Rotation-sourced credential change.

account.credentials.rotation_failed

account_id, application, automation_job_id, automation_link

Carries an error object. Provisional.

account.access.shared

account_id, account_name, new_teams_ids, role

account_name is PII. May co-fire with account.access.shared_with_user.

account.access.shared_with_user

account_id, account_name, new_users_ids, role

May co-fire with account.access.shared.

account.login.failed

account_id, application, automation_job_id, automation_link

Carries an error object. Provisional.

account.mfa.enabled

account_id, application, automation_job_id

Provisional. Correlates with account.mfa.setup_failed by automation_job_id.

account.mfa.disabled

account_id, provider

account.mfa.setup_failed

account_id, application, automation_job_id, automation_link

Carries an error object. Provisional.

Automation lifecycle events

All automation events include the base data fields: automation_job_id, application, account_id, automation_link. The following table lists automation lifecycle event types:

Event type

Extra data fields

Notes

automation.users.provisioned

child_count, child_failure_count

Rollup: one event per execution group.

automation.users.deprovisioned

child_count, child_failure_count

Rollup.

automation.users.role_updated

child_count, child_failure_count

Rollup.

automation.password.rotated

automation.mfa.enabled

automation.universal_logout.completed

Provisional.

automation.succeeded

action

Always-fire catch-all. Co-fires alongside the matching specific automation.* success event for the same job.

automation.failed

action, remediation

Carries an error object. See Automation failure codes.

NOTE: automation_link is the public API URL for the source automation job (GET https://<workspace>.cerby.com/api/v1/jobs/<automation_job_id>). Fetching it requires an X-API-Key with the Read automated jobs (read:automations) scope. Two preconditions apply: the workspace must have public API access enabled, and the key's user must have visibility of the job's target account. A key user without account visibility returns 401; a key missing the scope returns 403. It is not a browser link. To inspect the job interactively, use the Automation Dashboard in Cerby.

NOTE: child_count and child_failure_count are present but null in this release for rollup events.

NOTE: action is an opaque pass-through of the internal workflow code that identifies the automation type (for example, password_rotation, provide_access). Treat any value as an opaque string. Do not condition logic on specific values.

Provisional event types

Event types marked Provisional are defined in the catalog but do not yet have a confirmed delivery emitter in all cases. They may not arrive in production. The catalog will be finalized before the GA release.

Reserved event types

The following event types are reserved for a future release. Subscribing to them currently returns HTTP 400:

  • account.vault.assigned

  • account.trusted_session.established

  • automation.users.attributes_updated

  • automation.users.synced

Automation failure codes

The automation.failed event type covers all automation failures. The specific failure is identified by error.code, not by the event type. Individual error codes are not separately subscribable.

The error object has three fields:

Field
Type
Description

code

string

Stable machine identifier. Treat unrecognized values as opaque generic failures.

message

string or null

Human-readable description. May be null for some codes. Do not rely on its exact wording.

user_action

string or null

Coarse remediation category (see table below). Null or unknown values should be treated as "review in Cerby".

The following error codes are available in this release:

error.code

Tier

error.user_action

data.remediation

NotEnoughSeats

0

add_licenses

{ seats_required, seats_available, license_type }

SubscriptionDeactivated

0

reactivate_subscription

null

UnderpermissionedAccount

0

grant_permissions

null

InvalidBusinessId

0

fix_business_id

null

BadCredentials

1

review_credentials

null

EmptyPasswordError

1

review_credentials

null

AccountVaultError

1

contact_support

null

EmailNotProvidedError

2

update_contact_info

null

InvalidEmail

2

update_contact_info

null

PhoneNotProvidedError

2

update_contact_info

null

PhoneNotManagedError

2

configure_mfa

null

GeneralMFANotManagedError

2

configure_mfa

null

MFANotManagedError

2

configure_mfa

null

MFAAlreadyEnable

2

configure_mfa

null

OtherMFAMethodEnabledError

2

configure_mfa

null

VerificationMethodsNotManagedError

2

configure_mfa

null

SecureDeviceNotFoundInStorage

3

reauthenticate

null

NOTE: MFAAlreadyEnable is the exact wire value. The missing "d" is intentional. Do not correct it in code or documentation.

The following table describes each user_action value:

user_action

Meaning

review_credentials

Update the username or password stored in Cerby

update_contact_info

Add or correct an email or phone number in Cerby

configure_mfa

Set up or transfer MFA management to Cerby

reauthenticate

Re-establish the Cerby browser extension trusted session

contact_support

Contact Cerby Support to resolve the issue

add_licenses

Purchase additional seats in the target application

reactivate_subscription

Reactivate the target application subscription

grant_permissions

Grant the Cerby-managed account the required permissions

fix_business_id

Correct the business or tenant ID configured in Cerby

Tier 0 codes (add_licenses, reactivate_subscription, grant_permissions, fix_business_id) represent failures that must be remediated outside Cerby before retrying the automation. Only NotEnoughSeats populates data.remediation with machine-readable detail in this release. All other codes deliver data.remediation: null.

Delivery semantics

This section describes the guarantees your receiver can rely on when processing Cerby webhook deliveries: how to handle duplicate deliveries, how Cerby retries failed requests, and how your endpoint's response code determines what happens next. Understanding these semantics will help you build a receiver that fails safely instead of dropping events or exhausting the retry budget.

At-least-once and deduplication

Cerby guarantees at-least-once delivery. Your receiver may receive the same event more than once: on retries, or in rare cases as replays. Deduplicate as follows:

  • Per delivery: Use X-Cerby-Delivery (constant across retries) combined with X-Cerby-Attempt (rising). A retry is identified only by a repeated X-Cerby-Delivery with a higher X-Cerby-Attempt.

  • Per event: Use event_id to collapse the same logical event across different webhooks or delivery channels.

Do not deduplicate on payload content, URL, or signature equality.

Retry policy

The following table describes the retry policy parameters:

Parameter
Value

Max attempts

6 (1 initial + 5 retries)

Initial interval

60 seconds

Backoff

×4 per attempt (approximately 1 min, 4 min, 16 min, 64 min, 4.3 h)

Max interval

6 hours

Hard deadline

24 hours total

Response handling

The following table describes how Cerby handles each response code from your endpoint:

Your endpoint returns
Cerby behavior

2xx

Delivery complete.

408, 429, or 503 with Retry-After

Retried. Cerby honors Retry-After, clamped to one hour.

Any other 4xx

Fail-fast. The delivery is dead-lettered immediately, not retried.

5xx, timeout, or TCP error

Retried with standard backoff.

A 401 or 404 response fails fast and will not heal on retry. Fix the endpoint configuration and use Cerby's redelivery tooling to re-send if needed.

Respond 2xx quickly and process the event asynchronously. Slow synchronous processing risks burning your retry budget.

Sending to a Slack Incoming Webhook

When your endpoint URL is a Slack Incoming Webhook (hooks.slack.com), Cerby delivers a Slack Block Kit message instead of the standard JSON envelope. This format is required for successful Slack delivery.

The Block Kit message is a lossy subset of the full event payload. It includes the following fields:

  • Event type

  • Workspace ID

  • Application (when present in data)

  • Account ID (when present in data)

  • Automation job ID (when present in data, labeled "Automation Job"), rendered as plain text

  • occurred_at timestamp

  • For failure events: error.code, error.message, and error.user_action (labeled as "Remediation")

Fields present in the standard envelope that are not included in the Block Kit message include data.action, data.automation_link, and all other data fields not listed above. HTTPS receivers receive the full payload including these fields.

Cerby colors the attachment sidebar based on severity: #2EB67D (green) for success events and #D50200 (red) for failure events.

The following is an example Block Kit message for a success event:

Current release limitations

The following limitations apply to webhooks in this release:

  • account.deleted not delivered: The event type is available for subscription but Cerby does not deliver it in this release.

  • Canada region not supported: Webhooks are not available for Canada-resident workspaces (ca-central-1) in this release.

  • Most events are standalone: Events are generally not linked to related events. trace_id and correlation_id are reserved and always null in this release. The one exception: an automation.failed event and its eventual automation.succeeded event carry the same automation_job_id, so you can pair a failure with its later success using that field.

  • Provisional event types: Several event types are defined in the catalog but do not yet have confirmed delivery in all cases. These are marked as provisional in the Event types section above.

Feature guides:

Troubleshooting:

Last updated

Was this helpful?