Back to Security & Vulnerability Analysis

cloudflare-turnstile

cloudflareturnstilesecuritybot protectioncaptchafrontendbackendweb development
⭐ 860πŸ“„ MITπŸ•’ 2026-06-11Source β†—

Install this skill

npx skills add jezweb/claude-skills

Works across Claude Code, Cursor, Codex, Copilot & Antigravity

Cloudflare Turnstile is a non-intrusive challenge-response security mechanism that replaces traditional CAPTCHAs by verifying human behavior without requiring user interaction. It functions by embedding a lightweight script that validates requests against the Cloudflare edge network. This skill facilitates the integration of Turnstile widgets into web applications, ensuring all tokens are validated server-side to prevent bot traffic, spam, or malicious automated form submissions. It specifically handles the lifecycle of tokensβ€”from rendering the frontend widget to executing secure POST requests against the /siteverify endpoint. By managing both implicit and explicit rendering modes, this skill enables developers to secure sensitive entry points like login screens and contact forms while maintaining strict compliance with Cloudflare's mandatory server-side verification requirements.

When to Use This Skill

  • β€’Protecting user registration and authentication forms from credential stuffing
  • β€’Securing API endpoints against automated scrapers and brute-force attacks
  • β€’Reducing spam submissions on contact and newsletter sign-up forms
  • β€’Validating human traffic on high-risk checkout pages

How to Invoke This Skill

Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:

  • β€œset up Cloudflare Turnstile on my contact form
  • β€œhow do I validate a Turnstile token server side
  • β€œadd anti-bot protection to my React application
  • β€œhow to implement Cloudflare Turnstile with siteverify
  • β€œfix Cloudflare Turnstile integration issues

Pro Tips

  • πŸ’‘Always pass the `cf-turnstile-response` token from the frontend to your backend and validate it with Cloudflare's API. Never trust frontend validation alone.
  • πŸ’‘Leverage the `@marsidev/react-turnstile` library for React applications to simplify integration and manage component lifecycle effectively, especially concerning callback changes.
  • πŸ’‘Monitor Cloudflare Turnstile analytics regularly to gain insights into traffic patterns, bot behavior, and the effectiveness of your challenge configurations.

What this skill does

  • β€’Automatic verification of human behavior through browser telemetry
  • β€’Server-side token validation via the Siteverify API
  • β€’Support for both implicit and explicit widget rendering cycles
  • β€’Integration for React applications using the @marsidev library
  • β€’Support for dummy sitekeys for development and testing environments

When not to use it

  • βœ•Low-traffic static sites where bot activity is not a threat
  • βœ•Applications requiring complete offline or private intranet functionality without external CDN access

Example workflow

  1. Register a domain on the Cloudflare dashboard to receive a sitekey and secret key
  2. Embed the Turnstile JavaScript client in your HTML page to generate the widget
  3. Capture the cf-turnstile-response token upon user form submission
  4. Send the token and the client IP address to the /siteverify endpoint via a server-side POST request
  5. Verify the success boolean in the JSON response before proceeding with form logic

Prerequisites

  • –Active Cloudflare account
  • –Registered domain or hostname in the Turnflare dashboard
  • –Backend environment capable of making HTTPS POST requests

Pitfalls & limitations

  • !Tokens are strictly single-use and expire within five minutes
  • !Failure to include the remoteip in the verification request can lead to failed validations
  • !Relying on client-side success callbacks without server-side verification is a critical security vulnerability

FAQ

Do I need to store the Turnstile secret key in my frontend code?
No. You must keep the secret key strictly on the server-side to prevent unauthorized validation requests.
Can I cache the Turnstile script locally?
No. You must load api.js directly from the Cloudflare CDN to ensure you receive the latest updates and security patches.
How do I test Turnstile if I don't want to use real keys?
Use the Cloudflare dummy sitekey (1x00000000000000000000AA) to trigger test challenges without affecting production analytics.

How it compares

Unlike manual CAPTCHA implementation or generic regex filters, this skill enforces the mandatory siteverify handshake, ensuring that tokens cannot be forged or reused by bots.

Source & trust

⭐ 860 starsπŸ“„ MITπŸ•’ Updated 2026-06-11
πŸ“„ Full skill instructions β€” original source: jezweb/claude-skills
# Cloudflare Turnstile

**Status**: Production Ready βœ…
**Last Updated**: 2026-01-21
**Dependencies**: None (optional: @marsidev/react-turnstile for React)
**Latest Versions**: @marsidev/[email protected], [email protected]

**Recent Updates (2025)**:
- **December 2025**: @marsidev/react-turnstile v1.4.1 fixes race condition in script loading
- **August 2025**: v1.3.0 adds rerenderOnCallbackChange prop for React closure issues
- **March 2025**: Upgraded Turnstile Analytics with TopN statistics (7 dimensions: hostnames, browsers, countries, user agents, ASNs, OS, source IPs), anomaly detection, enhanced bot behavior monitoring
- **January 2025**: Brief remoteip validation enforcement (resolved, but highlights importance of correct IP passing)
- **2025**: WCAG 2.1 AA compliance, Free plan (20 widgets, 7-day analytics), Enterprise features (unlimited widgets, ephemeral IDs, any hostname support, 30-day analytics, offlabel branding)

---

## Quick Start (5 Minutes)

# 1. Create widget: https://dash.cloudflare.com/?to=/:account/turnstile
# Copy sitekey (public) and secret key (private)

# 2. Add widget to frontend
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<form>
<div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
<button type="submit">Submit</button>
</form>

# 3. Validate token server-side (Cloudflare Workers)
const formData = await request.formData()
const token = formData.get('cf-turnstile-response')

const verifyFormData = new FormData()
verifyFormData.append('secret', env.TURNSTILE_SECRET_KEY)
verifyFormData.append('response', token)
verifyFormData.append('remoteip', request.headers.get('CF-Connecting-IP')) // REQUIRED - see Critical Rules

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', { status: 401 })


**CRITICAL:**
- Token expires in 5 minutes, single-use only
- ALWAYS validate server-side (Siteverify API required)
- Never proxy/cache api.js (must load from Cloudflare CDN)
- Use different widgets for dev/staging/production

## Rendering Modes

**Implicit** (auto-render on page load):
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY" data-callback="onSuccess"></div>


**Explicit** (programmatic control for SPAs):
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"></script>
const widgetId = turnstile.render('#container', { sitekey: 'YOUR_SITE_KEY' })
turnstile.reset(widgetId) // Reset widget
turnstile.getResponse(widgetId) // Get token


**React** (using @marsidev/react-turnstile):
import { Turnstile } from '@marsidev/react-turnstile'
<Turnstile siteKey={TURNSTILE_SITE_KEY} onSuccess={setToken} />


---

## 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
βœ… **Always pass client IP to Siteverify** - Use CF-Connecting-IP header (Workers) or X-Forwarded-For (Node.js). Cloudflare briefly enforced strict remoteip validation in Jan 2025, causing widespread failures for sites not passing correct IP

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

---

## Known Issues Prevention

This skill prevents **15** documented issues:

### Issue #1: Missing Server-Side Validation
**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

### Issue #2: Token Expiration (5 Minutes)
**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

### Issue #3: Secret Key Exposed in Frontend
**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

### Issue #4: GET Request to Siteverify
**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

### Issue #5: Content Security Policy Blocking
**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

### Issue #6: Widget Crash (Error 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

### Issue #7: Configuration Error (Error 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

### Issue #8: Safari 18 / macOS 15 "Hide IP" Issue
**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)

### Issue #9: Brave Browser Confetti Animation Failure
**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

### Issue #10: Next.js + Jest Incompatibility
**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

### Issue #11: localhost Not in Allowlist
**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

### Issue #12: Token Reuse Attempt
**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. Turnstile tokens are single-use - after validation (success OR failure), the token is consumed and cannot be revalidated. Developers must explicitly call turnstile.reset() to generate a new token for subsequent submissions.
**Prevention**: Templates document single-use constraint and token refresh patterns

// CRITICAL: Reset widget after validation to get new token
const turnstileRef = useRef(null)

async function handleSubmit(e) {
e.preventDefault()
const token = formData.get('cf-turnstile-response')

const result = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify({ token })
})

// Reset widget regardless of success/failure
// Token is consumed either way
if (turnstileRef.current) {
turnstile.reset(turnstileRef.current)
}
}

<Turnstile
ref={turnstileRef}
siteKey={TURNSTILE_SITE_KEY}
onSuccess={setToken}
/>


### Issue #13: Error 106010 - Chrome/Edge First-Load Failure
**Error**: 106010 - "Generic parameter error" on first widget load in Chrome/Edge browsers
**Source**: [Cloudflare Error Codes](https://developers.cloudflare.com/turnstile/troubleshooting/client-side-errors/error-codes/), [Community Report](https://community.cloudflare.com/t/turnstile-inconsistent-errors/856678)
**Why It Happens**: Unknown browser-specific issue affecting Chrome and Edge on first page load. Console shows 400 error to https://challenges.cloudflare.com/cdn-cgi/challenge-platform. Firefox is not affected. Subsequent page reloads work correctly.
**Prevention**: Implement error callback with auto-retry logic

turnstile.render('#container', {
sitekey: SITE_KEY,
retry: 'auto',
'retry-interval': 8000,
'error-callback': (errorCode) => {
if (errorCode === '106010') {
console.warn('Chrome/Edge first-load issue (106010), auto-retrying...')
// Auto-retry will handle it
}
}
})


**Workaround**: Widget works correctly after page reload. Auto-retry setting resolves in most cases. Test in Incognito mode to rule out browser extensions. Review CSP rules to ensure Cloudflare Turnstile endpoints are allowed.

### Issue #14: Multiple Widgets Visual Status Stuck (Community-sourced)
**Error**: Widget displays "Pending..." status even after successful token generation
**Source**: [GitHub Issue #119](https://github.com/marsidev/react-turnstile/issues/119)
**Why It Happens**: CSS repaint issue when rendering multiple <Turnstile/> components on a single page. Only reproducible on full HD desktop screens. Token IS successfully generated (validation works), but visual status doesn't update. Hovering over widget triggers repaint and shows correct status.
**Prevention**: Force CSS repaint in success callback

<Turnstile
siteKey={KEY}
onSuccess={(token) => {
setToken(token)
// Force repaint by toggling display
const widget = document.querySelector('.cf-turnstile')
if (widget) {
widget.style.display = 'none'
setTimeout(() => widget.style.display = 'block', 0)
}
}}
/>


**Note**: This is a visual-only issue, not a validation failure. The token is correctly generated and functional.

### Issue #15: Jest Compatibility with @marsidev/react-turnstile (Updated Dec 2025)
**Error**: Jest encountered an unexpected token when importing @marsidev/react-turnstile
**Source**: [GitHub Issue #114](https://github.com/marsidev/react-turnstile/issues/114), [GitHub Issue #112](https://github.com/marsidev/react-turnstile/issues/112)
**Why It Happens**: ESM module resolution issues with Jest 30.2.0 (latest as of Dec 2025). Issue #112 closed as "not planned" by maintainer. Jest users are stuck; Vitest migration works.
**Prevention**: Mock the Turnstile component in Jest setup OR migrate to Vitest

// Option 1: Jest mocking (jest.setup.ts)
jest.mock('@marsidev/react-turnstile', () => ({
Turnstile: () => <div data-testid="turnstile-mock" />,
}))

// Option 2: transformIgnorePatterns in jest.config.js
module.exports = {
transformIgnorePatterns: [
'node_modules/(?!(@marsidev/react-turnstile)/)'
]
}

// Option 3 (Recommended): Migrate to Vitest
// Vitest handles ESM modules correctly without mocking


**Status**: Maintainer closed issue as "not planned". Recommend migrating to Vitest for new projects.

## Configuration

**wrangler.jsonc:**
{
"vars": { "TURNSTILE_SITE_KEY": "1x00000000000000000000AA" },
"secrets": ["TURNSTILE_SECRET_KEY"] // Run: wrangler secret put TURNSTILE_SECRET_KEY
}


**Required CSP:**
<meta http-equiv="Content-Security-Policy" content="
script-src 'self' https://challenges.cloudflare.com;
frame-src 'self' https://challenges.cloudflare.com;
">


---

## Common Patterns

### Pattern 1: Hono + Cloudflare Workers

import { Hono } from 'hono'

type Bindings = {
TURNSTILE_SECRET_KEY: string
TURNSTILE_SITE_KEY: string
}

const app = new Hono<{ Bindings: Bindings }>()

app.post('/api/login', async (c) => {
const body = await c.req.formData()
const token = body.get('cf-turnstile-response')

if (!token) {
return c.text('Missing Turnstile token', 400)
}

// Validate token
const verifyFormData = new FormData()
verifyFormData.append('secret', c.env.TURNSTILE_SECRET_KEY)
verifyFormData.append('response', token.toString())
verifyFormData.append('remoteip', c.req.header('CF-Connecting-IP') || '') // CRITICAL - always pass client IP

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

const outcome = await verifyResult.json<{ success: boolean }>()

if (!outcome.success) {
return c.text('Invalid Turnstile token', 401)
}

// Process login
return c.json({ message: 'Login successful' })
})

export default app


**When to use**: API routes in Cloudflare Workers with Hono framework

### Pattern 2: React + Next.js App Router

'use client'

import { Turnstile } from '@marsidev/react-turnstile'
import { useState } from 'react'

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

async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()

if (!token) {
setError('Please complete the challenge')
return
}

const formData = new FormData(e.currentTarget)
formData.append('cf-turnstile-response', token)

const response = await fetch('/api/contact', {
method: 'POST',
body: formData,
})

if (!response.ok) {
setError('Submission failed')
return
}

// Success
}

return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<textarea name="message" required />

<Turnstile
siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY!}
onSuccess={setToken}
onError={() => setError('Challenge failed')}
onExpire={() => setToken(undefined)}
/>

{error && <div className="error">{error}</div>}

<button type="submit" disabled={!token}>
Submit
</button>
</form>
)
}


**When to use**: Client-side forms in Next.js with React hooks

---

## Testing Keys

**Dummy Sitekeys (client):**
- Always pass: 1x00000000000000000000AA
- Always block: 2x00000000000000000000AB
- Force interactive: 3x00000000000000000000FF

**Dummy Secret Keys (server):**
- Always pass: 1x0000000000000000000000000000000AA
- Always fail: 2x0000000000000000000000000000000AA
- Token already spent: 3x0000000000000000000000000000000AA

---

## Bundled Resources

**Scripts:** check-csp.sh - Verify CSP allows Turnstile

**References:**
- widget-configs.md - All configuration options
- error-codes.md - Error code troubleshooting (100*/200*/300*/400*/600*)
- testing-guide.md - Testing strategies, dummy keys
- react-integration.md - React/Next.js patterns

**Templates:** Complete examples for Hono, React, implicit/explicit rendering, validation

---

## Advanced Features

**Pre-Clearance (SPAs):** Issue cookie that persists across page navigations
turnstile.render('#container', {
sitekey: SITE_KEY,
callback: async (token) => {
await fetch('/api/pre-clearance', { method: 'POST', body: JSON.stringify({ token }) })
}
})


**Custom Actions & Data:** Track challenge types, pass custom data (max 255 chars)
turnstile.render('#container', {
action: 'login', // Track in analytics
cdata: JSON.stringify({ userId: '123' }), // Custom payload
})


**Error Handling:** Use retry: 'auto' and error-callback for resilience
turnstile.render('#container', {
retry: 'auto',
'retry-interval': 8000, // ms between retries
'error-callback': (error) => { /* handle or show fallback */ }
})


---

## Dependencies

**Required:** None (loads from CDN)
**React:** @marsidev/[email protected] (Cloudflare-recommended), [email protected]
**Other:** vue-turnstile, ngx-turnstile, svelte-turnstile, @nuxtjs/turnstile

---

## Official Documentation

- https://developers.cloudflare.com/turnstile/
- Use mcp__cloudflare-docs__search_cloudflare_documentation tool

---

## Troubleshooting

### Problem: Error 110200 - "Unknown domain"
**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.

### Problem: Error 300030 - Widget crashes for legitimate users
**Solution**: Implement error callback with retry logic. This is a known Cloudflare-side issue (2025). Fallback to alternative verification if retries fail.

### Problem: Tokens always return 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

### Problem: CSP blocking iframe (Error 200500)
**Solution**: Add CSP directives:
<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
**Solution**: Document in error message that users should disable Safari's "Hide IP address" setting (Safari β†’ Settings β†’ Privacy β†’ Hide IP address β†’ Off)

### Problem: Next.js + Jest tests failing with @marsidev/react-turnstile
**Solution**: Mock the Turnstile component in Jest setup (or migrate to Vitest, which handles ESM modules correctly):
// Option 1: Jest mocking (jest.setup.ts)
jest.mock('@marsidev/react-turnstile', () => ({
Turnstile: () => <div data-testid="turnstile-mock" />,
}))

// Option 2: Migrate to Vitest (recommended for new projects)
// Vitest handles ESM modules without mocking required


**Note**: This issue persists in Jest 30.2.0 (Dec 2025). Maintainer closed as "not planned". See Issue #15 for full details.

---

**Errors Prevented**: 15 documented issues (Safari 18 Hide IP, Brave confetti, Next.js Jest, CSP blocking, token reuse, expiration, hostname allowlist, widget crash 300030, config error 600010, missing validation, GET request, secret exposure, Chrome/Edge 106010, multiple widgets rendering, token regeneration pattern)


---

---
paths: "**/*.ts", "**/*.tsx", "**/*turnstile*.ts"
---

# Cloudflare Turnstile Corrections

## Server-Side Validation MANDATORY

/* ❌ SECURITY VULNERABILITY - client-only validation */
// Frontend gets token, sends to your API
// API trusts token without verification

/* βœ… ALWAYS call Siteverify API */
async function verifyTurnstile(token: string, env: Env): Promise<boolean> {
const response = await fetch(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
secret: env.TURNSTILE_SECRET_KEY,
response: token,
}),
}
)
const result = await response.json()
return result.success === true
}


## Siteverify: POST Only, Not GET

/* ❌ GET not supported (unlike reCAPTCHA) */
fetch(https://challenges.cloudflare.com/turnstile/v0/siteverify?secret=...&response=...)

/* βœ… Must use POST */
fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
body: new URLSearchParams({ secret, response: token }),
})


## Token Expires in 5 Minutes

/* ⚠️ Tokens have 300 second TTL */
// User completes challenge
// Token valid for 5 minutes only
// After that, siteverify returns success: false

/* βœ… Verify immediately after form submission */


## Never Expose Secret Key

/* ❌ Secret in frontend */
const TURNSTILE_SECRET = 'xxx' // In client code!

/* βœ… Secret only in backend environment */
// .dev.vars or wrangler secret
// Access via env.TURNSTILE_SECRET_KEY


## Test Keys for Development

/* βœ… Dummy keys for testing */
// Site key (always passes): 1x00000000000000000000AA
// Site key (always fails): 2x00000000000000000000AB
// Site key (forces challenge): 3x00000000000000000000FF

// Secret key (always passes): 1x0000000000000000000000000000000AA
// Secret key (always fails): 2x0000000000000000000000000000000AB


## CSP Configuration Required

/* βœ… Allow Turnstile iframe in CSP */
// frame-src: https://challenges.cloudflare.com
// script-src: https://challenges.cloudflare.com


## Quick Fixes

| If Claude suggests... | Use instead... |
|----------------------|----------------|
| Client-only validation | ALWAYS call Siteverify API server-side |
| GET request to Siteverify | POST with URLSearchParams body |
| Secret key in frontend | Keep in backend env only |
| Production keys in dev | Use dummy test keys |
| Missing CSP | Allow challenges.cloudflare.com |

How to Use This Skill Unit

Option A: Project-Specific (Recommended)

  1. Click "Download" above
  2. In your project, create the directory: .agent/skills/cloudflare-turnstile/
  3. Save the file as SKILL.md
  4. The agent will automatically discover the skill based on its description.

Option B: Global Installation (All Agents)

Save the file to these locations to make it available across all projects:

  • Claude Code: ~/.claude/skills/jezweb/claude-skills/cloudflare-turnstile/SKILL.md
  • Cursor: ~/.cursor/skills/jezweb/claude-skills/cloudflare-turnstile/SKILL.md
  • Antigravity: ~/.gemini/antigravity/skills/jezweb/claude-skills/cloudflare-turnstile/SKILL.md

πŸš€ Install with CLI:
npx skills add jezweb/claude-skills

Read the Master Guide: Mastering Agent Skills β†’

Recommended Rules

View more rules β†’

Recommended Workflows

View more workflows β†’

Recommended MCP Servers

View more MCP servers β†’

Take It Further

Maximize your productivity with these powerful resources

πŸ“‹

Define Your Standards

Set up coding standards to ensure this workflow produces consistent, high-quality results.

Browse Rules Library
πŸ“–

Master Workflows

Learn how to create custom workflows, use Turbo Mode, and build your automation library.

Complete Guide

How to use this Skill in Claude Code & Cursor

For Claude Code (CLI)

To use this skill in Claude Code, copy the rule content into your project's custom instructions or follow our Add-Skill CLI guide. This ensures Claude follows your standards during every code generation.

For Cursor & Windsurf

For Cursor or Windsurf, individual skills are best used in the "Rules for AI" section. This specific unit helps the agent avoid security & vulnerability analysis issues, leading to cleaner, more efficient code.

Why the skill format matters: the standardized Agent Skills format lets your AI agent load detailed instructions only when they are relevant, keeping your prompt clean while improving results.

Source & attribution

This skill is categorized under Security & Vulnerability Analysis and is published by JezWeb, maintained in jezweb/claude-skills.

← Browse All Agent Skills
Sponsored AI assistant. Recommendations may be paid.