<!-- Capcat 文档「迁移指南」的 Markdown 版本（对应页面 https://capcat.ai/cn/guide/migrate.html）。全部文档索引：https://capcat.ai/llms.txt -->

# 迁移指南

从 reCAPTCHA / hCaptcha / Turnstile 迁移到 Capcat 只有两处改动：

1. **前端**：换 `<script>` 地址，把验证容器换成 `<cap-widget>`；
2. **服务端**：换 siteverify 的 URL、secret，以及读取的表单字段名。

siteverify 请求同时兼容 JSON 与表单编码（`secret=…&response=…`），原有的 reCAPTCHA / hCaptcha 客户端代码通常**只改 URL 和 secret 就能用**。

开始前先在[控制台](https://console.capcat.ai/cn)创建站点，拿到你的 **site key** 和 **secret**。

## 对照总览

| | reCAPTCHA v2 | hCaptcha | Turnstile | **Capcat** |
| --- | --- | --- | --- | --- |
| 脚本地址 | `www.google.com/recaptcha/api.js` | `js.hcaptcha.com/1/api.js` | `challenges.cloudflare.com/turnstile/v0/api.js` | `capcat.ai/widget/cap.js` |
| 前端标签 | `<div class="g-recaptcha">` | `<div class="h-captcha">` | `<div class="cf-turnstile">` | `<cap-widget>` |
| 表单 token 字段 | `g-recaptcha-response` | `h-captcha-response` | `cf-turnstile-response` | `cap-token` |
| siteverify 地址 | `www.google.com/recaptcha/api/siteverify` | `api.hcaptcha.com/siteverify` | `challenges.cloudflare.com/turnstile/v0/siteverify` | `api.capcat.ai/<site-key>/siteverify` |
| 通过判定 | `success: true` | `success: true` | `success: true` | `success: true` |

## 从 reCAPTCHA 迁移

### 前端

```html
<!-- 之前 -->
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<form action="/submit" method="POST">
  <div class="g-recaptcha" data-sitekey="<recaptcha-site-key>"></div>
  <button type="submit">提交</button>
</form>

<!-- 之后 -->
<script src="https://capcat.ai/widget/cap.js"></script>
<form action="/submit" method="POST">
  <cap-widget data-cap-api-endpoint="https://api.capcat.ai/<site-key>/"></cap-widget>
  <button type="submit">提交</button>
</form>
```

`<cap-widget>` 放在 `<form>` 内会自动注入隐藏的 `cap-token` 字段随表单提交，与 `g-recaptcha-response` 的机制一致；不需要 `onload` 回调或 `grecaptcha.render()`。

### 服务端

把 siteverify 的 URL 换成 Capcat、secret 换成控制台生成的 secret、读取的字段从 `g-recaptcha-response` 换成 `cap-token`：

```js
// 之前
const token = req.body["g-recaptcha-response"];
const r = await fetch("https://www.google.com/recaptcha/api/siteverify", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({ secret: RECAPTCHA_SECRET, response: token }),
});

// 之后 —— 表单编码原样可用，只改了 URL、secret 与字段名
const token = req.body["cap-token"];
const r = await fetch("https://api.capcat.ai/<site-key>/siteverify", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({ secret: CAPCAT_SECRET, response: token }),
});

const { success } = await r.json();
```

`remoteip` 等多余参数会被忽略，不必删除。

### 用的是 reCAPTCHA v3？

纯 Capcat 验证可以用通过 / 不通过直接替代 v3：工作量证明在浏览器后台静默完成，用户只需点一次复选框，没有图形拼图。若不启用客户自有 reCAPTCHA v3，把原来的 Google 阈值判断改为检查 `success` 即可；若仍需要 Google 分数，可开启可选的 [BYOK reCAPTCHA v3 防线](https://capcat.ai/cn/guide/protection.md)，鉴权后的 Capcat siteverify 会返回与 token 绑定的 `score`，详见[服务端验证 API](https://capcat.ai/cn/guide/siteverify.md)。

## 从 hCaptcha 迁移

### 前端

```html
<!-- 之前 -->
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
<div class="h-captcha" data-sitekey="<hcaptcha-site-key>"></div>

<!-- 之后 -->
<script src="https://capcat.ai/widget/cap.js"></script>
<cap-widget data-cap-api-endpoint="https://api.capcat.ai/<site-key>/"></cap-widget>
```

### 服务端

hCaptcha 的 siteverify 与 reCAPTCHA 同构：URL 换成 `https://api.capcat.ai/<site-key>/siteverify`，secret 换成 Capcat 的，token 字段从 `h-captcha-response` 换成 `cap-token`。表单编码与 JSON 都接受，`sitekey` / `remoteip` 参数忽略即可。

```py
# 之前
requests.post("https://api.hcaptcha.com/siteverify",
    data={"secret": HCAPTCHA_SECRET, "response": request.form["h-captcha-response"]})

# 之后
requests.post("https://api.capcat.ai/<site-key>/siteverify",
    data={"secret": CAPCAT_SECRET, "response": request.form["cap-token"]})
```

## 从 Turnstile 迁移

### 前端

```html
<!-- 之前 -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<div class="cf-turnstile" data-sitekey="<turnstile-site-key>"></div>

<!-- 之后 -->
<script src="https://capcat.ai/widget/cap.js"></script>
<cap-widget data-cap-api-endpoint="https://api.capcat.ai/<site-key>/"></cap-widget>
```

### 服务端

Turnstile 的 siteverify 支持 JSON 与表单编码，Capcat 同样两者都支持——只改 URL、secret 与字段名（`cf-turnstile-response` → `cap-token`）：

```js
// 之前
const r = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ secret: TURNSTILE_SECRET, response: token }),
});

// 之后
const r = await fetch("https://api.capcat.ai/<site-key>/siteverify", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ secret: CAPCAT_SECRET, response: token }),
});
```

## 迁移注意事项

- **token 一次性有效**：siteverify 校验成功一次后立即失效，重放返回 `success: false`（与三家行为一致）。有效期 5 分钟（2026 年 11 月之前创建的站点为 20 分钟）。
- **`remoteip` 与 `idempotency_key`**：字段名与 reCAPTCHA / hCaptcha / Turnstile 相同。随着你的站点逐步开放，Capcat 会把 `remoteip` 与解题的客户端比对，并对带相同 `idempotency_key` 的重试返回原结果，详见[把 token 绑定到访客](https://capcat.ai/cn/guide/siteverify.md#把-token-绑定到访客)。
- **默认兼容、风险信息按需开启**：基础响应仍只要求检查 `success`。客户自有 reCAPTCHA v3 可增加其标准 `score`；若启用 Account Defense **Score** 模式且运行门禁已生效，还可增加 `action` 与带版本的 `risk_*` 字段。两种分数方向及缺失字段处理详见[服务端验证 API](https://capcat.ai/cn/guide/siteverify.md)。
- **域名绑定**：对应 reCAPTCHA / hCaptcha 的域名白名单，在控制台站点设置里配置「允许的域名」；绑定后其他来源的验证请求会被直接拒绝。
- **内容安全策略（CSP）**：若站点启用了严格 CSP，需放行 `capcat.ai`（脚本与 wasm）和 `api.capcat.ai`（验证请求）。
- **SPA / 自定义流程**：不用表单提交时，监听组件的 `solve` 事件拿 token，详见[前端组件](https://capcat.ai/cn/guide/widget.md)。

迁移过程中新旧验证可以并行：前端逐页替换，服务端按 token 字段名区分走哪家校验，全部切完后再移除旧依赖。
