Webhook payload reference
When a submission clears verification, the forwarder sends one POST to your configured webhook URL. This page is the exact contract for that request. For the setup walkthrough, see set up hosted verification; for the concept, see verify without a backend.
Request line and headers
The forwarder sends a POST with a JSON body and these headers:
| Header | Value |
|---|---|
content-type | application/json |
accept | application/json |
user-agent | Caputchin-Forwarder/0.1 |
x-caputchin-test | 1, present only on a dashboard Test delivery; absent on real submissions |
The call is sent with redirects disabled and a timeout of a few seconds. The request is not signed; the secrecy of the webhook URL is what authenticates it.
Body
The body is a JSON object with two top-level keys, caputchin (verification metadata Caputchin adds) and form (your submitted fields):
{
"caputchin": {
"site_key": "cpt_pub_...",
"session_id": "...",
"game_id": "caputchin/games/leaf-memory",
"score": 847,
"duration_ms": 4200,
"verified_at": 1748640000000
},
"form": {
"email": "visitor@example.com",
"message": "Hello!"
}
}The caputchin object
| Field | Type | Meaning |
|---|---|---|
site_key | string | The public cpt_pub_... key the submission came from. |
session_id | string | An opaque verification-session id, useful for correlation or deduplication. On a test delivery it is prefixed test_. |
game_id | string or null | The game the visitor played. Null when the verification ran without a game. |
score | number or null | The game score. Client-claimed metadata for analytics, never a trust signal. Null when not applicable. |
duration_ms | number or null | How long the visitor spent playing the game, in milliseconds. Client-claimed, not a trust signal. Null when not applicable (for example a verification with no game). |
verified_at | number | When Caputchin verified the token, as a Unix epoch in milliseconds. |
test | boolean (optional) | Present and true only on a dashboard test delivery. Absent on real submissions. |
game_id, score, and duration_ms are each nullable on a real submission, so guard for null before reading them. None of the caputchin fields are a trust decision: the trust is that the request arrived at all, because a submission that fails verification is never forwarded.
The form object
form is a flat object of your submitted fields as string keys and string values, exactly as posted, with one change: the caputchin-token field is stripped before delivery, so it never appears here. The forwarder accepts text fields only; a submission carrying a file upload is rejected before any delivery.
What your handler should return
Return any 2xx status to acknowledge receipt. The forwarder treats a non-2xx response, a timeout, or a connection failure as a failed delivery, and a failed delivery is not retried. Track delivery health on the statistics page.
See also
- Set up hosted verification: wire a webhook and fire a test delivery.
- Verify without a backend: the concept, the privacy posture, and the URL-safety rules.
- Hosted verification statistics: reading delivery success and failure.