Select all squares with traffic lights. Now with bicycles. Now again, because you clicked a pixel too far. Everyone knows this ritual, and every website owner should know that each such exercise means abandoned forms and lost enquiries. In the last article of our series on Cloudflare tools, we take a look at Turnstile, a free alternative to reCAPTCHA that tells humans from bots without image puzzles. We show the full implementation: from the widget in HTML to verification in PHP, which is easy to plug into WordPress forms.
Cloudflare Turnstile: the end of annoying CAPTCHAs
24.09.2026 | Author: Marcin Wiercioch
What is wrong with classic CAPTCHA?
- UX and conversion: research has shown the same thing for years: an extra puzzle in a form means a few to more than ten percent more abandonments, and on mobile devices it can be worse,
- accessibility: image puzzles exclude visually impaired people, and audio alternatives can be harder than the test itself,
- privacy: reCAPTCHA sends user data to Google, which under GDPR requires consent and proper information; European data protection authorities have looked into this many times,
- effectiveness: modern bots often solve image puzzles better than humans, so it is mainly humans who suffer.
How does Turnstile work?
Instead of puzzles, Turnstile runs a set of non-intrusive browser tests in the background: it checks environment properties and behaviour typical of bots, without profiling the user and without tracking cookies. At most, the user sees a small widget with a spinning indicator and a success tick. The verification result goes into the form as a token, which your server has to confirm with the Cloudflare API. The service is free, also works on websites that do not use Cloudflare as a CDN, and comes in three modes: managed (a widget), non-interactive (a bar with no user involvement) and invisible.
Step 1: the keys
In the Cloudflare dashboard (the Turnstile section), we add a widget for our domain and get a pair of keys: the site key (public, goes into the HTML) and the secret key (secret, stays on the server). For local testing, Cloudflare provides special test keys that always pass or always fail, which is handy in a development environment.
Step 2: the widget in the form
<form action="/send.php" method="POST">
<input type="text" name="name" required>
<input type="email" name="email" required>
<textarea name="message" required></textarea>
<div class="cf-turnstile"
data-sitekey="0x4AAAAAAA_YourSiteKey"
data-theme="auto"
data-language="en"></div>
<button type="submit">Send</button>
</form>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
After successful verification, Turnstile adds a hidden cf-turnstile-response field with the token to the form. And here is an important thing we see in half of the projects we take over: the widget alone protects nothing. A bot can send a POST request directly, bypassing the browser. Protection only comes with step three.
Step 3: server-side verification
The server has to send the token to the siteverify endpoint and check the response. In PHP, and therefore in WordPress, it looks like this:
function turnstile_ok(string $token, string $ip): bool {
if ($token === "") {
return false;
}
$response = wp_remote_post("https://challenges.cloudflare.com/turnstile/v0/siteverify", [
"body" => [
"secret" => TURNSTILE_SECRET_KEY,
"response" => $token,
"remoteip" => $ip,
],
"timeout" => 5,
]);
if (is_wp_error($response)) {
return false;
}
$data = json_decode(wp_remote_retrieve_body($response), true);
return !empty($data["success"]);
}
// in the form handler:
$token = $_POST["cf-turnstile-response"] ?? "";
if (!turnstile_ok($token, $_SERVER["REMOTE_ADDR"])) {
wp_die("Spam verification failed. Please try again.");
}
We keep the secret in wp-config.php (the TURNSTILE_SECRET_KEY constant), not in the theme code. Important protocol details: the token is single-use and valid for 300 seconds. It cannot be verified twice, so with your own AJAX validation you need to reset the widget after a failed attempt (turnstile.reset()).
Integration with WordPress plugins
If your forms are handled by Contact Form 7, WPForms, Gravity Forms or WooCommerce, you do not need to write code: mature plugins (such as Simple Cloudflare Turnstile) add the widget and verification to login, registration, comment and checkout forms once you paste in the pair of keys. We leave custom code, like the example above, for bespoke forms, such as our own contact form.
Modes and good practices
- start with managed mode, where Cloudflare decides when to show an interaction; leave invisible mode for low-risk forms,
- set
data-theme="auto"anddata-languageto the language of your website, so the widget matches the site’s theme and the user’s language, - Turnstile protects a form, not the whole website; mass bots scanning the site are only stopped by WAF and rate limiting at the Cloudflare level,
- keep logs of rejected verifications (the error codes from siteverify); they make diagnosis easier when a customer reports that “the form does not work”,
- remember a correct CSP: the Turnstile script and frames need an exception for the challenges.cloudflare.com domain.
Turnstile and GDPR
Turnstile does not use cookies for tracking and does not build a user profile, and Cloudflare acts as a data processor here. In practice, implementation comes down to a mention in your privacy policy, without a separate consent that blocks the form. This is a completely different legal burden from embedding a service that links verification with an advertising ecosystem.
Series summary
This is the fifth and last part of our series: we went from running code at the network edge (Workers), through data stores (KV and R2) and product analytics (PostHog), to protecting forms without annoying people. The common denominator? Each of these tools lets small teams use infrastructure that a decade ago required a DevOps department. If you want to bring any of these solutions into your project, from a single form to a full edge architecture, let’s talk.



