Cloudflare Turnstile Setup: 13 Steps, Free vs $8/Mo [2026]

Credential stuffing bots hit login forms an average of several times a second on any mid-sized site, and the CAPTCHA tools built to stop them have started to cost real money once traffic scales. Google’s reCAPTCHA caps its free tier at 10,000 assessments a month before billing kicks in, and hCaptcha’s Pro plan starts at $139 a month past 100,000 checks. Cloudflare’s answer, Turnstile, skips the “click all the traffic lights” puzzle entirely and remains free with no published request cap on its Standard plan, and Cloudflare’s September 25, 2026 announcement states that Turnstile is free to use for everyone, works on any site, and does not require traffic to be proxied through Cloudflare. The scale advantage is borne out by Cloudflare’s own numbers, which show about 3 billion Turnstile verifications running through the service on a typical weekday in September 2026. This tutorial walks through setting it up from a blank Cloudflare account to a production login form protected by server-side verification, WAF rate limiting, and Logpush analytics.

By the end you will have a working sitekey and secret key pair, a tested widget on an HTML form, server-side validation code in both Node.js and Python, and a fallback plan for the eight most common integration failures. The whole setup takes about 90 minutes for a single site, longer if you are wiring Turnstile into an existing WAF rate-limiting policy.

Google · Preferred Sources

Don't miss new tech stories on Google

Add Tech Insider once in the Google app and our stories appear in your news suggestions.

Add Now

What Is Cloudflare Turnstile and Why It Replaced CAPTCHA

Cloudflare Turnstile is a bot-detection widget that verifies a visitor is not a bot without forcing them to solve a puzzle. Instead of image grids or distorted text, Turnstile runs a set of non-interactive browser and behavioral checks (canvas fingerprinting signals, timing analysis, proof-of-work challenges) and only escalates to a visible checkbox or challenge when the automated signals are inconclusive. Cloudflare markets it explicitly as a “CAPTCHA replacement,” and the company’s own pages state that Cloudflare itself has been CAPTCHA-free for years using the same underlying tech.

Turnstile ships in three widget modes, and all three are available on the free plan:

  • Managed — Cloudflare decides whether to show a checkbox, a challenge, or nothing at all based on risk signals. This is the default and the one most tutorials start with.
  • Non-interactive — a small animated badge appears but the visitor never has to click anything; it runs risk scoring silently in the background.
  • Invisible — no visible UI at all unless Cloudflare needs to challenge a specific request, useful on checkout pages where friction directly hurts conversion.

The widget issues a short-lived token after it finishes its checks. Your backend then exchanges that token for a pass/fail verdict by calling Cloudflare’s siteverify API. That server-side call is the part most tutorials gloss over and the part that actually stops bots, since a client-side-only integration can be bypassed by simply not running the JavaScript at all.

How Turnstile’s Risk Scoring Actually Works

Turnstile doesn’t publish its exact scoring model, but the public documentation and changelog entries make the broad strokes clear. When the widget loads, it collects a set of passive browser signals — things like how the JavaScript engine responds to certain function calls, whether canvas rendering behaves the way a real GPU-backed browser would, and timing patterns around DOM events. None of that requires the visitor to do anything. In parallel, Turnstile can run a lightweight proof-of-work challenge in the background, which costs a real browser almost nothing in CPU time but becomes expensive to fake at scale across thousands of headless requests.

If those signals come back clean, the widget issues a passing token without ever showing the visitor anything, which is why Managed mode on a normal browser session usually renders as an empty checkbox that briefly shows a checkmark and disappears. If the signals are ambiguous — a headless browser with some but not all automation flags stripped, for instance — Turnstile escalates to a visible interactive challenge. Only clearly automated traffic gets flagged outright. This tiered approach is why Turnstile’s pass-through rate for real users is high enough that most site owners report the widget as invisible in practice, even though it’s actively scoring every request.

Prerequisites and Versions You’ll Need

Before starting, confirm you have the following. None of this requires a paid Cloudflare plan — Turnstile Standard is $0/month with up to 20 widgets per account and 10 hostnames per widget, though enterprise-scale accounts get 200 hostnames per widget and 30-day analytics retention instead of the free tier’s 7-day lookback.

  • A free Cloudflare account (no domain has to be on Cloudflare’s DNS or proxy for Turnstile to work — this is a common misconception)
  • Node.js 20 LTS or newer if you’re following the Express example, or Python 3.11+ for the Flask example
  • A test domain or localhost setup — Cloudflare provides dummy sitekeys specifically for local development
  • curl or Postman for testing the siteverify API directly
  • Optional: a Cloudflare paid zone plan if you want to layer WAF rate-limiting rules on top (rate limiting rules are available on every Cloudflare plan including Free (with a 1-rule limit), though Turnstile itself does not require it)

If you’re comparing this against a WAF-based bot mitigation stack, see our breakdown of Cloudflare WAF vs AWS WAF vs Azure WAF for how the pricing and rule engines differ once you add rate limiting on top of Turnstile.

Step 1: Create or Confirm Your Cloudflare Account

Sign up at Cloudflare’s dashboard if you don’t already have an account. Unlike Cloudflare’s core CDN and DNS products, Turnstile does not require you to point your domain’s nameservers at Cloudflare. You can run Turnstile on a site hosted anywhere — Vercel, a VPS, shared hosting — because the widget only talks to Cloudflare’s own verification servers, not your DNS.

Log in and confirm your email if this is a new account. You do not need to add a credit card; the Free plan requires none.

Step 2: Open the Turnstile Dashboard and Create a Widget

From the Cloudflare dashboard sidebar, find Turnstile under the security or application services section. Click “Add widget.” You’ll be asked for:

  • Widget name — an internal label, e.g., “Production Login Form”
  • Domains — every hostname the widget will run on, including staging subdomains and localhost if you test locally
  • Widget mode — Managed, Non-interactive, or Invisible (you can change this later without regenerating keys)

As of the August 2026 general availability release of Turnstile Spin, Cloudflare offers three guided setup paths in this same screen for wiring the widget straight into your backend framework, which shortens this step if you’re using one of the frameworks it recognizes. The feature clearly struck a nerve — Cloudflare reported more than 23,000 accounts creating a brand-new Turnstile widget in a single week in September 2026, a pace of adoption the company attributes directly to Spin’s guided onboarding.

Step 3: Save Your Sitekey and Secret Key

Once the widget is created, Cloudflare generates two credentials:

  • Sitekey — a public, opaque string embedded in your frontend JavaScript. Safe to expose in HTML source.
  • Secret key — a private string used only in server-side calls to the siteverify API. Never ship this to the browser.

Store the secret key in your environment variable manager or secrets store rather than hardcoding it. If you’re already running HashiCorp Vault for other credentials, see our HashiCorp Vault setup guide for the pattern to follow — the same segregation logic applies to a Turnstile secret key as it does to a database password.

Step 4: Add the Client-Side Widget to a Plain HTML Form

Drop this script tag and div into any form. Replace YOUR_SITE_KEY with the sitekey from Step 3.

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

<form action="/login" method="POST">
  <input type="email" name="email" required>
  <input type="password" name="password" required>

  <div class="cf-turnstile"
       data-sitekey="YOUR_SITE_KEY"
       data-callback="onTurnstileSuccess"
       data-action="login"></div>

  <button type="submit">Log In</button>
</form>

<script>
  function onTurnstileSuccess(token) {
    console.log("Turnstile token issued:", token);
  }
</script>

The widget automatically injects a hidden input named cf-turnstile-response into the form containing the token. That field is what your backend reads during verification — you don’t need to manually wire the callback unless you want extra client-side logic before submit.

Step 5: Add Turnstile to a React or Next.js Form

For component-based frameworks, load the script once and render the widget div inside your form component. This example uses plain React without a third-party wrapper library, so it works whether you’re on Create React App, Vite, or Next.js’s App Router.

import { useEffect, useRef } from "react";

export default function LoginForm() {
  const widgetRef = useRef(null);

  useEffect(() => {
    const script = document.createElement("script");
    script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js";
    script.async = true;
    document.body.appendChild(script);
    return () => document.body.removeChild(script);
  }, []);

  return (
    <form action="/api/login" method="POST">
      <input type="email" name="email" required />
      <input type="password" name="password" required />
      <div
        ref={widgetRef}
        className="cf-turnstile"
        data-sitekey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY}
        data-action="login"
      />
      <button type="submit">Log In</button>
    </form>
  );
}

Keep the sitekey in a NEXT_PUBLIC_-prefixed (or equivalent client-exposed) environment variable since it’s meant to be public. The secret key stays server-only and should never carry that prefix.

Step 6: Verify the Token Server-Side With Node.js and Express

This is the step that actually blocks bots. Client-side widgets can be stripped out by anyone reading your HTML; the server-side check against Cloudflare’s own infrastructure cannot be faked without a valid token.

const express = require("express");
const app = express();
app.use(express.urlencoded({ extended: true }));

const TURNSTILE_SECRET = process.env.TURNSTILE_SECRET_KEY;
const VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";

app.post("/login", async (req, res) => {
  const token = req.body["cf-turnstile-response"];
  const ip = req.headers["cf-connecting-ip"] || req.ip;

  if (!token) {
    return res.status(400).json({ error: "missing-turnstile-token" });
  }

  const verifyRes = await fetch(VERIFY_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      secret: TURNSTILE_SECRET,
      response: token,
      remoteip: ip,
    }),
  });

  const outcome = await verifyRes.json();

  if (!outcome.success) {
    console.warn("Turnstile rejected:", outcome["error-codes"]);
    return res.status(403).json({ error: "bot-check-failed" });
  }

  // outcome.action, outcome.cdata, outcome.challenge_ts, outcome.hostname
  // are all available here for logging and per-endpoint scoring
  return res.json({ status: "verified", proceeding_to: "auth-check" });
});

app.listen(3000);

The success field is the only one you strictly need to gate on, but action and cdata are worth logging if you reuse the same sitekey across multiple forms — they let you tell a login attempt apart from a signup attempt in your analytics without deploying a second widget.

Step 7: Verify the Token Server-Side With Python and Flask

If your backend runs Python instead, the logic is identical — just a different HTTP client.

import os
import requests
from flask import Flask, request, jsonify

app = Flask(__name__)
TURNSTILE_SECRET = os.environ["TURNSTILE_SECRET_KEY"]
VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify"

@app.route("/login", methods=["POST"])
def login():
    token = request.form.get("cf-turnstile-response")
    remote_ip = request.headers.get("CF-Connecting-IP", request.remote_addr)

    if not token:
        return jsonify({"error": "missing-turnstile-token"}), 400

    resp = requests.post(VERIFY_URL, json={
        "secret": TURNSTILE_SECRET,
        "response": token,
        "remoteip": remote_ip,
    }, timeout=5)
    outcome = resp.json()

    if not outcome.get("success"):
        app.logger.warning("Turnstile rejected: %s", outcome.get("error-codes"))
        return jsonify({"error": "bot-check-failed"}), 403

    return jsonify({"status": "verified"})

Set a request timeout on the outbound call. Cloudflare’s siteverify endpoint is reliable, but any outbound HTTP call in a request path needs a timeout so a slow network hiccup doesn’t hang your login endpoint.

Step 8: Add Turnstile to a WordPress Form

If you’re running WordPress, you don’t need custom code at all. Cloudflare-maintained and third-party add-ons for Gravity Forms and other major form plugins ship native Turnstile fields — you paste in the sitekey and secret key from the plugin settings screen and the plugin handles both the widget render and the server-side verification call for you. Check your form plugin’s changelog for a Turnstile add-on before writing custom PHP; most mainstream WordPress form plugins added support during 2025 and have iterated on it through 2026, and Gravity Forms released its Cloudflare Turnstile add-on version 1.5.1 with widget bug fixes in October 2026. The same pattern holds outside the WordPress ecosystem — Drupal.org’s project page for its Turnstile module logged 7,704 site installations as of September 2026, a sign that CMS-level adoption of Turnstile has become routine well beyond any single platform.

Migrating an Existing reCAPTCHA Integration to Turnstile

If you’re moving off reCAPTCHA rather than starting fresh, the migration is more mechanical than architectural — you’re swapping one token-issuing widget and one verification endpoint for another, and the rest of your form logic barely changes. A few things worth doing deliberately rather than as an afterthought:

  • Run both widgets in parallel first. Add Turnstile alongside your existing reCAPTCHA integration on a low-traffic form and log both outcomes without blocking on Turnstile’s result yet. This gives you a real comparison of how often the two disagree before you cut over anything customer-facing.
  • Rename your environment variables deliberately. It’s tempting to reuse a variable literally named RECAPTCHA_SECRET for the new Turnstile secret key to save a step. Don’t — a future engineer debugging a bot-check failure will waste time assuming it’s still reCAPTCHA. Use TURNSTILE_SECRET_KEY consistently, matching the naming used throughout this tutorial.
  • Update your CSP and allowed-script-source headers. reCAPTCHA loads scripts from google.com and gstatic.com; Turnstile loads from challenges.cloudflare.com and, per the July 2026 changelog update, occasionally from the hagen and brunhild subdomains covered in the advanced tips section below. Old CSP rules pointing at Google’s domains can be removed once the cutover is complete.
  • Keep the reCAPTCHA fallback path for one release cycle. If your form logic supports it, keep the old verification code behind a feature flag for a week or two rather than deleting it immediately. Bot-detection cutovers are exactly the kind of change where a quiet rollback option is worth the extra week of code.

Because both services expose a nearly identical shape of verification response (a boolean success field plus an error-codes array), most teams find the actual code diff for this migration is under 30 lines per form — the work is almost entirely in testing and monitoring the cutover, not in writing new logic.

Step 9: Test With Cloudflare’s Dummy Sitekeys

Before pointing real traffic at your integration, use Cloudflare’s published dummy keys to test both the pass and fail paths without waiting for real bot traffic:

Dummy Sitekey PurposeBehaviorUse Case
Always passes (visible)Widget renders, always returns successHappy-path UI testing
Always passes (invisible)No UI, always returns successTesting invisible mode flows
Always blocksWidget renders, always returns failureTesting your error-handling UI
Forces an interactive challengeAlways shows the visible checkbox challengeTesting the managed-mode challenge screen

Cloudflare’s own documentation lists the exact dummy key strings under its testing reference — swap them in during CI runs so your test suite never depends on live bot-detection scoring, which is nondeterministic by design.

Step 10: Layer Cloudflare WAF Rate Limiting on Top

Turnstile stops the bulk of scripted bot traffic, but it is not a silver bullet — a determined attacker can route requests through paid CAPTCHA-solving services that specifically advertise Turnstile support, with per-1,000-solve pricing as low as $0.60 as of a September 2026 market survey. That means defense in depth still matters. If your zone runs on a Cloudflare plan that supports WAF rate limiting rules, add a rule that caps POST requests to your login and signup endpoints per IP per minute, independent of the Turnstile check:

# Cloudflare WAF rate limiting rule (via dashboard or API)
Expression: (http.request.uri.path eq "/login" and http.request.method eq "POST")
Characteristics: IP address
Period: 1 minute
Requests threshold: 10
Action: Block for 10 minutes

This combination — Turnstile for bot scoring, WAF rate limiting for volume abuse — closes the gap that either control leaves open on its own. Our Cloudflare WAF comparison covers what’s included at each plan tier if you’re deciding whether to upgrade for this step.

Step 11: Turn On Turnstile Events Logging

Cloudflare added a dedicated Turnstile Events dataset to Logpush in June 2026, with fields including ASN, action, browser name and version, client IP, country code, event type, hostname, OS name and version, sitekey, and timestamp. Push this to your existing log pipeline (S3, Splunk, Datadog, or whatever you already use) so you can see challenge and pass/fail rates broken out by country and browser instead of only the aggregate numbers on the dashboard.

On the free plan, in-dashboard analytics only look back 7 days; Enterprise extends that to 30 days. If you need longer retention for trend analysis, Logpush is the way to get it without upgrading the whole zone.

Step 12: Set a cdata and Action Naming Convention

If you plan to reuse one sitekey across login, signup, and checkout — which is fine on the free plan’s 20-widget cap — set a distinct data-action value per form (e.g., login, signup, checkout) and pass a cdata string that ties back to a request ID in your own logs. Both fields come back in the siteverify response, so you can slice your bot-rejection rate by funnel stage without deploying a widget per page.

Step 13: Roll Out and Monitor the First Week

Ship to a single high-value form first — login is the usual starting point since credential stuffing is the most automated attack most sites face. Watch your siteverify failure rate for the first week. A sudden spike usually means either a legitimate integration bug (see the troubleshooting section below) or that you’re now visibly blocking a bot campaign that was previously invisible in your logs. Both are useful signals; just don’t roll out to every form on day one without a baseline to compare against.

Cloudflare Turnstile vs reCAPTCHA vs hCaptcha

The three dominant bot-check widgets differ mainly on pricing model and how much friction they put in front of real users. Turnstile’s pitch is that it removes the puzzle entirely; reCAPTCHA v3 also runs invisibly but bills per assessment past its free cap; hCaptcha monetizes some of its free tier through the challenges themselves.

FeatureCloudflare TurnstileGoogle reCAPTCHAhCaptcha
Free tier limitUnlimited requests (Standard plan)10,000 assessments/month10,000 verifications/month
Paid entry priceEnterprise, contact sales only$8/month up to 100K, then $1/1,000Pro from $139/month + $0.99/1,000 past 100K
Requires visible puzzleRarely — managed mode escalates only when neededRarely with v3 (score-based)Sometimes (image challenges)
Requires Cloudflare DNS/proxyNoNoNo
Widgets per account (free)Up to 20Per project, no hard widget cap publishedPer site key, account-tier dependent
Hostnames per widget (free)10Multiple domains per keyMultiple domains per key
Analytics retention (free)7 daysVaries by console viewVaries by plan

The practical takeaway: if your form traffic is comfortably under 10,000 submissions a month, all three are effectively free and the choice comes down to which ecosystem you’re already in. Past that threshold, Turnstile’s flat-free Standard plan becomes the cheapest option by a wide margin, which is why high-traffic sites have been the fastest to migrate off reCAPTCHA in 2026.

5 Common Pitfalls When Setting Up Turnstile

Most Turnstile integration bugs fall into a small handful of repeatable categories, and nearly all of them show up in the first week of a rollout rather than months later. Catching these before you flip the switch on a production form saves a round of confused support tickets from real users getting blocked.

1. Skipping server-side verification entirely. The single most common mistake is rendering the widget and checking only that the hidden field is non-empty on the client. That check is trivial to fake with a browser devtools console. The token must be sent to siteverify from your backend, using the secret key, every time.

2. Reusing a token across multiple submit attempts. Each Turnstile token is single-use and tied to a short validity window. If a user double-clicks submit, or your frontend retries a failed request without re-running the widget, the second siteverify call will fail even though the user isn’t a bot. Re-render the widget after any failed submission.

3. Forgetting to register every hostname. Turnstile checks the widget’s configured hostname list against the page it’s loaded on. Staging domains, preview deployments (like Vercel’s per-branch URLs), and localhost all need to be added explicitly, or the widget silently fails to render there.

4. Treating Turnstile as a complete defense. As covered in Step 10, commercial solver services specifically advertise Turnstile-solving capability at sub-$3-per-thousand pricing. Pair Turnstile with rate limiting and behavioral monitoring on sensitive endpoints rather than relying on it alone.

5. Not handling the widget’s async load timing. In single-page apps, the Turnstile script can finish loading after your component has already rendered, especially on slow connections. Always check that window.turnstile exists before calling any of its JS API methods programmatically, or rely on the auto-render behavior shown in Step 4 instead of manual rendering.

Troubleshooting: 8 Common Turnstile Errors and Fixes

The error-codes array in a failed siteverify response is deliberately specific, which makes most Turnstile problems fast to diagnose once you know what each code actually means in practice rather than just reading its literal name. The table below covers the errors you’re most likely to hit during setup and in the first weeks of production traffic.

Error / SymptomLikely CauseFix
Widget never rendersHostname not registered on the widget configAdd the exact hostname (including subdomain) in the Turnstile dashboard
missing-input-secretSecret key omitted or empty in the siteverify POST bodyConfirm the environment variable is loaded before the request handler runs
invalid-input-secretWrong secret key, or a sitekey pasted where the secret key belongsRe-copy the secret key from the widget’s settings page, not the sitekey field
missing-input-responseForm is submitting before the widget finished generating a tokenDisable the submit button until data-callback fires
invalid-input-responseToken expired, already used, or malformedRe-render the widget on any failed submission before retrying
timeout-or-duplicateSame token submitted twice (double form submit)Debounce your submit handler and disable the button after first click
Widget renders but always fails in CILive sitekey used instead of a dummy testing keySwap in Cloudflare’s documented dummy keys for automated tests
Verification works locally, fails in productionProduction hostname wasn’t added to the widget, or secret key mismatch between environmentsConfirm each environment (dev/staging/prod) has its own widget and matching key pair

Advanced Tips for Production Deployments

Use Non-Interactive or Invisible Mode on Checkout Pages

Any added friction at checkout measurably hurts conversion. Switch high-value transactional forms to non-interactive or invisible mode so legitimate users almost never see a challenge, while Managed mode remains a reasonable default for lower-stakes forms like newsletter signups where a small amount of friction is an acceptable tradeoff for stronger bot filtering.

Watch the New Verification Hostnames

As of a July 2026 changelog update, Turnstile may issue requests to hagen.challenges.cloudflare.com or brunhild.challenges.cloudflare.com as part of its browser verification process, in addition to the primary challenges domain. If your site enforces a strict Content-Security-Policy, add both to your connect-src and frame-src directives, or verification will silently fail in browsers that respect CSP.

Consider Enterprise for Ephemeral IDs

Enterprise Turnstile (sold as part of Cloudflare’s Bot Management bundle) adds ephemeral IDs, which let you track a specific device across multiple sessions without cookies — useful for detecting account-takeover patterns where the same device farm rotates through stolen credentials. This is a meaningful upgrade over the Standard plan if you’re running a fintech or e-commerce platform with real fraud losses at stake, not just comment-spam annoyance.

Use Turnstile Spin’s Guided Setup for New Frameworks

Turnstile Spin, which reached general availability on August 10, 2026, bundles three setup paths directly into the widget-creation screen rather than leaving you to copy boilerplate from separate documentation pages. By September 2026, Cloudflare was describing Spin less as a wizard and more as an agent-mediated setup tool — one that reasons about your stack and writes both halves of the integration itself rather than just handing you boilerplate to paste in. If you’re starting a brand-new project rather than retrofitting one, walking through Spin’s guided flow first can save you from hand-typing the client-side snippet and the server-side siteverify call separately — it generates both halves from the same screen so the sitekey and secret key are pre-wired into the sample code it hands you.

Plan for Mobile App Traffic Separately

The web widget documented throughout this tutorial assumes a browser context with a DOM to render into. If you also need bot protection inside a native iOS or Android app — for example, an in-app signup form — that traffic doesn’t go through a browser at all, so the same JavaScript snippet won’t apply. Treat mobile app traffic as a separate integration surface: verify what’s currently supported for your platform directly in Cloudflare’s developer docs before assuming the web widget instructions transfer directly, since mobile SDK support has been an active area of Cloudflare’s 2026 roadmap and specifics can change between releases.

Score Requests Differently by Action

Not every form on your site carries the same risk. A newsletter signup being spammed is an annoyance; a checkout form being hit by carding bots is a direct financial loss. Rather than treating every siteverify failure identically, use the action field from the response to apply different consequences — log and soft-block on low-stakes forms, but hard-block the IP and trigger a manual review queue entry on high-stakes ones like checkout or password reset.

Complete Working Project: Turnstile-Protected Login Endpoint

Here’s the full, minimal Express server combining everything above — widget, server-side verification, and basic rate-limiting middleware — as a starting point you can drop into a new project.

// server.js
const express = require("express");
const rateLimit = require("express-rate-limit");
const app = express();

app.use(express.urlencoded({ extended: true }));
app.use(express.static("public"));

const loginLimiter = rateLimit({
  windowMs: 60 * 1000,
  max: 10,
  message: { error: "too-many-attempts" },
});

const TURNSTILE_SECRET = process.env.TURNSTILE_SECRET_KEY;
const VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";

async function verifyTurnstile(token, ip) {
  const res = await fetch(VERIFY_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ secret: TURNSTILE_SECRET, response: token, remoteip: ip }),
  });
  return res.json();
}

app.post("/login", loginLimiter, async (req, res) => {
  const token = req.body["cf-turnstile-response"];
  const { email, password } = req.body;
  const ip = req.headers["cf-connecting-ip"] || req.ip;

  if (!token) return res.status(400).json({ error: "missing-turnstile-token" });

  const outcome = await verifyTurnstile(token, ip);
  if (!outcome.success) {
    return res.status(403).json({ error: "bot-check-failed", codes: outcome["error-codes"] });
  }

  // Proceed to real credential check (hashed password comparison, etc.)
  return res.json({ status: "ok", email });
});

app.listen(3000, () => console.log("Server running on :3000"));

Pair this with the HTML form from Step 4, set TURNSTILE_SECRET_KEY in your environment, install express-rate-limit, and you have a login endpoint that requires a valid Turnstile pass and caps attempts at 10 per minute per IP before it ever touches your password comparison logic.

How Turnstile Fits Into a Broader Bot-Defense Stack

Turnstile solves one layer of a larger problem. Credential-stuffing and account-takeover attempts increasingly come from automated toolchains that also probe for other weaknesses, from exposed API endpoints to unpatched plugins. If you haven’t run a broader vulnerability scan against your own forms and endpoints recently, our OWASP ZAP setup guide covers a free way to test what an attacker sees before they find it themselves. And if bots are specifically targeting ticket or inventory drops rather than login forms, the mitigation techniques differ enough that it’s worth reading our roundup of bot protection solutions built for ticketing platforms.

Email-based account recovery flows are another common bot target, and hardening those starts with basic domain authentication. Our guide to setting up SPF, DKIM, and DMARC closes a related gap: bots that pair credential stuffing with spoofed password-reset emails. Sites in iGaming and similar high-fraud verticals face an even sharper version of this problem, covered in our guide to stopping credential stuffing in iGaming.

Reading the Turnstile Analytics Dashboard

Once traffic starts flowing through a widget, Cloudflare’s dashboard shows a breakdown of challenge outcomes without needing the Logpush pipeline from Step 11. The three numbers worth watching daily are the solve rate (the percentage of widget loads that end in a passing token), the challenge rate (how often Turnstile escalates from silent scoring to a visible interactive check), and the failure rate at your own siteverify call, which is a different number from Cloudflare’s dashboard entirely since it only counts tokens your backend actually received and validated.

A healthy production login form typically shows a challenge rate in the low single digits — most real users never see anything. If that number climbs sharply after a deploy, check whether you accidentally changed the widget mode, since switching from Invisible to Managed will raise the challenge rate on its own without any change in actual bot traffic. Conversely, a challenge rate near zero over a long period on a form that should be attracting bot attention (like a public signup page) is worth a second look — it can mean the widget isn’t loading for a meaningful share of visitors rather than that everyone is passing cleanly.

What Turnstile Does Not Protect Against

Turnstile scores the request that carries the token, not everything that happens afterward. It won’t stop a legitimate, logged-in user’s account from being abused after a credential-stuffing success elsewhere, it doesn’t inspect payloads for SQL injection or XSS, and it doesn’t replace authentication itself — a bot that solves the check still needs a valid password to log in. Treat it as the first filter in a pipeline, not the whole pipeline. Combining it with rate limiting (Step 10), a WAF ruleset, and monitoring via Logpush (Step 11) is what actually closes the loop for a production login or checkout flow.

It’s also worth being honest with stakeholders about what “stopping bots” means in practice. Turnstile raises the cost of automation meaningfully — free, naive scripts get blocked outright, and even semi-sophisticated headless-browser attacks get caught by the passive signal checks described earlier. What it does is shift the remaining attackers toward paid solver services, which changes the economics of an attack rather than eliminating it entirely. For most sites, that shift alone is enough to make automated abuse unprofitable at the volumes attackers were previously running for free. For a site handling real financial transactions, it’s one control among several, not a finish line.

Frequently Asked Questions

Is Cloudflare Turnstile really free?

Yes. The Standard plan is $0/month with no published request cap, up to 20 widgets per account, and 10 hostnames per widget as of September 2026. Enterprise pricing is quote-only and adds features like ephemeral IDs, more hostnames per widget, and longer analytics retention — none of which are required for a typical small-to-midsize site.

Do I need to move my domain to Cloudflare’s DNS to use Turnstile?

No. Turnstile works as a standalone widget regardless of who hosts your DNS or your application. It only requires that your registered hostnames match the domains where the widget is loaded.

What’s the difference between the sitekey and the secret key?

The sitekey is public and goes in your frontend JavaScript or HTML. The secret key is private and is used exclusively in server-side calls to the siteverify API. Never expose the secret key in client-side code, browser devtools, or a public repository.

Can bots bypass Turnstile?

Some can, at a cost. Commercial CAPTCHA-solving services advertise Turnstile support at roughly $0.60 to $2.89 per 1,000 solves as of a September 2026 market survey, meaning a sufficiently motivated attacker can pay to defeat it at scale. This is why Turnstile should be paired with rate limiting and monitoring rather than treated as a standalone defense.

How is Turnstile different from Cloudflare’s Bot Management product?

Turnstile is a widget you embed on specific forms to score individual submissions. Bot Management is a broader, zone-wide product that scores all traffic hitting your Cloudflare-proxied domain, including API traffic that never touches a form. Enterprise Turnstile ships bundled with a Bot Management subscription, but the free Standard Turnstile plan works independently of it.

Does Turnstile slow down page load?

The script tag should be loaded with async defer as shown in Step 4, which prevents it from blocking initial page render. In non-interactive or invisible mode, most users never see any visual delay at all since the risk scoring happens in the background.

Can I use one sitekey for multiple pages or forms?

Yes, as long as all the pages share hostnames registered on that widget. Use the data-action attribute to label which form generated a given token so your backend logs and Turnstile Events data can be filtered per funnel stage.

What happens if the siteverify API call fails or times out?

Treat any non-success response, including a network timeout, as a failed verification and block the request rather than defaulting to allow. Set a short timeout (5 seconds is generous) on the outbound HTTP call in your backend so a Cloudflare-side hiccup doesn’t hang your entire login endpoint, and log the failure so you can distinguish a real outage from a bot-driven spike.

Nadia Dubois

Nadia Dubois

AI & Innovation Editor

Nadia Dubois is the AI & Innovation Editor at Tech Insider, where she tracks the rapid evolution of artificial intelligence, from foundation models to real-world enterprise deployment. She previously covered AI and startups for La Tribune and contributed to MIT Technology Review's European coverage. Nadia specializes in generative AI, AI regulation, and the intersection of technology and European industrial policy. She holds a dual degree in Computational Linguistics and Journalism from Sciences Po Paris.

View all articles