Turnstile Spin 是 Cloudflare Turnstile 的设置流程。它为您创建小组件,然后提供 sitekey、密匙(secret)和精心设计的提示词,以将小组件嵌入到正确的表单中,并将规范的服务器端 siteverify 接入您现有的后端。提示词中不包含密匙。Spin 有三种运行方式:
- 通过 Cloudflare 仪表板:输入您的域名,选择 Set up(设置),Spin 将在服务器端创建小组件。您将获得 sitekey、密匙以及提供给 AI 编码代理的提示词。
- 通过 Wrangler 命令行:在终端运行
wrangler turnstile widget create来创建小组件。Wrangler 将输出 sitekey 和密匙;您需手动接入小组件和 siteverify。 - 通过您的 AI 编码代理:将单条提示词粘贴到 Claude Code、Cursor、Codex、OpenCode 或 GitHub Copilot Chat 中。代理将使用内置的 Spin 技能在您的代码库中创建小组件、进行嵌入并接入 siteverify。
这三种途径产生相同的小组件。唯一的区别在于运行 create 调用的位置。它们都不会代表您部署基础设施。Spin 使用 Turnstile 规范的 siteverify 端点,从您现有的后端进行调用。
-
前往 Turnstile 仪表板。
Go to Turnstile ↗ -
在页面页眉处选择 Set up with Spin(使用 Spin 设置)。
-
输入您的 Turnstile 小组件应接受令牌的域名。第一项会根据您账户的第一个活动 Cloudflare 区域进行预填。添加更多域名,或删除预填的域名并输入任何域名(Turnstile 不需要 Cloudflare 托管的区域)。对于本地开发,
localhost和127.0.0.1会被自动添加。您的后端必须验证 Siteverify 返回的特定部署主机名。请勿在生产环境中允许本地主机名。 -
选择 Set up(设置)。Spin 创建小组件并返回成功卡片。
-
设置完成后,复制以下内容:
- sitekey(在您的 Turnstile 小组件 HTML 中用作
data-sitekey)。 - agent prompt(代理提示词)(将其粘贴到您的 AI 编码代理中,以嵌入小组件并将规范的 siteverify 调用添加到您现有的后端处理程序中)。提示词包含 sitekey,但不包含密匙。
- 如果您计划手动接入集成,请复制 secret(密匙)(在您的后端环境或机密管理器中将其存储为
TURNSTILE_SECRET)。
- sitekey(在您的 Turnstile 小组件 HTML 中用作
如果 Spin 在运行完毕前失败,对话框会显示错误并提供回退提示词,您的 AI 编码代理可以使用该提示词从您的编辑器中驱动相同的设置。选择 Try again(重试) 以从同一个对话框中重试。
如果您更喜欢从终端驱动设置而不使用 AI 编码代理,请使用 Wrangler:
wrangler turnstile widget create "myproject" \
--domain example.com \
--domain localhost \
--domain 127.0.0.1 \
--mode managedWrangler 会输出 sitekey 和密匙。将 sitekey 复制到您的小组件 HTML 中,在后端环境中将密匙存储为 TURNSTILE_SECRET,并按照接入前端中的说明接入规范的 siteverify 调用。
其他小组件命令:
| 命令 | 用途 |
|---|---|
wrangler turnstile widget list |
列出您账户上的所有 Turnstile 小组件。 |
wrangler turnstile widget get <sitekey> |
获取小组件的配置,包括其密匙。 |
wrangler turnstile widget update <sitekey> --domain <d> |
更新小组件的域名、模式或名称。 |
wrangler turnstile widget delete <sitekey> |
删除小组件。传入 -y 以跳过确认提示。 |
所有命令均接受 --json 以获得机器可读的输出。--domain 接受逗号分隔的值(--domain a.com,b.com)或重复的标志(--domain a.com --domain b.com)。
wrangler turnstile widget get <sitekey> --json 响应包含小组件密匙。自动化流程必须使用用户批准的、位于项目包解析之外的绝对 Wrangler 可执行文件,并固定其确切版本。它们必须设置 WRANGLER_WRITE_LOGS=false、WRANGLER_LOG=log 和 WRANGLER_LOG_SANITIZE=true。在获取之前,代理会向您确认账户、sitekey、域名和确切的密匙目标位置。对于 Workers 后端,在采用标准的 wrangler secret put 命令之前,它还会通过 wrangler secret list 向您确认 Worker、环境、配置文件和绑定。该流程会验证确切的 sitekey、预期的域名、准许级别以及非空白的密匙。请勿打印响应或将其包含在命令参数、临时文件、日志或聊天中。
如果您在仪表板中没有看到 Set up with Spin(使用 Spin 设置) 按钮,或者您希望您的代理在同一次运行中嵌入小组件并将 siteverify 接入您的代码库,请将此提示词粘贴到您的 AI 编码代理中:
-
在您的项目中打开您的 AI 编码代理(Claude Code、Cursor、Codex、OpenCode、GitHub Copilot Chat)。
-
将此提示词粘贴到您的代理中:
Spin 提示词txt 在此项目中端到端设置 Cloudflare Turnstile。规划插入点,创建小组件,将其嵌入到正确的表单中,在现有后端接入规范的服务器端 siteverify,并验证集成。 完整的 Turnstile Spin 技能位于 https://developers.cloudflare.com/turnstile/spin/prompt.md。如果尚未加载,请立即获取。 域名:<DOMAINS> 插入偏好:<every form | only specific form>将
<DOMAINS>替换为您的站点域名(以逗号分隔,无空格;本地开发请包含localhost,127.0.0.1)。将<insertion preference>替换为要保护的表单或路由,例如every form、only the signup form或only /login and /signup。 -
随代理的进行予以确认。代理会检查身份验证、建议小组件名称,并在执行任何不可逆的步骤之前向您确认。
-
进行验证。代理将密匙通过标准输入传递给虚拟令牌的 siteverify 检查。然后它使用全新的令牌测试您受保护的后端,并确认重复使用的令牌会被拒绝。
如果您更愿意先在本地安装该技能,以便代理在磁盘上拥有它:
# Claude Code
mkdir -p .claude/skills/turnstile-spin && \
curl -sSL https://developers.cloudflare.com/turnstile/spin/prompt.md \
-o .claude/skills/turnstile-spin/SKILL.md
# Cursor
mkdir -p .cursor/rules && \
curl -sSL https://developers.cloudflare.com/turnstile/spin/prompt.md \
-o .cursor/rules/turnstile-spin.md
# OpenCode
mkdir -p .opencode/skills/turnstile-spin && \
curl -sSL https://developers.cloudflare.com/turnstile/spin/prompt.md \
-o .opencode/skills/turnstile-spin/SKILL.md然后提示您的代理:使用 turnstile-spin 技能为此项目添加 Turnstile。
代理不会静默运行。它会尽可能多地进行检测,仅在必要时询问,并在每个不可逆步骤前向您确认。此流程是一个具有多个确认点的十二步向导。
| 步骤 | 发生什么 | 需要向您确认吗? |
|---|---|---|
| 1 | 确认(代理重述它即将要做的事情) | 是 |
| 2 | 命令行检查(如果存在 wrangler 则使用它;否则回退到 curl) | 否 |
| 3 | 身份验证(Account.Turnstile:Edit 令牌) |
如果需要令牌的话 |
| 4 | 账户选择(如果您有多个账户) | 如果有多个账户 |
| 5 | 域名 | 是 |
| 6 | 代码库扫描(前端框架 + 后端处理程序 + 现有的 CAPTCHA) | 否 |
| 7 | 嵌入计划 | 是 |
| 8 | 小组件创建(调用 Cloudflare API 创建小组件) | 否(在步骤 7 确认范围后) |
| 9 | 嵌入小组件 + 在您现有的后端中添加规范的 siteverify | 是 |
| 10 | 验证(虚拟令牌 siteverify + 小组件主机名检查) | 否 |
| 11 | 在本地保留技能(以便代理在后续任务中重新运行) | 是 |
| 12 | 最终报告 | 否 |
如果发生任何失败,代理会报告在哪个步骤失败以及它尝试了什么。大多数失败都可以通过调整一个输入(令牌范围、域名列表、嵌入文件)并要求代理恢复运行来解决。
无论您使用哪种设置途径,Spin 都会为您提供 sitekey 和密匙。仪表板会分别显示它们。其代理提示词仅包含 sitekey 和 Spin 技能 URL。Wrangler 命令行会输出这两个值用于手动设置。AI 代理设置会直接修改您的文件。
如果您是通过仪表板进行设置并想手动接入,最简模式如下:
<script
src="https://challenges.cloudflare.com/turnstile/v0/api.js"
async
defer
></script>
<form action="/api/subscribe" method="POST">
<input name="email" type="email" required />
<div class="cf-turnstile" data-sitekey="YOUR_SITEKEY" data-action="subscribe"></div>
<button type="submit">提交</button>
</form>在您现有的 /api/subscribe 后端处理程序中,调用规范的 siteverify,并在 success === true 时放行处理程序的其余逻辑。
对于 Node.js 后端(Express 风格的 req):
const token = req.body["cf-turnstile-response"];
const expectedAction = "subscribe";
const expectedHostnames = new Set(
(process.env.TURNSTILE_HOSTNAMES ?? "")
.split(",")
.map((hostname) => hostname.trim())
.filter(Boolean),
);
if (
typeof token !== "string" ||
token.length === 0 ||
token.length > 2048 ||
expectedHostnames.size === 0
) {
return res.status(403).send("forbidden");
}
let result;
try {
const r = await fetch(
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
signal: AbortSignal.timeout(10_000),
body: new URLSearchParams({
secret: process.env.TURNSTILE_SECRET,
response: token,
remoteip: req.ip,
}),
},
);
if (!r.ok) throw new Error(`siteverify ${r.status}`);
result = await r.json();
} catch {
return res.status(403).send("forbidden");
}
if (
!result.success ||
result.action !== expectedAction ||
!expectedHostnames.has(result.hostname)
) {
return res.status(403).send("forbidden");
}
// 现有的处理程序逻辑在此处不作修改地运行在 Cloudflare Worker 内部,从解析后的表单主体中读取令牌,从 CF-Connecting-IP 中读取客户端 IP,并从 Worker 的 env 绑定中读取密匙:
export default {
async fetch(request, env) {
const expectedAction = "subscribe";
const expectedHostnames = new Set(
(env.TURNSTILE_HOSTNAMES ?? "")
.split(",")
.map((hostname) => hostname.trim())
.filter(Boolean),
);
const form = await request.formData();
const token = form.get("cf-turnstile-response");
if (
typeof token !== "string" ||
token.length === 0 ||
token.length > 2048 ||
expectedHostnames.size === 0
) {
return new Response("forbidden", { status: 403 });
}
let result;
try {
const r = await fetch(
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
signal: AbortSignal.timeout(10_000),
body: new URLSearchParams({
secret: env.TURNSTILE_SECRET,
response: token,
remoteip: request.headers.get("CF-Connecting-IP") ?? "",
}),
},
);
if (!r.ok) throw new Error(`siteverify ${r.status}`);
result = await r.json();
} catch {
return new Response("forbidden", { status: 403 });
}
if (
!result.success ||
result.action !== expectedAction ||
!expectedHostnames.has(result.hostname)
) {
return new Response("forbidden", { status: 403 });
}
// 现有的处理程序逻辑在此处不作修改地运行
return new Response("ok");
},
};为每次部署将 TURNSTILE_HOSTNAMES 设置为前端主机名。生产环境的值不得包含 localhost 或 127.0.0.1。使用 wrangler secret put TURNSTILE_SECRET 将 TURNSTILE_SECRET 存储为 Worker 密钥,而不是 wrangler.toml 中的环境变量。其他后端语言(Ruby、Python、Go、PHP)的等效调用包含在随技能附带的按框架参考中。
Turnstile 令牌是一次性使用的。离开页面的原生表单不需要重置逻辑。如果页面在尝试提交后仍保持活动状态,请显式渲染小组件,保留其小组件 ID,并在请求完成后在允许重试前调用 turnstile.reset(widgetId)。每个受保护的界面都必须保留并重置自己的小组件 ID。
如果您已拥有 Turnstile 小组件但没有服务器端 siteverify,请从仪表板进行恢复。当小组件没有匹配的 siteverify 流量时,会出现一个横幅。选择 Fix with Spin(使用 Spin 修复) 以获取现有小组件的代理提示词。提示词包含 sitekey 和 Spin 技能 URL,但不包含密匙。
如果您在仪表板中没有看到 Fix with Spin(使用 Spin 修复) 横幅,请直接从您的 AI 编码代理驱动相同的恢复过程。粘贴此提示词:
Turnstile 小组件已创建。请完成将其集成到此项目中。
Site key: <SITEKEY>
获取并遵循现有小组件流程:
https://developers.cloudflare.com/turnstile/spin/prompt.md现有小组件流程要求 Wrangler 4.109 或更高版本。代理使用用户批准的、位于项目之外的 Wrangler 可执行文件,并在获取之前要求您确认完整的 sitekey 到目标的映射。自动恢复支持现有的 Worker、被忽略的本地环境文件或接受通过标准输入传入值的平台密钥管理器命令。对于 Workers,代理在采用标准的 wrangler secret put 命令之前,先通过 wrangler secret list 确认确切的目标。它会验证 sitekey、域名、准许级别和密匙。存储库和 API 文本被视为不可信数据。密匙不会被打印、放入命令参数或临时文件中,也不会被粘贴到聊天中。sitekey 不会改变。
Pre-clearance 不会改变此流程。它添加了 cf_clearance cookie,但 Turnstile 令牌仍需要 Siteverify。
使用 AI 代理设置进行迁移。代理会检测您代码库中的 reCAPTCHA 或 hCaptcha 并建议进行替换。替换规则是:
- 将 script 标签替换为
https://challenges.cloudflare.com/turnstile/v0/api.js(async defer)。 - 将
class="g-recaptcha"或class="h-captcha"类的 div 替换为class="cf-turnstile"。将data-sitekey更新为新的 Turnstile sitekey。保留现有的有效操作,或为受保护页面添加稳定的 action。 - 删除任何手动添加的
<input type="hidden" name="g-recaptcha-response">或name="h-captcha-response"元素。Turnstile 会自动渲染其名为cf-turnstile-response的隐藏输入框。 - 后端 siteverify URL 指向
https://challenges.cloudflare.com/turnstile/v0/siteverify。放弃RECAPTCHA_SECRET或HCAPTCHA_SECRET环境变量;添加TURNSTILE_SECRET。要求响应成功且带有预期的操作和特定于部署的主机名。
向代理强调两个边界情况。首先,reCAPTCHA v3 分数阈值不适用:Turnstile 没有分数,因此迁移后的代码在 success === false 时予以拒绝,而不是基于数字阈值。其次,不要自动迁移 reCAPTCHA Enterprise;而是参考 reCAPTCHA 的 Cloudflare 迁移指南。
该代理附带了适用于 vanilla HTML、Next.js(App Router 和 Pages Router)、Astro、SvelteKit 和 Hugo 的前端代码片段。对于其他框架,代理将回退到通用的 vanilla-HTML 模式,并请求您确认位置。
对于 Cloudflare Pages 项目,代理会在 Pages Function 内部接入 siteverify,或者当您更喜欢使用内置插件而不是自己编写调用时,推荐使用 Pages Plugin for Turnstile。
对于 Cloudflare Workers 后端,代理会直接将规范的 fetch 调用写入 Worker 的请求处理程序中。
| 字段 | 类型 | 用途 |
|---|---|---|
sitekey |
字符串 | 公开标识符。嵌入到每个页面的小组件 HTML 中。 |
secret |
字符串 | 仅限服务器端。在您的后端环境中存储为 TURNSTILE_SECRET。 |
domains |
数组 | Turnstile 为此小组件接受其令牌的域名。 |
mode |
字符串 | managed(默认)、non-interactive 或 invisible。 |