---
type: how-to
title: Migrate from Cloudflare Turnstile
summary: "Swap Cloudflare Turnstile for Caputchin with minimal changes: the same client-widget-plus-server-verify shape maps almost one-to-one, and like Turnstile the result is a clean pass/fail, here is the before-and-after."
---

Caputchin uses the same two-part model as Cloudflare Turnstile, a widget on the page that produces a token, and a server-side check that confirms it, so migrating is mostly a mechanical swap, not a rewrite. Turnstile is the closest of the common providers to Caputchin, because both return an authoritative pass/fail rather than a risk score, so there is no threshold to re-tune. This guide gives the before-and-after for each piece.

If you have not yet created a Caputchin account and site key, do [create your account](/docs/getting-started/create-your-account) first; you will need a **public key** (`cpt_pub_...`) for the page and a **secret** (`cpt_sec_...`) for your backend, the same split as Turnstile's site key and secret key.

## The mental model is unchanged

```mermaid
flowchart LR
  A["Widget on your page"] -->|token| B["Your form / request"]
  B -->|token| C["Your backend"]
  C -->|secret + token| D["siteverify endpoint"]
  D -->|success?| C
```

Everything you already built around Turnstile, render a widget, collect a token, POST it to your server, verify it before trusting the request, stays. Only the names and endpoints change.

## 1. Swap the client snippet

| | Turnstile | Caputchin |
|---|---|---|
| Script | `<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer>` | `<script src="https://cdn.jsdelivr.net/npm/@caputchin/widget@3/dist/widget.js">` |
| Element | `<div class="cf-turnstile" data-sitekey="...">` | `<caputchin-widget sitekey="cpt_pub_...">` |
| Token field in a form | `cf-turnstile-response` (auto-injected) | `caputchin-token` (auto-injected) |
| Reading the token in JS | `turnstile.getResponse()` | the [`pass` event](/docs/references/widget-methods-and-events) `detail.token` |

Like Turnstile, the Caputchin widget **auto-injects a hidden token field** into the form it sits in, so if your form already did a normal POST, you only change the element and the field name your backend reads. See [add the widget](/docs/getting-started/add-the-widget) for the full client setup and [the CDN vs npm choice](/docs/integration-guides/cdn).

## 2. Swap the server verify

This is where the two are closest, the request and response shapes line up almost field for field.

| | Turnstile | Caputchin |
|---|---|---|
| Endpoint | `POST https://challenges.cloudflare.com/turnstile/v0/siteverify` | `POST https://verify.caputchin.com/v1/siteverify` |
| Request fields | `secret`, `response` | `secret`, `response` (identical) |
| Response | `{ success, challenge_ts, hostname, "error-codes", action, cdata }` | `{ success, challenge_ts, hostname, error-codes }` |
| Trust rule | act only if `success === true` | act only if `success === true` (identical) |

In most stacks the only change to your verification code is the URL and the secret value; both already branch on `success`, so there is no score threshold to port. Turnstile also accepts JSON for the verify call, exactly like Caputchin, so the body shape carries over unchanged. The full request/response reference, including the error codes, is on [verify a token on your backend](/docs/integration-guides/verify-on-your-backend); framework snippets are in [backend examples](/docs/integration-guides/backend-examples).

If your Turnstile integration read `action` or `cdata` off the response, note Caputchin has no direct equivalent; those are Turnstile-specific labels you set on the widget. Caputchin's analogous metadata (which game was played, its score) lives in the response's `platform` block and is informational only.

## What carries over, and what gets better

- **Still a clean pass/fail.** You keep the simplest possible trust rule, `if (success)`, with no probability or threshold to maintain.
- **The privacy posture you chose Turnstile for, kept.** Caputchin collects no IP, User-Agent, fingerprint, or behavioral telemetry; the protocol has nowhere to put a visitor identifier. See the [philosophy](/docs/understanding-caputchin/philosophy).
- **An optional game.** Instead of an invisible or managed challenge, you can turn verification into a short game your visitors actually play, and it is the part that holds up against AI solvers. See [add a game](/docs/getting-started/add-a-game).
- **A strict CSP stays strict.** Caputchin runs under a tight Content-Security-Policy; you allow a few origins rather than loosen your policy. See [how Caputchin sandboxes games](/docs/understanding-caputchin/game-sandboxing#the-csp-you-set-on-your-own-page).

## Gotchas

- **Two keys, two homes.** The public key (`cpt_pub_...`) goes on the page; the secret (`cpt_sec_...`) stays server-side only, exactly the Turnstile discipline. Do not ship the secret to the browser.
- **The token is single-use.** Like Turnstile, a token verifies once. Do not cache or replay it.
- **Change the field name your backend reads.** The hidden field is `caputchin-token`, not `cf-turnstile-response`, a common one-line miss.
- **No `action` / `cdata`.** If you branched on those, move that logic elsewhere; Caputchin does not carry them.

## See also

- [Add the widget](/docs/getting-started/add-the-widget): the full client setup.
- [Verify a token on your backend](/docs/integration-guides/verify-on-your-backend): the authoritative `siteverify` reference.
- [Backend examples](/docs/integration-guides/backend-examples): the verify call in each language.
- [Add a game](/docs/getting-started/add-a-game): turn verification into a game.
