---
type: reference
title: Webhook payload reference
summary: The exact request a hosted-verification webhook receives, every field in the body, and the headers Caputchin sends.
---

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](/docs/hosted-verification/set-up); for the concept, see [verify without a backend](/docs/hosted-verification/overview).

## 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):

```json
{
  "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](/docs/hosted-verification/statistics) page.

## See also

- [Set up hosted verification](/docs/hosted-verification/set-up): wire a webhook and fire a test delivery.
- [Verify without a backend](/docs/hosted-verification/overview): the concept, the privacy posture, and the URL-safety rules.
- [Hosted verification statistics](/docs/hosted-verification/statistics): reading delivery success and failure.
