---
type: how-to
title: Migrate from reCAPTCHA
description: Swap reCAPTCHA v2 / v3 / Enterprise for Caputchin with minimal backend changes.
---

# Migrate from reCAPTCHA

Caputchin's `/siteverify` is intentionally shape-compatible with Google's reCAPTCHA verify endpoint. The browser surface differs (custom element instead of a script + global), but your server-side code mostly keeps working as-is.

This guide assumes you already have Caputchin site keys. See [quickstart](quickstart.md) if not.

## Field mapping

### Browser HTML

| reCAPTCHA | Caputchin |
|---|---|
| `<script src="https://www.google.com/recaptcha/api.js"></script>` | `<script src="https://cdn.jsdelivr.net/npm/@caputchin/widget@1/dist/widget.js"></script>` |
| `<div class="g-recaptcha" data-sitekey="..."></div>` | `<caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>` |
| Form field `g-recaptcha-response` | Form field `caputchin-token` |
| `data-callback="onSubmit"` | `addEventListener("complete", onSubmit)` |

For invisible reCAPTCHA v2, the equivalent is the Caputchin **invisible default** — just omit the `game` attribute. For v3 (no challenge UI), use the same invisible default.

### Backend `/siteverify`

| reCAPTCHA request body | Caputchin request body |
|---|---|
| `secret=YOUR_SECRET` | `secret: "cpt_sec_..."` |
| `response=USER_TOKEN` | `response: "<caputchin-token>"` |
| `remoteip=USER_IP` (optional) | not used — Caputchin does not collect IPs |

| reCAPTCHA response | Caputchin response |
|---|---|
| `success: true \| false` | `success: true \| false` (same) |
| `error-codes: []` | `error-codes: []` (same) |
| `score: 0.0–1.0` (v3) | not provided — Caputchin treats game score as metadata, not a trust signal |
| `action: "..."` (v3) | not provided — see [principles](#principles) below |
| `challenge_ts` | not provided |
| `hostname` | not provided |

The shape compatibility means a server using `recaptcha.verify(token)` can usually be redirected to Caputchin by changing **only the URL** and **only the secret**.

## Step-by-step swap

### 1. Replace the script tag

Remove the reCAPTCHA `<script>` and replace `<div class="g-recaptcha">` with `<caputchin-widget>`. If your form uses `g-recaptcha-response`, rename the field accessor in your backend to `caputchin-token`.

### 2. Update the secret

Replace your reCAPTCHA secret in your environment with the Caputchin secret (`cpt_sec_...`). Same env var name is fine.

### 3. Update the verify URL

Change the verify endpoint:

```diff
-const VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify";
+const VERIFY_URL = "https://api.caputchin.com/api/v1/siteverify";
```

### 4. Drop reCAPTCHA-specific assertions

If your verifier reads `score`, `action`, `hostname`, or `challenge_ts`, remove those checks. Caputchin gives you `success` + `error-codes` and that is the entire trust signal. The wrapped token also carries `game_id`, `score`, and `duration_ms`, but those are **client-claimed metadata**, not security signals — do not gate access on them.

### 5. Test in staging

Submit the form with a real challenge and inspect the verdict. The shape should be familiar:

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

If anything is wrong, the [troubleshooting](troubleshooting.md) guide maps every error code.

## Principles {#principles}

Two things are intentionally different from reCAPTCHA:

| Difference | Why |
|---|---|
| No risk score | Caputchin makes no behavioral risk claims. Cap's proof-of-work is the entire humanity signal. |
| No IP / user-agent / fingerprint collection | Caputchin is structurally private — there is no `remoteip` field because the platform does not store user IPs. |

The full positioning is in [privacy](../privacy.md).

## Rollback plan

If you need to roll back, keep both the old and new scripts side-by-side for one deploy. The form field name is the only meaningful collision — rename one of them temporarily, then swap when you are confident.
