Skip to main content

Webhooks

BotShield delivers verification events to your endpoint via Svix. Each delivery is cryptographically signed — you verify the signature with your endpoint’s signing secret, exactly as shown below. This is the recommended way to receive results; polling verification/status is supported but adds latency and cost.

Anonymous by construction

Census performs one operation: anonymous human attestation — one operation, no modes. No identity ever crosses the boundary — verification payloads carry no email, no auth_mode, no BotShield user id, and no identity-bearing token. You learn that a human verified, never who. Rows correlate to your own system by request_id (and the metadata you supplied). To attach a known identity on your side, key off the request_id you generated when you created the verification.

Event Types

There are exactly three events. census.human_unavailable merges the former failed and expired cases — distinguish them by the payload (failed_at + reason for a denial/error vs. expired_at for a TTL lapse). Exactly one event fires per verification request. Once delivered, the request is terminal — no further events fire for that request_id.
Result states in the webhook. Census exposes three result states — Human Verified, MultiPass Active, and Human Unavailable — and the webhook now carries its own event for each: Human Verified = census.human_verified, MultiPass Active = census.multipass_active, Human Unavailable = census.human_unavailable. MultiPass Active no longer collapses into Human Verified — webhook-only partners receive census.multipass_active directly, mirroring the embed’s synchronous botshield:multipass-status distinction. Human Unavailable covers both denial/error and TTL expiry; tell them apart by the payload fields (failed_at + reason vs. expired_at).

Payloads

event_id is the same value as request_id — both are the req_<hex> attestation id. They are aliases for the one identifier; dedupe on either.

census.human_verified

census.multipass_active

A returning human resolved via MultiPass continuity at the precheck — no fresh challenge was issued. Same anonymous payload shape as census.human_verified; this is the webhook-only mirror of the embed’s synchronous MultiPass Active result.

census.human_unavailable

Fires on a denial/error (carries failed_at + reason) or on a TTL lapse (carries expired_at). The two cases are distinguished by which timestamp is present. Denied / errored:
reason is one of: user_denied, device_lock_required, internal_error, platform_declined. Expired (link TTL lapsed — default 10 minutes):

Fields

Get your signing secret

  1. In the Console → Settings → Webhooks, open the webhooks portal.
  2. Add an endpoint with your HTTPS URL (e.g. https://yourapp.com/api/botshield-webhook).
  3. Copy that endpoint’s Signing Secret — it starts with whsec_. Store it as an environment variable; treat it like a password.
Each endpoint has its own secret. From the same portal you can send test events, inspect delivery logs, and replay failed deliveries.

Verify the signature (server-side)

Every delivery includes three headers — svix-id, svix-timestamp, svix-signature — and is signed with your endpoint’s whsec_ secret. Verify with the official svix library. You must pass the raw, unparsed request body to the verifier (a re-serialized JSON object will not match the signature).

Node / Express

The svix library is also available for Python, Go, Rust, Java/Kotlin, Ruby, C#, and PHP — the verification call is identical in shape (Webhook(secret).verify(rawBody, headers)). See the Svix docs for per-language snippets.
Why the signature, not a token? Authenticity comes from the Svix envelope signature over the whole payload — there is no identity-bearing token to validate, and nothing in the body to decode for a user. Verify the signature, then trust the type + request_id.

Idempotency, delivery & retries

  • Idempotency — dedupe on event_id (or the svix-id header). Retries reuse the same id.
  • Acknowledge with any 2xx (empty body fine). Non-2xx triggers retries with exponential backoff.
  • Inspect & replay — every attempt is logged in the Console webhooks portal; you can replay failed deliveries there.
  • Human Presence — the verdict + reason model and the user-facing result states
  • Trusted Account Signal — how linked verified accounts strengthen MultiPass durability (internal UUID, never identity)