---
type: how-to
---

# Verify a Caputchin token on your backend

When a user submits the form, your backend receives a `caputchin-token` field. Pass it to `/siteverify` along with your secret. Reference for the endpoint shape is [api](../api.md#post-siteverify-customer-backend--platform).

## 1. Read the token from the form

```
caputchin-token=<wrappedToken>
```

The widget injects this hidden field. See [widget](../widget.md).

## 2. POST to `/siteverify`

```http
POST https://api.caputchin.com/siteverify
Content-Type: application/json

{
  "secret": "cpt_sec_...",
  "response": "<wrappedToken>"
}
```

Top level is reCAPTCHA-shape-compatible — if you've integrated reCAPTCHA before, the request shape is familiar.

## 3. Read the response

```json
{
  "success": true,
  "...other cap_siteverify fields": "...",
  "platform": {
    "game_id": "bubble-pop",
    "score": 0.847,
    "duration_ms": 4200
  }
}
```

The `platform.score` and `platform.duration_ms` are **client-claimed metadata**, not security signals — see [ADR-0002](../adr/0002-no-risk-scoring.md). Treat verification as binary on `success`. If you want to record `score` for a leaderboard or analytics, that's fine; just don't gate trust decisions on it.

## 4. Handle replay protection

The platform verifies the wrapped token's HMAC and rejects replays automatically. You don't need to track tokens client-side. A token is single-use; submitting the same one twice returns `success: false`.

## 5. Use any HTTP client

There is no first-party server SDK in any language — see [ADR-0011](../adr/0011-drop-server-library-mvp.md). The endpoint is a plain JSON POST, so `fetch` in Node, `requests` in Python, `Net::HTTP` in Ruby, or whatever HTTP client your backend already uses works directly. Copy-paste examples for the most common languages live in [snippets](../snippets.md). For a typed client, run your preferred generator against the [OpenAPI spec](../api.md#openapi-spec) — the contract is three endpoints, so the generated output is short.

## What about the customer's risk model?

If you want risk scoring (block / challenge / allow tiers), build it on top of verified PoW with your own signals. The platform deliberately does not provide a confidence score — see [ADR-0002](../adr/0002-no-risk-scoring.md) and [principles](../principles.md#score-is-just-game-score).

## Common mistakes

- **Verifying client-side.** Don't. The widget's `complete` event is UX-gating only. All trust decisions happen server-side with your secret.
- **Sending the secret to the browser.** Don't. The secret never leaves your backend.
- **Forgetting to add your domain to the allowlist.** Tokens issued from non-allowed origins fail verification. Configure in the [dashboard](../dashboard.md).
- **Using `score` to make trust decisions.** It's metadata, not security. See above.
