Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/turnstile/gotchas.md
1# Troubleshooting & Gotchas23## Critical Rules45### ❌ Skipping Server-Side Validation6**Problem:** Client-only validation is easily bypassed.78**Solution:** Always validate on server.9```javascript10// CORRECT - Server validates token11app.post('/submit', async (req, res) => {12const token = req.body['cf-turnstile-response'];13const validation = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {14method: 'POST',15body: JSON.stringify({ secret: SECRET, response: token })16}).then(r => r.json());1718if (!validation.success) return res.status(403).json({ error: 'CAPTCHA failed' });19});20```2122### ❌ Exposing Secret Key23**Problem:** Secret key leaked in client-side code.2425**Solution:** Server-side validation only. Never send secret to client.2627### ❌ Reusing Tokens (Single-Use Rule)28**Problem:** Tokens are single-use. Revalidation fails with `timeout-or-duplicate`.2930**Solution:** Generate new token for each submission. Reset widget on error.31```javascript32if (!response.ok) window.turnstile.reset(widgetId);33```3435### ❌ Not Handling Token Expiry36**Problem:** Tokens expire after 5 minutes.3738**Solution:** Handle expiry callback or use auto-refresh.39```javascript40window.turnstile.render('#container', {41sitekey: 'YOUR_SITE_KEY',42'refresh-expired': 'auto', // or 'manual' with expired-callback43'expired-callback': () => window.turnstile.reset(widgetId)44});45```4647## Common Errors4849| Error | Cause | Solution |50|-------|-------|----------|51| **Widget not rendering** | Incorrect sitekey, CSP blocking, file:// protocol | Check sitekey, add CSP for challenges.cloudflare.com, use http:// |52| **timeout-or-duplicate** | Token expired (>5min) or reused | Generate fresh token, don't cache >5min |53| **invalid-input-secret** | Wrong secret key | Verify secret from dashboard, check env vars |54| **missing-input-response** | Token not sent | Check form field name is 'cf-turnstile-response' |5556## Framework Gotchas5758### React: Widget Re-mounting59**Problem:** Widget re-renders on state change, losing token.6061**Solution:** Control lifecycle with useRef.62```tsx63function TurnstileWidget({ onToken }) {64const containerRef = useRef(null);65const widgetIdRef = useRef(null);6667useEffect(() => {68if (containerRef.current && !widgetIdRef.current) {69widgetIdRef.current = window.turnstile.render(containerRef.current, {70sitekey: 'YOUR_SITE_KEY',71callback: onToken72});73}74return () => {75if (widgetIdRef.current) {76window.turnstile.remove(widgetIdRef.current);77widgetIdRef.current = null;78}79};80}, []);8182return <div ref={containerRef} />;83}84```8586### React StrictMode: Double Render87**Problem:** Widget renders twice in dev due to StrictMode.8889**Solution:** Use cleanup function.90```tsx91useEffect(() => {92const widgetId = window.turnstile.render('#container', { sitekey });93return () => window.turnstile.remove(widgetId);94}, []);95```9697### Next.js: SSR Hydration98**Problem:** `window.turnstile` undefined during SSR.99100**Solution:** Use `'use client'` or dynamic import with `ssr: false`.101```tsx102'use client';103export default function Turnstile() { /* component */ }104```105106### SPA: Navigation Without Cleanup107**Problem:** Navigating leaves orphaned widgets.108109**Solution:** Remove widget in cleanup.110```javascript111// Vue112onBeforeUnmount(() => window.turnstile.remove(widgetId));113114// React115useEffect(() => () => window.turnstile.remove(widgetId), []);116```117118## Network & Security119120### CSP Blocking121**Problem:** Content Security Policy blocks script/iframe.122123**Solution:** Add CSP directives.124```html125<meta http-equiv="Content-Security-Policy"126content="script-src 'self' https://challenges.cloudflare.com;127frame-src https://challenges.cloudflare.com;">128```129130### IP Address Forwarding131**Problem:** Server receives proxy IP instead of client IP.132133**Solution:** Use correct header.134```javascript135// Cloudflare Workers136const ip = request.headers.get('CF-Connecting-IP');137138// Behind proxy139const ip = request.headers.get('X-Forwarded-For')?.split(',')[0];140```141142### CORS (Siteverify)143**Problem:** CORS error calling siteverify from browser.144145**Solution:** Never call siteverify client-side. Call your backend, backend calls siteverify.146147## Limits & Constraints148149| Limit | Value | Impact |150|-------|-------|--------|151| Token validity | 5 minutes | Must regenerate after expiry |152| Token use | Single-use | Cannot revalidate same token |153| Widget size | 300x65px (normal), 130x120px (compact) | Plan layout |154155## Debugging156157### Console Logging158```javascript159window.turnstile.render('#container', {160sitekey: 'YOUR_SITE_KEY',161callback: (token) => console.log('✓ Token:', token),162'error-callback': (code) => console.error('✗ Error:', code),163'expired-callback': () => console.warn('⏱ Expired'),164'timeout-callback': () => console.warn('⏱ Timeout')165});166```167168### Check Token State169```javascript170const token = window.turnstile.getResponse(widgetId);171console.log('Token:', token || 'NOT READY');172console.log('Expired:', window.turnstile.isExpired(widgetId));173```174175### Test Keys (Use First)176Always develop with test keys before production:177- Site: `1x00000000000000000000AA`178- Secret: `1x0000000000000000000000000000000AA`179180### Network Tab181- Verify `api.js` loads (200 OK)182- Check siteverify request/response183- Look for 4xx/5xx errors184185## Misconfigurations186187### Wrong Key Pairing188**Problem:** Site key from one widget, secret from another.189190**Solution:** Verify site key and secret are from same widget in dashboard.191192### Test Keys in Production193**Problem:** Using test keys in production.194195**Solution:** Environment-based keys.196```javascript197const SITE_KEY = process.env.NODE_ENV === 'production'198? process.env.TURNSTILE_SITE_KEY199: '1x00000000000000000000AA';200```201202### Missing Environment Variables203**Problem:** Secret undefined on server.204205**Solution:** Check .env and verify loading.206```bash207# .env208TURNSTILE_SECRET=your_secret_here209210# Verify211console.log('Secret loaded:', !!process.env.TURNSTILE_SECRET);212```213214## Reference215216- [Turnstile Docs](https://developers.cloudflare.com/turnstile/)217- [Dashboard](https://dash.cloudflare.com/?to=/:account/turnstile)218- [Error Codes](https://developers.cloudflare.com/turnstile/troubleshooting/)219