cloudflare-turnstile

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cloudflare Turnstile

Cloudflare Turnstile

Status: Production Ready ✅ | Last Verified: 2025-11-26
Dependencies: None (optional: @marsidev/react-turnstile for React)

状态:已就绪可用于生产环境 ✅ | 最后验证时间:2025-11-26
依赖项:无(可选:React项目可使用@marsidev/react-turnstile)

Quick Start (10 Minutes)

快速开始(10分钟)

1. Create Turnstile Widget

1. 创建Turnstile小部件

Get your sitekey and secret key from Cloudflare Dashboard.
bash
undefined
从Cloudflare控制台获取站点密钥(sitekey)和密钥(secret key)。
bash
undefined

Create new widget → Copy sitekey (public) and secret key (private)

创建新小部件 → 复制公开的sitekey和私有的secret key


**Why this matters:**
- Each widget has unique sitekey/secret pair
- Sitekey goes in frontend (public)
- Secret key ONLY in backend (private)
- Use different widgets for dev/staging/production

**重要性说明**:
- 每个小部件都有唯一的sitekey/secret密钥对
- sitekey用于前端(公开)
- secret key仅用于后端(私有)
- 开发/预发布/生产环境使用不同的小部件

2. Add Widget to Frontend

2. 在前端添加小部件

Embed the Turnstile widget in your HTML form.
html
<!DOCTYPE html>
<html>
<head>
  <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
  <form id="myForm" action="/submit" method="POST">
    <input type="email" name="email" required>
    <!-- Turnstile widget renders here -->
    <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
    <button type="submit">Submit</button>
  </form>
</body>
</html>
CRITICAL:
  • Never proxy or cache
    api.js
    - must load from Cloudflare CDN
  • Widget auto-creates hidden input
    cf-turnstile-response
    with token
  • Token expires in 5 minutes
  • Each token is single-use only
将Turnstile小部件嵌入HTML表单中。
html
<!DOCTYPE html>
<html>
<head>
  <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
  <form id="myForm" action="/submit" method="POST">
    <input type="email" name="email" required>
    <!-- Turnstile小部件将在此处渲染 -->
    <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
    <button type="submit">提交</button>
  </form>
</body>
</html>
关键注意事项
  • 请勿代理或缓存
    api.js
    脚本——必须从Cloudflare CDN加载
  • 小部件会自动创建隐藏输入框
    cf-turnstile-response
    存储令牌
  • 令牌有效期为5分钟
  • 每个令牌仅可使用一次

3. Validate Token on Server

3. 在服务端验证令牌

ALWAYS validate the token server-side. Client-side verification alone is not secure.
typescript
// Cloudflare Workers example
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const formData = await request.formData()
    const token = formData.get('cf-turnstile-response')
    const ip = request.headers.get('CF-Connecting-IP')

    // Validate token with Siteverify API
    const verifyFormData = new FormData()
    verifyFormData.append('secret', env.TURNSTILE_SECRET_KEY)
    verifyFormData.append('response', token)
    verifyFormData.append('remoteip', ip)

    const result = await fetch(
      'https://challenges.cloudflare.com/turnstile/v0/siteverify',
      {
        method: 'POST',
        body: verifyFormData,
      }
    )

    const outcome = await result.json()

    if (!outcome.success) {
      return new Response('Invalid Turnstile token', { status: 401 })
    }

    // Token valid - proceed with form processing
    return new Response('Success!')
  }
}

必须在服务端验证令牌。仅客户端验证并不安全。
typescript
// Cloudflare Workers示例
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const formData = await request.formData()
    const token = formData.get('cf-turnstile-response')
    const ip = request.headers.get('CF-Connecting-IP')

    // 使用Siteverify API验证令牌
    const verifyFormData = new FormData()
    verifyFormData.append('secret', env.TURNSTILE_SECRET_KEY)
    verifyFormData.append('response', token)
    verifyFormData.append('remoteip', ip)

    const result = await fetch(
      'https://challenges.cloudflare.com/turnstile/v0/siteverify',
      {
        method: 'POST',
        body: verifyFormData,
      }
    )

    const outcome = await result.json()

    if (!outcome.success) {
      return new Response('无效的Turnstile令牌', { status: 401 })
    }

    // 令牌验证通过——继续处理表单
    return new Response('操作成功!')
  }
}

The 3-Step Setup Process

三步设置流程

Step 1: Create Widget Configuration

步骤1:创建小部件配置

  1. Log into Cloudflare Dashboard
  2. Navigate to Turnstile section
  3. Click "Add Site"
  4. Configure:
    • Widget Mode: Managed (recommended), Non-Interactive, or Invisible
    • Domains: Add allowed hostnames (e.g., example.com, localhost for dev)
    • Name: Descriptive name (e.g., "Production Login Form")
Key Points:
  • Use separate widgets for dev/staging/production
  • Restrict domains to only those you control
  • Managed mode provides best balance of security and UX
  • localhost must be explicitly added for local testing
  1. 登录Cloudflare控制台
  2. 导航至Turnstile板块
  3. 点击「添加站点」
  4. 配置以下内容:
    • 小部件模式:托管模式(推荐)、非交互式模式或隐形模式
    • 域名:添加允许的主机名(例如example.com,开发环境可添加localhost)
    • 名称:描述性名称(例如「生产环境登录表单」)
核心要点
  • 开发/预发布/生产环境使用独立的小部件
  • 仅添加受你控制的域名
  • 托管模式在安全性和用户体验间达到最佳平衡
  • 本地测试需显式添加localhost

Step 2: Client-Side Integration

步骤2:客户端集成

Choose between implicit or explicit rendering:
Implicit Rendering (Recommended for static forms):
html
<!-- 1. Load script -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

<!-- 2. Add widget -->
<div class="cf-turnstile"
     data-sitekey="YOUR_SITE_KEY"
     data-callback="onSuccess"
     data-error-callback="onError"></div>

<script>
function onSuccess(token) {
  console.log('Turnstile success:', token)
}

function onError(error) {
  console.error('Turnstile error:', error)
}
</script>
Explicit Rendering (For SPAs/dynamic UIs):
typescript
// 1. Load script with explicit mode
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" defer></script>

// 2. Render programmatically
const widgetId = turnstile.render('#container', {
  sitekey: 'YOUR_SITE_KEY',
  callback: (token) => {
    console.log('Token:', token)
  },
  'error-callback': (error) => {
    console.error('Error:', error)
  },
  theme: 'auto',
  execution: 'render', // or 'execute' for manual trigger
})

// Control lifecycle
turnstile.reset(widgetId)        // Reset widget
turnstile.remove(widgetId)       // Remove widget
turnstile.execute(widgetId)      // Manually trigger challenge
const token = turnstile.getResponse(widgetId) // Get current token
React Integration (using @marsidev/react-turnstile):
tsx
import { Turnstile } from '@marsidev/react-turnstile'

export function MyForm() {
  const [token, setToken] = useState<string>()

  return (
    <form>
      <Turnstile
        siteKey={TURNSTILE_SITE_KEY}
        onSuccess={setToken}
        onError={(error) => console.error(error)}
      />
      <button disabled={!token}>Submit</button>
    </form>
  )
}
选择隐式渲染或显式渲染:
隐式渲染(推荐用于静态表单):
html
<!-- 1. 加载脚本 -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

<!-- 2. 添加小部件 -->
<div class="cf-turnstile"
     data-sitekey="YOUR_SITE_KEY"
     data-callback="onSuccess"
     data-error-callback="onError"></div>

<script>
function onSuccess(token) {
  console.log('Turnstile验证成功:', token)
}

function onError(error) {
  console.error('Turnstile验证失败:', error)
}
</script>
显式渲染(适用于单页应用/动态UI):
typescript
// 1. 加载显式模式脚本
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" defer></script>

// 2. 程序化渲染小部件
const widgetId = turnstile.render('#container', {
  sitekey: 'YOUR_SITE_KEY',
  callback: (token) => {
    console.log('令牌:', token)
  },
  'error-callback': (error) => {
    console.error('错误:', error)
  },
  theme: 'auto',
  execution: 'render', // 或'execute'用于手动触发验证
})

// 控制小部件生命周期
turnstile.reset(widgetId)        // 重置小部件
turnstile.remove(widgetId)       // 删除小部件
turnstile.execute(widgetId)      // 手动触发验证挑战
const token = turnstile.getResponse(widgetId) // 获取当前令牌
React集成(使用@marsidev/react-turnstile):
tsx
import { Turnstile } from '@marsidev/react-turnstile'

export function MyForm() {
  const [token, setToken] = useState<string>()

  return (
    <form>
      <Turnstile
        siteKey={TURNSTILE_SITE_KEY}
        onSuccess={setToken}
        onError={(error) => console.error(error)}
      />
      <button disabled={!token}>提交</button>
    </form>
  )
}

Step 3: Server-Side Validation

步骤3:服务端验证

MANDATORY: Always call Siteverify API to validate tokens.
typescript
interface TurnstileResponse {
  success: boolean
  challenge_ts?: string
  hostname?: string
  error-codes?: string[]
  action?: string
  cdata?: string
}

async function validateTurnstile(
  token: string,
  secretKey: string,
  options?: {
    remoteip?: string
    idempotency_key?: string
    expectedAction?: string
    expectedHostname?: string
  }
): Promise<TurnstileResponse> {
  const formData = new FormData()
  formData.append('secret', secretKey)
  formData.append('response', token)

  if (options?.remoteip) {
    formData.append('remoteip', options.remoteip)
  }

  if (options?.idempotency_key) {
    formData.append('idempotency_key', options.idempotency_key)
  }

  const response = await fetch(
    'https://challenges.cloudflare.com/turnstile/v0/siteverify',
    {
      method: 'POST',
      body: formData,
    }
  )

  const result = await response.json<TurnstileResponse>()

  // Additional validation
  if (result.success) {
    if (options?.expectedAction && result.action !== options.expectedAction) {
      return { success: false, 'error-codes': ['action-mismatch'] }
    }

    if (options?.expectedHostname && result.hostname !== options.expectedHostname) {
      return { success: false, 'error-codes': ['hostname-mismatch'] }
    }
  }

  return result
}

// Usage in Cloudflare Worker
const result = await validateTurnstile(
  token,
  env.TURNSTILE_SECRET_KEY,
  {
    remoteip: request.headers.get('CF-Connecting-IP'),
    expectedHostname: 'example.com',
  }
)

if (!result.success) {
  return new Response('Turnstile validation failed', { status: 401 })
}

强制要求:必须调用Siteverify API验证令牌。
typescript
interface TurnstileResponse {
  success: boolean
  challenge_ts?: string
  hostname?: string
  error-codes?: string[]
  action?: string
  cdata?: string
}

async function validateTurnstile(
  token: string,
  secretKey: string,
  options?: {
    remoteip?: string
    idempotency_key?: string
    expectedAction?: string
    expectedHostname?: string
  }
): Promise<TurnstileResponse> {
  const formData = new FormData()
  formData.append('secret', secretKey)
  formData.append('response', token)

  if (options?.remoteip) {
    formData.append('remoteip', options.remoteip)
  }

  if (options?.idempotency_key) {
    formData.append('idempotency_key', options.idempotency_key)
  }

  const response = await fetch(
    'https://challenges.cloudflare.com/turnstile/v0/siteverify',
    {
      method: 'POST',
      body: formData,
    }
  )

  const result = await response.json<TurnstileResponse>()

  // 额外验证逻辑
  if (result.success) {
    if (options?.expectedAction && result.action !== options.expectedAction) {
      return { success: false, 'error-codes': ['action-mismatch'] }
    }

    if (options?.expectedHostname && result.hostname !== options.expectedHostname) {
      return { success: false, 'error-codes': ['hostname-mismatch'] }
    }
  }

  return result
}

// 在Cloudflare Worker中使用
const result = await validateTurnstile(
  token,
  env.TURNSTILE_SECRET_KEY,
  {
    remoteip: request.headers.get('CF-Connecting-IP'),
    expectedHostname: 'example.com',
  }
)

if (!result.success) {
  return new Response('Turnstile令牌验证失败', { status: 401 })
}

Critical Rules

关键规则

Always Do

必须执行的操作

Call Siteverify API - Server-side validation is mandatory ✅ Use HTTPS - Never validate over HTTP ✅ Protect secret keys - Never expose in frontend code ✅ Handle token expiration - Tokens expire after 5 minutes ✅ Implement error callbacks - Handle failures gracefully ✅ Use dummy keys for testing - Test sitekey:
1x00000000000000000000AA
Set reasonable timeouts - Don't wait indefinitely for validation ✅ Validate action/hostname - Check additional fields when specified ✅ Rotate keys periodically - Use dashboard or API to rotate secrets ✅ Monitor analytics - Track solve rates and failures ✅ Validate token AFTER form submission - Verify tokens after user completes form, not before. Premature validation creates security vulnerabilities where attackers obtain valid tokens then bypass protection
调用Siteverify API——服务端验证是强制要求 ✅ 使用HTTPS——绝不要通过HTTP验证 ✅ 保护密钥——绝不要在前端代码中暴露secret key ✅ 处理令牌过期——令牌有效期为5分钟 ✅ 实现错误回调——优雅处理验证失败情况 ✅ 使用测试密钥——测试用sitekey:
1x00000000000000000000AA
设置合理超时时间——不要无限等待验证结果 ✅ 验证操作/主机名——指定时检查额外字段 ✅ 定期轮换密钥——通过控制台或API轮换密钥 ✅ 监控分析数据——跟踪验证通过率和失败情况 ✅ 表单提交后验证令牌——用户完成表单后再验证令牌,不要提前验证。提前验证会产生安全漏洞,攻击者可获取有效令牌后绕过防护

Never Do

禁止执行的操作

Skip server validation - Client-side only = security vulnerability ❌ Proxy api.js script - Must load from Cloudflare CDN ❌ Reuse tokens - Each token is single-use only ❌ Use GET requests - Siteverify only accepts POST ❌ Expose secret key - Keep secrets in backend environment only ❌ Trust client-side validation - Tokens can be forged ❌ Cache api.js - Future updates will break your integration ❌ Use production keys in tests - Use dummy keys instead ❌ Ignore error callbacks - Always handle failures

跳过服务端验证——仅客户端验证等于存在安全漏洞 ❌ 代理api.js脚本——必须从Cloudflare CDN加载 ❌ 重复使用令牌——每个令牌仅可使用一次 ❌ 使用GET请求——Siteverify仅接受POST请求 ❌ 暴露secret key——仅在后端环境中存储密钥 ❌ 信任客户端验证——令牌可能被伪造 ❌ 缓存api.js——未来更新会导致集成失效 ❌ 在测试中使用生产密钥——使用测试密钥替代 ❌ 忽略错误回调——必须处理验证失败情况

Known Issues Prevention

常见问题预防

This skill prevents 12 documented issues:
本技能可预防12类已记录的问题:

Issue #1: Missing Server-Side Validation

问题1:缺少服务端验证

Error: Zero token validation in Turnstile Analytics dashboard Source: https://developers.cloudflare.com/turnstile/get-started/ Why It Happens: Developers only implement client-side widget, skip Siteverify call Prevention: All templates include mandatory server-side validation with Siteverify API
错误表现:Turnstile分析仪表盘中显示令牌验证次数为0 来源https://developers.cloudflare.com/turnstile/get-started/ 原因:开发者仅实现了客户端小部件,未调用Siteverify接口 预防方案:所有模板均包含强制的Siteverify服务端验证逻辑

Issue #2: Token Expiration (5 Minutes)

问题2:令牌过期(5分钟)

Error:
success: false
for valid tokens submitted after delay Source: https://developers.cloudflare.com/turnstile/get-started/server-side-validation Why It Happens: Tokens expire 300 seconds after generation Prevention: Templates document TTL and implement token refresh on expiration
错误表现:延迟提交的有效令牌返回
success: false
来源https://developers.cloudflare.com/turnstile/get-started/server-side-validation 原因:令牌生成300秒后过期 预防方案:模板中记录了令牌生命周期,并实现了过期自动刷新逻辑

Issue #3: Secret Key Exposed in Frontend

问题3:前端暴露secret key

Error: Security bypass - attackers can validate their own tokens Source: https://developers.cloudflare.com/turnstile/get-started/server-side-validation Why It Happens: Secret key hardcoded in JavaScript or visible in source Prevention: All templates show backend-only validation with environment variables
错误表现:安全绕过——攻击者可自行验证令牌 来源https://developers.cloudflare.com/turnstile/get-started/server-side-validation 原因:secret key硬编码在JavaScript中或在源码中可见 预防方案:所有模板均展示了仅后端验证的实现方式,使用环境变量存储密钥

Issue #4: GET Request to Siteverify

问题4:向Siteverify发送GET请求

Error: API returns 405 Method Not Allowed Source: https://developers.cloudflare.com/turnstile/migration/recaptcha Why It Happens: reCAPTCHA supports GET, Turnstile requires POST Prevention: Templates use POST with FormData or JSON body
错误表现:API返回405 Method Not Allowed 来源https://developers.cloudflare.com/turnstile/migration/recaptcha 原因:reCAPTCHA支持GET请求,但Turnstile仅接受POST 预防方案:模板使用POST请求并携带FormData或JSON请求体

Issue #5: Content Security Policy Blocking

问题5:内容安全策略(CSP)拦截

Error: Error 200500 - "Loading error: The iframe could not be loaded" Source: https://developers.cloudflare.com/turnstile/troubleshooting/client-side-errors/error-codes Why It Happens: CSP blocks challenges.cloudflare.com iframe Prevention: Skill includes CSP configuration reference and check-csp.sh script
错误表现:错误码200500——「加载错误:无法加载iframe」 来源https://developers.cloudflare.com/turnstile/troubleshooting/client-side-errors/error-codes 原因:CSP策略阻止了challenges.cloudflare.com的iframe 预防方案:本技能包含CSP配置参考和check-csp.sh脚本

Issue #6: Widget Crash (Error 300030)

问题6:小部件崩溃(错误码300030)

Error: Generic client execution error for legitimate users Source: https://community.cloudflare.com/t/turnstile-is-frequently-generating-300x-errors/700903 Why It Happens: Unknown - appears to be Cloudflare-side issue (2025) Prevention: Templates implement error callbacks, retry logic, and fallback handling
错误表现:合法用户遇到通用客户端执行错误 来源https://community.cloudflare.com/t/turnstile-is-frequently-generating-300x-errors/700903 原因:未知——2025年发现为Cloudflare侧问题 预防方案:模板实现了错误回调、重试逻辑和降级处理

Issue #7: Configuration Error (Error 600010)

问题7:配置错误(错误码600010)

Error: Widget fails with "configuration error" Source: https://community.cloudflare.com/t/repeated-cloudflare-turnstile-error-600010/644578 Why It Happens: Missing or deleted hostname in widget configuration Prevention: Templates document hostname allowlist requirement and verification steps
错误表现:小部件因「配置错误」失败 来源https://community.cloudflare.com/t/repeated-cloudflare-turnstile-error-600010/644578 原因:小部件配置中缺少或删除了主机名 预防方案:模板记录了主机名白名单要求和验证步骤

Issue #8: Safari 18 / macOS 15 "Hide IP" Issue

问题8:Safari 18 / macOS 15「隐藏IP」问题

Error: Error 300010 when Safari's "Hide IP address" is enabled Source: https://community.cloudflare.com/t/turnstile-is-frequently-generating-300x-errors/700903 Why It Happens: Privacy settings interfere with challenge signals Prevention: Error handling reference documents Safari workaround (disable Hide IP)
错误表现:启用Safari「隐藏IP地址」时出现错误码300010 来源https://community.cloudflare.com/t/turnstile-is-frequently-generating-300x-errors/700903 原因:隐私设置干扰了验证挑战信号 预防方案:错误处理参考文档记录了Safari的解决方法(关闭隐藏IP)

Issue #9: Brave Browser Confetti Animation Failure

问题9:Brave浏览器庆祝动画失败

Error: Verification fails during success animation Source: https://github.com/brave/brave-browser/issues/45608 (April 2025) Why It Happens: Brave shields block animation scripts Prevention: Templates handle success before animation completes
错误表现:验证成功动画期间验证失败 来源https://github.com/brave/brave-browser/issues/45608(2025年4月) 原因:Brave护盾阻止了动画脚本 预防方案:模板在动画完成前处理验证成功逻辑

Issue #10: Next.js + Jest Incompatibility

问题10:Next.js + Jest兼容性问题

Error: @marsidev/react-turnstile breaks Jest tests Source: https://github.com/marsidev/react-turnstile/issues/112 (Oct 2025) Why It Happens: Module resolution issues with Jest Prevention: Testing guide includes Jest mocking patterns and dummy sitekey usage
错误表现:@marsidev/react-turnstile导致Jest测试失败 来源https://github.com/marsidev/react-turnstile/issues/112(2025年10月) 原因:Jest的模块解析问题 预防方案:测试指南包含Jest模拟模式和测试密钥使用方法

Issue #11: localhost Not in Allowlist

问题11:localhost未在白名单中

Error: Error 110200 - "Unknown domain: Domain not allowed" Source: https://developers.cloudflare.com/turnstile/troubleshooting/client-side-errors/error-codes Why It Happens: Production widget used in development without localhost in allowlist Prevention: Templates use dummy test keys for dev, document localhost allowlist requirement
错误表现:错误码110200——「未知域名:域名不被允许」 来源https://developers.cloudflare.com/turnstile/troubleshooting/client-side-errors/error-codes 原因:开发环境使用生产小部件,但未将localhost加入白名单 预防方案:模板在开发环境使用测试密钥,并记录了localhost白名单要求

Issue #12: Token Reuse Attempt

问题12:尝试重复使用令牌

Error:
success: false
with "token already spent" error Source: https://developers.cloudflare.com/turnstile/troubleshooting/testing Why It Happens: Each token can only be validated once Prevention: Templates document single-use constraint and token refresh patterns

错误表现:返回
success: false
并提示「token already spent」错误 来源https://developers.cloudflare.com/turnstile/troubleshooting/testing 原因:每个令牌仅可验证一次 预防方案:模板记录了令牌单次使用限制和刷新模式

Configuration

配置说明

Wrangler (Workers): Load
templates/wrangler-turnstile-config.jsonc
for complete configuration. Key settings:
vars
for public sitekey (safe to commit),
secrets
for private secret key (use
wrangler secret put TURNSTILE_SECRET_KEY
).
CSP Directives (if using Content Security Policy):
html
<meta http-equiv="Content-Security-Policy" content="
  script-src 'self' https://challenges.cloudflare.com;
  frame-src 'self' https://challenges.cloudflare.com;
  connect-src 'self' https://challenges.cloudflare.com;">

Wrangler(Workers):加载
templates/wrangler-turnstile-config.jsonc
获取完整配置。核心设置:
vars
存储公开的sitekey(可安全提交到代码仓库),
secrets
存储私有secret key(使用
wrangler secret put TURNSTILE_SECRET_KEY
设置)。
CSP指令(若使用内容安全策略):
html
<meta http-equiv="Content-Security-Policy" content="
  script-src 'self' https://challenges.cloudflare.com;
  frame-src 'self' https://challenges.cloudflare.com;
  connect-src 'self' https://challenges.cloudflare.com;">

Common Patterns

通用模式

Hono + Cloudflare Workers: Server-side validation in Workers API routes with Hono framework. Load
references/common-patterns.md
#pattern-1 when building Workers endpoints requiring bot protection.
React + Next.js: Client-side forms with @marsidev/react-turnstile integration. Load
references/common-patterns.md
#pattern-2 when integrating Turnstile with React/Next.js applications.
E2E Testing: Automated testing with dummy keys (Playwright, Cypress, Jest). Load
references/common-patterns.md
#pattern-3 when writing E2E tests or setting up CI/CD pipelines.
Widget Lifecycle: Programmatic widget control for SPAs (render, reset, remove, getToken). Load
references/common-patterns.md
#pattern-4 when building SPAs requiring explicit widget management.

Hono + Cloudflare Workers:在Workers API路由中使用Hono框架实现服务端验证。构建需要机器人防护的Workers端点时,加载
references/common-patterns.md
的#pattern-1。
React + Next.js:使用@marsidev/react-turnstile集成客户端表单。在React/Next.js应用中集成Turnstile时,加载
references/common-patterns.md
的#pattern-2。
端到端测试:使用测试密钥进行自动化测试(Playwright、Cypress、Jest)。编写端到端测试或配置CI/CD流水线时,加载
references/common-patterns.md
的#pattern-3。
小部件生命周期:程序化控制单页应用中的小部件(渲染、重置、删除、获取令牌)。构建需要显式管理小部件的单页应用时,加载
references/common-patterns.md
的#pattern-4。

When to Load References

何时加载参考文档

references/widget-configs.md
: Configuring widget appearance, themes, execution modes, size, language, or retry behavior.
references/error-codes.md
: Debugging error codes 100*, 200*, 300*, 400*, 600* or troubleshooting client-side failures (CSP, domain errors, widget crashes).
references/testing-guide.md
: Setting up E2E tests (Playwright, Cypress), local development with dummy keys, or CI/CD pipeline integration.
references/react-integration.md
: Integrating with React, Next.js, or troubleshooting @marsidev/react-turnstile issues (Jest mocking, SSR, hooks).
references/common-patterns.md
: Building Hono Workers routes, React forms, E2E tests, or widget lifecycle management (explicit rendering).
references/advanced-topics.md
: Implementing pre-clearance for SPAs, custom actions/cdata, retry strategies, or multi-widget pages.
references/setup-checklist.md
: Preparing for deployment, verifying complete setup, or ensuring production readiness (14-point checklist).
references/migration-guide.md
: Migrating from reCAPTCHA (v2) or hCaptcha to Turnstile, including compat mode, API differences, and POST-only Siteverify requirement.
references/browser-support.md
: Browser compatibility matrix, Safari 18 "Hide IP" workaround, Brave shields issues, and browser-specific fallbacks.
references/mobile-implementation.md
: WebView integration for iOS, Android, React Native, and Flutter, including User Agent consistency and storage persistence requirements.
templates/
: wrangler-turnstile-config.jsonc (Workers env), turnstile-widget-implicit.html (static forms), turnstile-widget-explicit.ts (SPA rendering), turnstile-server-validation.ts (Siteverify API), turnstile-react-component.tsx (React integration), turnstile-hono-route.ts (Hono validation), turnstile-test-config.ts (testing setup)
scripts/check-csp.sh
: Verify Content Security Policy allows Turnstile (usage:
./scripts/check-csp.sh https://example.com
)

references/widget-configs.md
:配置小部件外观、主题、执行模式、尺寸、语言或重试行为时。
references/error-codes.md
:调试错误码100*、200*、300*、400*、600*或排查客户端失败问题(CSP、域名错误、小部件崩溃)时。
references/testing-guide.md
:设置端到端测试(Playwright、Cypress)、使用测试密钥进行本地开发或集成CI/CD流水线时。
references/react-integration.md
:集成React、Next.js或排查@marsidev/react-turnstile问题(Jest模拟、SSR、钩子)时。
references/common-patterns.md
:构建Hono Workers路由、React表单、端到端测试或小部件生命周期管理(显式渲染)时。
references/advanced-topics.md
:为单页应用实现预验证、自定义操作/cdata、重试策略或多小部件页面时。
references/setup-checklist.md
:准备部署、验证完整设置或确保生产就绪状态时(14项检查清单)。
references/migration-guide.md
:从reCAPTCHA(v2)或hCaptcha迁移到Turnstile时,包括兼容模式、API差异和仅POST的Siteverify要求。
references/browser-support.md
:浏览器兼容性矩阵、Safari 18「隐藏IP」解决方法、Brave护盾问题和浏览器特定降级方案时。
references/mobile-implementation.md
:iOS、Android、React Native和Flutter的WebView集成时,包括用户代理一致性和存储持久化要求。
templates/
wrangler-turnstile-config.jsonc(Workers环境配置)、turnstile-widget-implicit.html(静态表单)、turnstile-widget-explicit.ts(单页应用渲染)、turnstile-server-validation.ts(Siteverify API)、turnstile-react-component.tsx(React集成)、turnstile-hono-route.ts(Hono验证)、turnstile-test-config.ts(测试设置)
scripts/check-csp.sh
:验证内容安全策略是否允许Turnstile(使用方式:
./scripts/check-csp.sh https://example.com

Dependencies

依赖项

Required: None (Turnstile loads from Cloudflare CDN)
Optional: @marsidev/react-turnstile@1.3.1 (React), turnstile-types@1.2.3 (TypeScript), vue-turnstile (Vue 3), ngx-turnstile (Angular), svelte-turnstile (Svelte), @nuxtjs/turnstile (Nuxt)

必填:无(Turnstile从Cloudflare CDN加载)
可选:@marsidev/react-turnstile@1.3.1(React)、turnstile-types@1.2.3(TypeScript)、vue-turnstile(Vue 3)、ngx-turnstile(Angular)、svelte-turnstile(Svelte)、@nuxtjs/turnstile(Nuxt)

Official Documentation

官方文档

Troubleshooting

故障排查

Problem: Error 110200 - "Unknown domain"

问题:错误码110200——「未知域名」

Solution: Add your domain (including localhost for dev) to widget's allowed domains in Cloudflare Dashboard. For local dev, use dummy test sitekey
1x00000000000000000000AA
instead.
解决方案:在Cloudflare控制台中将你的域名(包括开发环境的localhost)添加到小部件的允许域名列表中。本地开发可使用测试sitekey
1x00000000000000000000AA
替代。

Problem: Error 300030 - Widget crashes for legitimate users

问题:错误码300030——合法用户遇到小部件崩溃

Solution: Implement error callback with retry logic. This is a known Cloudflare-side issue (2025). Fallback to alternative verification if retries fail.
解决方案:实现带重试逻辑的错误回调。这是2025年已知的Cloudflare侧问题。若重试失败,降级到替代验证方案。

Problem: Tokens always return
success: false

问题:令牌始终返回
success: false

Solution:
  1. Check token hasn't expired (5 min TTL)
  2. Verify secret key is correct
  3. Ensure token hasn't been validated before (single-use)
  4. Check hostname matches widget configuration
解决方案
  1. 检查令牌是否已过期(5分钟生命周期)
  2. 验证secret key是否正确
  3. 确保令牌未被验证过(单次使用)
  4. 检查主机名是否与小部件配置匹配

Problem: CSP blocking iframe (Error 200500)

问题:CSP拦截iframe(错误码200500)

Solution: Add CSP directives:
html
<meta http-equiv="Content-Security-Policy" content="
  frame-src https://challenges.cloudflare.com;
  script-src https://challenges.cloudflare.com;
">
解决方案:添加以下CSP指令:
html
<meta http-equiv="Content-Security-Policy" content="
  frame-src https://challenges.cloudflare.com;
  script-src https://challenges.cloudflare.com;
">

Problem: Safari 18 "Hide IP" causing Error 300010

问题:Safari 18「隐藏IP」导致错误码300010

Solution: Document in error message that users should disable Safari's "Hide IP address" setting (Safari → Settings → Privacy → Hide IP address → Off)
解决方案:在错误提示中告知用户需关闭Safari的「隐藏IP地址」设置(Safari → 设置 → 隐私 → 隐藏IP地址 → 关闭)

Problem: Next.js + Jest tests failing with @marsidev/react-turnstile

问题:Next.js + Jest测试因@marsidev/react-turnstile失败

Solution: Mock the Turnstile component in Jest setup:
typescript
// jest.setup.ts
jest.mock('@marsidev/react-turnstile', () => ({
  Turnstile: () => <div data-testid="turnstile-mock" />,
}))

Token Efficiency: ~65-70% savings vs manual integration
Errors Prevented: 12 documented security/validation issues with complete solutions
Deployment Checklist: Load
references/setup-checklist.md
for complete 14-point pre-deployment verification
解决方案:在Jest配置中模拟Turnstile组件:
typescript
// jest.setup.ts
jest.mock('@marsidev/react-turnstile', () => ({
  Turnstile: () => <div data-testid="turnstile-mock" />,
}))

令牌效率:相比手动集成节省约65-70%的工作量
预防的错误:12类已记录的安全/验证问题,并提供完整解决方案
部署检查清单:加载
references/setup-checklist.md
获取完整的14项预部署验证清单