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; pollingverification/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, noauth_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_idis the same value asrequest_id— both are thereq_<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
- In the Console → Settings → Webhooks, open the webhooks portal.
- Add an endpoint with your HTTPS URL (e.g.
https://yourapp.com/api/botshield-webhook). - Copy that endpoint’s Signing Secret — it starts with
whsec_. Store it as an environment variable; treat it like a password.
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
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 thetype+request_id.
Idempotency, delivery & retries
- Idempotency — dedupe on
event_id(or thesvix-idheader). 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.
Related
- 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)