Turnstile foundations
Understand the Turnstile flow
Trace widget rendering, browser challenge, token submission, server validation, and the protected application action.
Turnstile is Cloudflare’s CAPTCHA alternative. It tells humans and bots apart without asking people to click on blurry traffic lights. Most of the time the visitor does nothing at all. And it works on any site, even one that does not use Cloudflare’s CDN.
The protection has two halves. You need both.
The browser half
The widget renders on your page and obtains a short-lived token:
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<form method="POST" action="/submit">
<input type="email" name="email" required />
<div class="cf-turnstile" data-sitekey="your-site-key"></div>
<button type="submit">Sign up</button>
</form>
When the challenge passes, Turnstile adds a hidden field named cf-turnstile-response to the form. That field holds the token. It’s the only thing the browser half produces.
The server half
The form sends that token to your server. Your server calls Siteverify, Cloudflare’s validation endpoint, with your secret key. Then it reads the result and decides whether to run the protected action:
const result = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
body: new URLSearchParams({
secret: env.TURNSTILE_SECRET,
response: token,
}),
})
const outcome = await result.json()
if (!outcome.success) {
return new Response('Failed the bot check', { status: 403 })
}
Only when outcome.success is true do you do the real work: store the signup, send the email, whatever the form protects.
Why the widget alone proves nothing
An attacker can call your endpoint directly and skip the page entirely. One curl command posts the form fields without ever rendering your HTML. So a missing server check means no check at all. The widget is a way to get a token. Siteverify is the check.
There is a mirror-image mistake: calling Siteverify from browser JavaScript. That means shipping the secret key to the client, where anyone can read it. Siteverify is a server-to-server call. If you ever spot siteverify in client code, treat the secret as leaked and rotate it.
Draw the complete contact-form flow on paper and mark the only component you trust to approve the final submission. It is your server. Everything before that point is input.
Lesson completed