Cloudflare Turnstile: How It Works, What It Sees on Your End, and How to Set It Up — Step-by-Step Guide
Table of contents
- Introduction: what you'll get from this guide
- Preliminary setup: tools and access
- Core concepts: how cloudflare turnstile works
- Step 1: understanding exactly what turnstile sees on your end
- Step 2: creating a widget in the cloudflare dashboard
- Step 3: embedding the widget on the page with the form
- Step 4: setting up server-side token verification
- Step 5: testing the widget in all modes with test keys
- Step 6: looking at turnstile through the visitor's eyes via mobile proxies
- Result verification: final checklist
- Common mistakes and their solutions
- Advanced capabilities for the pros
- Faq: common questions about cloudflare turnstile
- Conclusion
Introduction: What You'll Get From This Guide
Cloudflare Turnstile is that small widget with a spinning circle and the word "Verifying" that's increasingly replacing the familiar image-based captcha. It sits on registration forms, in e-commerce shopping carts, on landing pages, and in user accounts across various services. The average visitor barely notices it. But for a marketer, affiliate specialist, or developer working through mobile proxies and opening dozens of sites a day, it becomes a puzzle. Why does the check fly by in a second on one profile, while on another the widget hesitates, asks you to tick a checkbox, or throws an error code?
In this guide, we'll examine Cloudflare Turnstile from two sides. From the site owner's perspective — you'll add the widget to a page with your own hands, set up server-side verification, and learn to read the statistics. From the visitor's perspective — you'll understand exactly which signals Turnstile collects, what it sees about your browser, network, and proxy, and why it makes a particular decision. This knowledge is equally useful both for those protecting their forms from bots and for those who want their working profiles to look like regular users to the check.
Who This Guide Is For
- Business owners and marketers whose spam submissions and fake registrations ruin their stats and drain their budget.
- Developers who need to quickly and correctly embed Turnstile into a form and properly verify the token on the server.
- Affiliate specialists and multi-accounting professionals who work through mobile proxies and want to understand what Turnstile sees on their connection.
What You Need to Know Beforehand
No special knowledge required. It's enough to understand what an HTML page is, know how to open developer tools in a browser, and have at least minimal experience with any server-side language — PHP, Node.js, Python. If you don't have a server-side setup, you can still go through most of the guide: the widget can be added and tested on a local page.
How Much Time It Takes
A full walkthrough takes about an hour and a half to two hours. Registration and widget creation take 10-15 minutes, embedding on the page takes 20 minutes, server-side verification takes 30-40 minutes, testing and diagnostics — another 30 minutes. The theoretical sections can be read in any order and revisited as needed.
Preliminary Setup: Tools and Access
Before you start, gather everything you need. This will save you from pauses in the middle of the process.
Required Tools and Access
- A Cloudflare account. Free. Register with an email address in a couple of minutes. You don't need to move your domain to Cloudflare — Turnstile works on any site, wherever it's hosted.
- A site or test page. Any HTML page with a form will do. For local experiments, a file on your computer opened through a simple local server is enough.
- A server environment. Any hosting with PHP, or Node.js or Python on your machine. Needed for the second half of the guide — token verification.
- A modern browser. Chrome, Firefox, Edge, or Safari, current version, with developer tools open.
- A mobile proxy with IP rotation. Needed in the diagnostics section to see how the widget reacts to different networks and address rotation.
System Requirements
Turnstile isn't demanding. The widget works in any browser that supports modern JavaScript and loads its code from the domain challenges.cloudflare.com. If that domain is blocked in your network or by browser extensions, the widget won't load — keep that in mind when testing. Server-side verification requires the ability to make outgoing HTTPS requests.
What to Prepare Before You Start
- Create a text file for notes. You'll use it to record the site key, widget name, list of hostnames, and test results.
- Open the page with the form you want to protect and save a copy with the date marked. This is your backup.
- If you have a server-side form handler, make a copy of it too. We'll be adding verification code to it.
- Check that your local or production server serves the page over HTTPS or via localhost. Turnstile works over plain HTTP too, but you'll still need HTTPS on a production site.
⚠️ Warning: The widget's secret key must never be stored in HTML, in JavaScript on the page, or in a public repository. It lives only on the server. If you accidentally published it, immediately rotate the key in the Cloudflare dashboard — the old one will stop working.
Core Concepts: How Cloudflare Turnstile Works
To make the following steps clear, let's break down the key terms in plain language.
Key Terms
- Widget — the block the visitor sees. Technically, it's an iframe loaded from Cloudflare's domain and embedded in your page.
- Site key — the widget's public identifier. It's inserted into the HTML and visible to everyone. Cloudflare uses it to know which widget to render and for which domains it's allowed.
- Secret key — the private key. Your server uses it to confirm that the incoming token is genuine. It never leaves the server.
- Token — the string the widget issues after a successful check. It's placed in a hidden form field and sent to your server along with the rest of the data.
- Siteverify — the Cloudflare endpoint where the server sends the token along with the secret key and gets a response: success or not.
- Widget mode — how it's displayed: Managed, Non-interactive, or Invisible. The difference is described below.
- Hostname — the domain where the widget is allowed to be used. If the domain isn't listed, the widget will refuse to work with an error.
How It Works in Four Sentences
- The page loads the Turnstile script, and the widget quietly runs a set of checks in the browser.
- Cloudflare collects the results, evaluates them together with network data, and decides: pass immediately, show a checkbox for confirmation, or deny.
- On success, the widget generates a one-time token and inserts it into the form.
- Your server receives the form, sends the token to siteverify, and only processes the submission on a positive response.
What Turnstile Checks — The Big Picture
Here it's important to understand the main thing. Cloudflare Turnstile doesn't look at "humanness" through image puzzles. It evaluates environment consistency: how well the browser, network, and behavior add up to one plausible picture. The main signal groups:
- Browser environment. The script runs a series of small JavaScript tasks and checks that the environment behaves like a real browser: how window objects are structured, how graphics render, whether there are signs of automation, whether the declared User-Agent matches the engine's actual capabilities.
- Proof of work. The widget asks the browser to perform a small computation. For a human, it's a fraction of a second; for a bot opening thousands of pages, it's a noticeable load.
- Network signals. The reputation of the IP address and autonomous system the request comes from, whether network characteristics match the declared browser, and the history of requests from this address across Cloudflare's entire network.
- Device trust tokens. On Apple devices and in some other ecosystems, Turnstile can request confirmation from the operating system that it's a real device — and then the check passes with no computation at all.
- On-page behavior. When the widget appears, when the form is submitted, and how natural the actions are in Managed mode.
What Turnstile doesn't do: it doesn't collect data for ad profiling, doesn't track users across sites via third-party cookies, and never shows image puzzles. This matters both from a data privacy law perspective and from a conversion perspective — visitors don't leave because of an annoying captcha.
The Three Widget Modes
- Managed — the default mode. The widget is visible, spins an indicator, and if it's unsure, shows a checkbox you need to click. Suitable for most forms.
- Non-interactive — the widget is visible but never requires action. It either passes on its own or throws an error. Good for pages where extra clicks can't be allowed.
- Invisible — the widget isn't rendered at all. The check runs in the background. Convenient for buttons and forms where you don't want to change the design, but it requires careful error handling.
Step 1: Understanding Exactly What Turnstile Sees on Your End
Goal of this stage: before configuring anything, you should understand what data about your environment the widget receives. This is the foundation for diagnostics in the following steps and for deliberate work through mobile proxies.
How to Watch the Widget Work With Your Own Eyes
- Open any site that uses Cloudflare Turnstile. These widgets are easy to spot by the Cloudflare logo in the bottom right corner of the block and the "Privacy" and "Terms" links.
- Press F12 or right-click — "Inspect" to open developer tools.
- Go to the "Network" tab and refresh the page.
- In the filter bar, type challenges.cloudflare.com. You'll see several requests: loading api.js, loading the widget iframe itself, and one or more POST requests — that's the submission of check results.
- Open the "Elements" tab and find the block with the class cf-turnstile. Inside it, after a successful check, a hidden input field will appear with the name cf-turnstile-response and a long string as its value. That's the token.
Tip: The contents of POST requests are encrypted and obfuscated; reading them is pointless. Look at something else: how many requests were sent, how long the check took, and whether the widget status changed to "Success." That's your external trust indicator.
What Turnstile Sees About Your Browser
The widget runs JavaScript right in your window, so it has access to everything any script on the page does: engine version, installed APIs, screen dimensions, time zone, interface languages, graphics and font rendering characteristics, and the behavior of functions that are often overridden in automated browsers. It doesn't read your files or poke into other tabs. But it's excellent at noticing when a browser claims one thing and does another. For example, the User-Agent says "Chrome on Android," but the environment has no touch events and has APIs that don't exist on mobile devices.
What Turnstile Sees About Your Network
This is where things get interesting for those working through mobile proxies. All the widget's requests go to Cloudflare's servers, which means Cloudflare sees your external IP address, its autonomous system (i.e., the carrier), the country, and also low-level connection characteristics — exactly how your client establishes the secure connection. These characteristics differ across browsers, and Cloudflare compares them against the declared User-Agent.
Mobile carriers hand out addresses from large shared pools; hundreds of real subscribers sit behind one address at the same time. That's why such addresses have a neutral or good reputation on their own — blocking them would mean blocking real people. But reputation is only one signal. If a browser with a desktop script's network fingerprint, a time zone from another continent, and signs of automation comes from a mobile address, the picture stops adding up, and the widget switches to interactive mode or denies access.
What Turnstile Sees About Your Behavior
In Managed mode, the widget pays attention to how quickly the form is submitted after loading, whether the user interacted with the page, and whether the checkbox click looks natural. In Invisible and Non-interactive modes, the behavioral component is minimal — the decision is made based on environment and network.
✅ Check: At this stage, you should be able to open the Network tab, filter requests to challenges.cloudflare.com, see the hidden field cf-turnstile-response, and explain in your own words the three signal groups: browser, network, behavior. If you can, move on to creating your own widget.
Possible Issues
- There are no requests to challenges.cloudflare.com at all. Most likely the domain is blocked by a browser extension or corporate filter. Disable blockers during testing.
- The widget hangs in the verifying state forever. Check your computer's system time: a large discrepancy with the real time breaks the check.
Step 2: Creating a Widget in the Cloudflare Dashboard
Goal of this stage: get a pair of keys — site key and secret key — and properly configure the domain list and operation mode.
- Open the Cloudflare dashboard and log in. If you don't have an account, click "Sign up," enter your email and password, and confirm the email.
- In the left menu, find Turnstile. If you have multiple accounts, first select the right one on the main page.
- Click the blue Add widget button.
- In the Widget name field, enter a clear name, for example "Landing form — main." The name is only visible to you, but with a dozen widgets it'll save you from confusion.
- In the Hostname management block, click Add hostnames and enter the domains where the widget will work. Enter them without the protocol and without a path: example.com, not https://example.com/form. Subdomains must be added separately, or specify the root domain — then subdomains will also be allowed.
- For local tests, add localhost to the list. This is officially supported and doesn't interfere with production use.
- In the Widget Mode block, choose the mode. For the first time, pick Managed — that way you'll see all widget states, including interactive.
- For now, leave the Pre-clearance option off. It's only needed if the site is proxied through Cloudflare, and we'll cover it in the advanced section.
- Click Create.
- On the next screen you'll see two fields: Site Key and Secret Key. Copy both into your notes file. The secret key can be viewed later in the widget settings, but it's more convenient to save it right away.
Tip: Create two widgets at once — one for the production domain, and a second one named "Test" with the hostname localhost. That way you'll experiment with modes and settings without touching the working widget's statistics.
What the Correct Result Looks Like
A card will appear in the Turnstile list with the widget name, its mode, and the hostname list. The site key starts with "0x" and is about 24 characters long; the secret key also starts with "0x" but is longer. If a key looks different, you probably copied the wrong field.
✅ Check: Your notes file contains the site key, secret key, widget name, hostname list, and the chosen mode. In the Cloudflare dashboard, the widget appears in the list with active status.
Possible Issues
- The Create button is inactive. No hostname has been added, or it was entered with an error (protocol, slash, space).
- The Turnstile item isn't in the menu. You're inside a specific domain's settings. Go back to the account level — Turnstile lives there, not inside a zone.
Step 3: Embedding the Widget on the Page With the Form
Goal of this stage: the widget displays on your page, passes the check, and inserts the token into the form.
Connecting the Script
- Open the HTML file of the page with the form in your editor.
- Inside the head tag or before the closing body tag, add the script connection line:
<script src='https://challenges.cloudflare.com/turnstile/v0/api.js' async defer></script>The async and defer attributes let the page not wait for the script to load. The widget will appear a bit later, but the user won't see any delay in content loading.
Placing the Widget Container
- Find the form you're protecting. Usually it's a form tag with name, email, and phone fields.
- Right before the submit button, insert an empty block with the class cf-turnstile and your site key:
<form action='/submit.php' method='POST'> <input type='text' name='name' placeholder='Your name'> <input type='email' name='email' placeholder='Email'> <div class='cf-turnstile' data-sitekey='YOUR_SITE_KEY' data-theme='light'></div> <button type='submit'>Submit</button> </form>- Replace YOUR_SITE_KEY with the key from your notes file. The secret key must not be inserted here.
- Save the file and open the page in your browser via localhost.
What You Should See
A second or two after loading, a widget about 300 by 65 pixels will appear in the block's place. First it shows a loading indicator and the text "Verifying," then a green checkmark and "Success." If Cloudflare decided to re-verify the environment, a checkbox with the text "Verify you are human" will appear — click it, and after a moment the widget will show success.
Open developer tools, the Elements tab, and expand the cf-turnstile block. Inside, a hidden input field with the name cf-turnstile-response has appeared. Its value is a long token. That's what gets sent to the server when the form is submitted.
Useful Container Attributes
- data-theme — light, dark, or auto. Auto adapts to the user's system theme.
- data-size — normal, compact, or flexible. Flexible stretches the widget to the container's width — handy for mobile layouts.
- data-language — language code, e.g., en. By default, the widget takes the browser's language.
- data-action — a short label, e.g., login or checkout. It's returned in the siteverify response and helps distinguish forms in statistics.
- data-callback — the name of the JavaScript function called on success. The token is passed to it.
- data-error-callback — the function that receives an error code if something goes wrong.
- data-refresh-expired — what to do when the token expires: auto re-requests on its own, manual shows a refresh button, never does nothing.
Tip: Add data-error-callback right away and log the error code to the console. Turnstile's codes are informative: the 110xxx series indicates key or domain problems, 300xxx indicates a browser execution failure, 600xxx means the check wasn't passed. Without this, you'll be guessing why the widget is silent.
Alternative Path: Explicit Rendering via JavaScript
If you're working in a framework or want to control when the widget appears, replace implicit rendering with explicit. Add the parameter render=explicit to the script URL and call turnstile.render with the needed parameters:
turnstile.render('#my-widget', { sitekey: 'YOUR_SITE_KEY', theme: 'auto', action: 'signup', callback: function(token) { console.log('Token received', token.length); } });This approach lets you re-render the widget after an error using the turnstile.reset method and get the current token with the turnstile.getResponse method.
✅ Check: The widget displays on the page, shows "Success," the cf-turnstile-response field with the token is present in the DOM, and there are no errors in the console. Try refreshing the page three or four times — a new token should appear each time.
Possible Issues
- The widget shows error 110200. The domain the page is opened from isn't added to the widget's hostnames. Check that you're opening it via localhost, not via 127.0.0.1 or file:// — these are different hostnames.
- The widget doesn't appear, the console is empty. The script didn't load. Check the script URL for typos and for blockers.
- The widget breaks the layout. Use data-size='flexible' or wrap the block in a container of the needed width.
Step 4: Setting Up Server-Side Token Verification
Goal of this stage: the server rejects any form submissions without a valid token. This is the most important step — without it, the widget is just decoration, because a bot can send a POST request directly, bypassing the page.
How the siteverify Request Works
Your server sends a POST request to https://challenges.cloudflare.com/turnstile/v0/siteverify with the fields:
- secret — your secret key;
- response — the token from the cf-turnstile-response field;
- remoteip — the visitor's IP, optional but useful;
- idempotency_key — an optional unique request identifier, covered in the advanced section.
The response comes back as JSON. Key fields:
{ "success": true, "challenge_ts": "2026-03-14T10:22:31.000Z", "hostname": "example.com", "error-codes": [], "action": "signup", "cdata": "" }The token lives for 300 seconds and is single-use. Re-verifying the same token returns the timeout-or-duplicate error.
PHP Example
- Open the form handler file, e.g., submit.php.
- At the very beginning, before any work with the form data, add the verification block:
<?php $token = $_POST['cf-turnstile-response'] ?? ''; if ($token === '') { http_response_code(400); exit('Verification failed: no token'); } $data = [ 'secret' => getenv('TURNSTILE_SECRET'), 'response' => $token, 'remoteip' => $_SERVER['REMOTE_ADDR'] ]; $ch = curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $raw = curl_exec($ch); curl_close($ch); $result = json_decode($raw, true); if (empty($result['success'])) { http_response_code(403); exit('Verification failed: ' . implode(',', $result['error-codes'] ?? ['no-response'])); }