Turnstile
By the end of this lesson you will understand how Turnstile works, how to integrate it into your forms, and how to validate tokens on your server.
What Is Turnstile?
Turnstile is Cloudflare's free CAPTCHA alternative. Instead of making users solve puzzles (like reCAPTCHA), Turnstile uses invisible browser challenges to verify that a visitor is human — without friction.
flowchart LR
USER["User submits form"] --> TURNSTILE["Turnstile Widget\n(browser challenge)"]
TURNSTILE -->|"Token generated"| SERVER["Your Server"]
SERVER -->|"Verify token"| CF_API["Cloudflare\nVerification API"]
CF_API -->|"✅ Valid / ❌ Invalid"| SERVER
style TURNSTILE fill:#f6821f,color:#fff,stroke:#e5711e
style CF_API fill:#2563eb,color:#fff,stroke:#1e40af
Why Turnstile over reCAPTCHA?
| Feature | Turnstile | reCAPTCHA v2 | reCAPTCHA v3 |
|---|---|---|---|
| User friction | None (invisible) or minimal (one click) | High (image puzzles) | None (invisible) |
| Privacy | No tracking, no cookies, GDPR-friendly | Tracks users, sets cookies | Tracks users, sets cookies |
| Cost | Free (unlimited) | Free (with limits) | Free (with limits) |
| Speed | ~100ms challenge | 10–30 seconds for user | ~100ms (but less accurate) |
| Hosted anywhere | ✅ Works on any site (no Cloudflare required) | ✅ | ✅ |
| Accuracy | High | High (but annoying) | Medium (score-based) |
Turnstile works on any website — you don't need to use Cloudflare as your CDN. It's a standalone widget you can add to any form.
Widget Modes
Turnstile offers three widget modes:
| Mode | User Experience | When to Use |
|---|---|---|
| Managed | Cloudflare decides — invisible if confident, shows a checkbox if needed | Default — best for most use cases |
| Non-Interactive | Always invisible — user never sees a widget | Login forms, checkout — zero friction required |
| Invisible | No visible widget at all — runs entirely in the background | API protection, background form validation |
Setting Up Turnstile
Step 1: Create a Turnstile Widget
- Go to dash.cloudflare.com/turnstile
- Click "Add site"
- Enter your website's domain
- Select the widget mode (Managed, Non-Interactive, or Invisible)
- Copy your Site Key and Secret Key
Step 2: Add the Widget to Your HTML
<!DOCTYPE html>
<html>
<head>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
<form action="/login" method="POST">
<input type="email" name="email" placeholder="Email" required>
<input type="password" name="password" placeholder="Password" required>
<!-- Turnstile widget -->
<div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
<button type="submit">Log In</button>
</form>
</body>
</html>
The widget renders automatically and generates a token (cf-turnstile-response) in a hidden form field when the challenge passes.
Step 3: Verify the Token on Your Server
When the form is submitted, your server receives the cf-turnstile-response token. You must verify it with Cloudflare's API:
import requests
from flask import Flask, request
app = Flask(__name__)
SECRET_KEY = "YOUR_SECRET_KEY"
@app.route("/login", methods=["POST"])
def login():
token = request.form.get("cf-turnstile-response")
# Verify with Cloudflare
response = requests.post(
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
data={
"secret": SECRET_KEY,
"response": token,
"remoteip": request.remote_addr # optional
}
)
result = response.json()
if result.get("success"):
# Human verified — proceed with login
return "Login successful!"
else:
# Verification failed — block the request
return "Verification failed", 403
const express = require('express');
const app = express();
app.use(express.urlencoded({ extended: true }));
const SECRET_KEY = "YOUR_SECRET_KEY";
app.post('/login', async (req, res) => {
const token = req.body['cf-turnstile-response'];
const response = await fetch(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `secret=${SECRET_KEY}&response=${token}`
}
);
const result = await response.json();
if (result.success) {
res.send('Login successful!');
} else {
res.status(403).send('Verification failed');
}
});
Verification API Response
{
"success": true,
"challenge_ts": "2024-01-01T00:00:00Z",
"hostname": "example.com",
"action": "login",
"cdata": ""
}
{
"success": false,
"error-codes": ["invalid-input-response"]
}
Advanced Configuration
Custom Appearance
<div class="cf-turnstile"
data-sitekey="YOUR_SITE_KEY"
data-theme="dark"
data-size="compact"
data-language="auto">
</div>
| Attribute | Options | Default |
|---|---|---|
data-theme | light, dark, auto | auto |
data-size | normal, compact | normal |
data-language | auto, or ISO language code | auto |
data-action | Custom action name (e.g., login, signup) | none |
data-callback | JavaScript function called on success | none |
JavaScript API
// Render manually
const widgetId = turnstile.render('#container', {
sitekey: 'YOUR_SITE_KEY',
callback: function(token) {
console.log('Challenge passed:', token);
},
theme: 'dark'
});
// Reset the widget
turnstile.reset(widgetId);
// Remove the widget
turnstile.remove(widgetId);
Testing Keys
Cloudflare provides test keys for development:
| Key | Behavior |
|---|---|
Site key: 1x00000000000000000000AA | Always passes |
Site key: 2x00000000000000000000AB | Always blocks |
Site key: 3x00000000000000000000FF | Forces interactive challenge |
Secret key: 1x0000000000000000000000000000000AA | Always passes verification |
Secret key: 2x0000000000000000000000000000000AA | Always fails verification |
Common Misconceptions
"Turnstile only works on Cloudflare-proxied sites"
Reality: Turnstile works on any website, regardless of whether it uses Cloudflare as a CDN. It's a standalone product.
"Invisible mode means no security"
Reality: Invisible mode runs the full browser challenge in the background. It's just as secure — the user simply doesn't see a widget.
"I need to pay after hitting a certain number of verifications"
Reality: Turnstile is free and unlimited — no per-verification charges, no volume limits.
Key Takeaways
- Turnstile is a free, unlimited CAPTCHA alternative — no puzzles, privacy-preserving.
- It works on any site — not just Cloudflare-proxied domains.
- Three modes: Managed (recommended), Non-Interactive, and Invisible.
- Always verify the token server-side — never trust client-side validation alone.
- Use test keys during development to simulate pass/fail scenarios.
What's Next
- Continue to Challenges to learn about Cloudflare's challenge mechanisms.