---
type: how-to
title: Migrate from hCaptcha
summary: "Swap hCaptcha for Caputchin with minimal changes: the same client-widget-plus-server-verify shape maps almost one-to-one, here is the exact before-and-after for each piece."
---

Caputchin uses the same two-part model as hCaptcha, 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. The server exchange in particular mirrors the `siteverify` shape hCaptcha itself follows. 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 hCaptcha'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 hCaptcha, 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

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

Like hCaptcha, 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.

| | hCaptcha | Caputchin |
|---|---|---|
| Endpoint | `POST https://api.hcaptcha.com/siteverify` | `POST https://verify.caputchin.com/v1/siteverify` |
| Request fields | `secret`, `response` | `secret`, `response` (identical) |
| Response | `{ success, challenge_ts, hostname, "error-codes", score? }` | `{ success, challenge_ts, hostname, error-codes, score? }` |
| Trust rule | act only if `success === true` | act only if `success === true` (identical) |

One format note: hCaptcha's verify endpoint expects `application/x-www-form-urlencoded`, while Caputchin's accepts JSON. If your existing code sent form-encoded fields, switch the body to JSON (`{"secret":"...","response":"..."}`) with `Content-Type: application/json`. The fields themselves are unchanged. The full request/response reference 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).

## 3. Drop the score, if you used hCaptcha Enterprise

hCaptcha Enterprise returns a risk `score` (where a **higher** number means **more** risk) and asks you to pick a threshold. Caputchin does **not** work that way: `success` is an authoritative pass/fail, because the verification was actually cleared (a proof-of-work check, or a [game round re-derived on the server](/docs/understanding-caputchin/gaming-anti-cheat)), not a risk estimate.

If your code had `if (data.score < 0.7)`, replace it with `if (data.success)`. Caputchin's `score` field is unrelated: it exists only for game rounds and is the **game's** score (informational, for your own leaderboards), never a risk or bot-likelihood, so do not gate access on it. (Note the direction is also opposite, hCaptcha's score rises with risk; Caputchin's rises with game performance.)

## What carries over, and what gets better

- **The privacy posture you chose hCaptcha for, kept.** Caputchin collects no IP, User-Agent, fingerprint, or behavioral telemetry; the protocol has nowhere to put a visitor identifier, so there is none to leak or sell. See the [philosophy](/docs/understanding-caputchin/philosophy).
- **An optional game.** Instead of an image-selection 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 hCaptcha discipline. Do not ship the secret to the browser.
- **The token is single-use.** Like hCaptcha, a token verifies once. Do not cache or replay it.
- **Change the field name your backend reads.** The hidden field is `caputchin-token`, not `h-captcha-response`, a common one-line miss.
- **Body format.** Send the verify request as JSON, not form-encoded.

## 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.
