Cloudflare Turnstile bypass tactics in 2026
Cloudflare Turnstile bypass is one of the most-searched scraper topics in 2026 because Turnstile rolled out aggressively across mid-tier and enterprise Cloudflare customers between 2023 and 2025. Unlike reCAPTCHA, Turnstile usually shows nothing visible to the user, just a passive widget that scores the session and either passes or escalates. For scrapers, that means failure mode is a silent denial: the form submit returns the same page with an invalid-token error, and you have no clear signal of which fix to try first.
This guide covers how Turnstile actually works under the hood, what passive checks it runs, what challenge variants it escalates to, and the working bypass patterns in 2026. There is no magic single fix. The right approach depends on whether the site uses managed challenge mode, invisible mode, or non-interactive mode, and on whether you can use a third-party solver or need to render the widget in a real browser.
What Turnstile actually checks
Turnstile is Cloudflare’s CAPTCHA replacement, launched as a free service in 2023. It produces a token that the site verifies server-side via Cloudflare’s siteverify endpoint, similar to how reCAPTCHA works. Unlike reCAPTCHA, the user-facing widget is intentionally minimal: a small box that says “Verifying” and either passes or shows a checkbox.
Under the hood, Turnstile runs a series of passive and active checks:
- Browser fingerprint: TLS, HTTP/2, canvas, WebGL, audio (the same battery as Cloudflare Bot Management)
- JavaScript challenge: a minified script that exercises browser APIs in specific patterns
- Session history: cookies and localStorage entries from prior visits via Cloudflare-protected sites
- IP reputation: Cloudflare’s global view of the IP’s behavior
- Behavioral signals: mouse movement, scroll, focus events on the page
- Proof-of-work: a small computational challenge the browser solves before the token issues
When all checks pass, Turnstile silently issues a token. When some fail, it escalates to a managed challenge (interactive checkbox) or to a denial. The escalation logic is opaque from the outside.
For Cloudflare’s official documentation, see the Turnstile docs.
Three Turnstile modes
Site operators configure Turnstile in one of three modes:
| mode | UI shown | passes when |
|---|---|---|
| Managed | invisible, escalates if needed | passive checks pass; escalates to checkbox if not |
| Non-interactive | invisible, never escalates | passive checks pass; denies if not |
| Invisible | invisible, never escalates | passive checks pass; denies if not |
The difference between non-interactive and invisible is mostly UI: invisible has no visible widget at all, non-interactive shows a small “Protected by Cloudflare” indicator. Both behave identically for scrapers.
Managed mode is the most common in 2026 because it is the default. It is also the easiest to bypass because escalation to checkbox gives scrapers an opportunity to solve via third-party services. Non-interactive and invisible modes have no escalation path, so failure is final.
Bypass approach 1: pass passive checks with a real browser
If your scraper has clean TLS fingerprinting, clean canvas/WebGL/audio, and a clean residential or mobile proxy, you might pass Turnstile’s passive checks without any further action. The widget loads, runs its checks, issues a token, and your form submission goes through.
from patchright.async_api import async_playwright
async def submit_form_with_turnstile(url: str, proxy: dict):
async with async_playwright() as p:
browser = await p.chromium.launch(
headless=True,
proxy=proxy,
args=["--disable-blink-features=AutomationControlled"],
)
ctx = await browser.new_context(
viewport={"width": 1920, "height": 1080},
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
)
page = await ctx.new_page()
await page.goto(url, wait_until="networkidle")
# Wait for Turnstile to issue token (visible in iframe or as input value)
await page.wait_for_function(
"""() => {
const input = document.querySelector('[name="cf-turnstile-response"]');
return input && input.value && input.value.length > 100;
}""",
timeout=30000,
)
# Now fill and submit the form
await page.fill("input[name='email']", "test@example.com")
await page.fill("input[name='password']", "secret123")
await page.click("button[type='submit']")
await page.wait_for_load_state("networkidle")
return await page.content()
The wait_for_function block waits for the Turnstile token to appear in the hidden input. If it does within 30 seconds, you have a valid token and can submit. If not, the passive checks failed and you need to try a different approach.
For this to work, your scraping stack needs:
- patchright or rebrowser-playwright (handles canvas, WebGL, audio)
- Clean residential or mobile proxy (no datacenter)
- Real-Chrome User-Agent matching your TLS profile
- Some humanization on the page (mouse movement, scroll)
If you have all four, Turnstile passive often passes on the first try. If it does not, escalate.
Bypass approach 2: third-party Turnstile solvers
Several solver services accept Turnstile sitekeys and return tokens. The major ones in 2026:
| service | price per 1000 | success rate | response time |
|---|---|---|---|
| 2Captcha | $1.50 | 80-90% | 15-45s |
| AntiCaptcha | $1.30 | 80-90% | 15-45s |
| CapSolver | $0.80 | 85-95% | 5-20s |
| NopeCHA | $0.60 | 75-90% | 10-30s |
| ScraperAPI | bundled | varies | bundled |
These services run real browsers (or Cloudflare-friendly headless setups) on residential proxies, generate tokens, and return them via API. You inject the returned token into the form and submit.
import requests
import time
def solve_turnstile_with_capsolver(api_key: str, sitekey: str, page_url: str) -> str:
# Submit task
create = requests.post(
"https://api.capsolver.com/createTask",
json={
"clientKey": api_key,
"task": {
"type": "AntiTurnstileTaskProxyLess",
"websiteURL": page_url,
"websiteKey": sitekey,
},
},
).json()
task_id = create["taskId"]
# Poll for result
for _ in range(30):
time.sleep(2)
result = requests.post(
"https://api.capsolver.com/getTaskResult",
json={"clientKey": api_key, "taskId": task_id},
).json()
if result.get("status") == "ready":
return result["solution"]["token"]
raise TimeoutError("Solver timed out")
# Usage in scraper
sitekey = "0x4AAAAAAAB1c4ABCDEFG" # extract from page HTML
token = solve_turnstile_with_capsolver(API_KEY, sitekey, page_url)
# Inject into the page and submit
await page.evaluate(f"""
document.querySelector('[name="cf-turnstile-response"]').value = '{token}';
""")
await page.click("button[type='submit']")
The token is bound to a specific (sitekey, page URL, time window) tuple. It expires within 5 minutes. Use it immediately or get a fresh one.
Extracting the sitekey
To use a solver, you need the sitekey. It is in the page HTML, usually as a data-sitekey attribute on the Turnstile widget div:
<div class="cf-turnstile" data-sitekey="0x4AAAAAAAB1c4ABCDEFG"></div>
Or in the Turnstile JS init:
turnstile.render('#turnstile-widget', {
sitekey: '0x4AAAAAAAB1c4ABCDEFG',
callback: function(token) { /* ... */ },
});
Extract via Playwright:
sitekey = await page.evaluate("""
() => {
const el = document.querySelector('[data-sitekey]');
return el ? el.getAttribute('data-sitekey') : null;
}
""")
If the sitekey is not in a data attribute, look for it in script tags via regex:
import re
html = await page.content()
match = re.search(r"sitekey:\s*['\"]([0-9a-zA-Z]+)['\"]", html)
sitekey = match.group(1) if match else None
For some Cloudflare configurations, the sitekey is dynamically generated and only available after the page JavaScript runs. In that case, wait for the Turnstile widget to render before extracting.
Bypass approach 3: token harvesting from a stable browser
Some scrapers maintain a small pool of long-lived real browsers (residential VPNs or actual desktops) that solve Turnstiles on demand and return tokens to the scraper fleet. This is more cost-effective than per-token third-party solver fees if your volume is high enough.
# Conceptual sketch of a token-harvesting service
import asyncio
from playwright.async_api import async_playwright
class TurnstileHarvester:
def __init__(self):
self.tokens = {} # sitekey -> [token, ...]
self.browser = None
self.context = None
async def start(self):
self.playwright = await async_playwright().start()
self.browser = await self.playwright.chromium.launch(
headless=False, # real Chrome window
args=["--disable-blink-features=AutomationControlled"],
)
self.context = await self.browser.new_context()
async def harvest(self, sitekey: str, page_url: str, count: int = 10):
page = await self.context.new_page()
await page.goto(page_url)
for _ in range(count):
await page.wait_for_function("""() => {
const i = document.querySelector('[name="cf-turnstile-response"]');
return i && i.value && i.value.length > 100;
}""", timeout=30000)
token = await page.evaluate("""() =>
document.querySelector('[name="cf-turnstile-response"]').value
""")
self.tokens.setdefault(sitekey, []).append(token)
# Reset the widget to harvest another
await page.evaluate("turnstile.reset()")
await asyncio.sleep(2)
await page.close()
def get_token(self, sitekey: str) -> str:
if sitekey in self.tokens and self.tokens[sitekey]:
return self.tokens[sitekey].pop(0)
raise RuntimeError(f"No tokens for sitekey {sitekey}")
This is a maintained pattern at higher scale. For lower volumes, third-party solvers are simpler.
When tokens are not enough: the IP-binding case
Some Turnstile configurations bind the token to the issuing IP. A token harvested from one IP and submitted from another IP fails verification. You can detect this by harvesting and submitting through the same proxy.
# Always use the same proxy for token harvest and form submission
HARVEST_PROXY = "http://user:pass@residential-proxy.example.com:8080"
async def harvest_with_proxy(sitekey, page_url):
# Harvest with proxy
pass
async def submit_with_same_proxy(form_url, token):
# Submit with the SAME proxy
pass
Cloudflare does not document IP binding behavior, but observed failures often correlate with IP changes between harvest and submit. Use the same proxy throughout.
Comparison: bypass approaches
| approach | cost | difficulty | reliability | maintenance |
|---|---|---|---|---|
| pass passive with clean stack | very low | medium | medium | medium |
| third-party solver | $0.60-1.50/1000 | low | medium-high | very low |
| token harvesting from real browsers | high upfront | high | high | high |
| Browserbase managed | high per page | trivial | high | none |
Most teams in 2026 use a hybrid: try clean-stack first (zero marginal cost), fall back to a solver if the passive check fails. This keeps costs down for the easy cases and unblocks the hard ones.
For broader patterns on browser-driving in scraping, see scraping JavaScript-heavy SPAs with AI agents.
What changes when Cloudflare upgrades to “I’m Under Attack” mode
Cloudflare’s “Under Attack” mode is a separate (and more aggressive) protection layer that adds a JavaScript challenge before any page loads. The challenge solves a proof-of-work computation in JavaScript and issues a cf_clearance cookie. Without that cookie, every request returns a challenge page.
Bypassing Under Attack requires:
- A real or near-real JavaScript engine that can execute the challenge
- Time (the challenge intentionally takes 5-10 seconds)
- The resulting
cf_clearancecookie, used for all subsequent requests within the same session
Tools for this:
- cloudflare-scrape (Python): older, broken since 2023 for most challenges
- cloudscraper (Python): same lineage, semi-maintained
- FlareSolverr: Selenium-based proxy that solves challenges and exposes a REST API for scrapers
- patchright + Playwright: handles the challenge naturally because it runs full Chrome
For Under Attack mode, just use Playwright. Lighter-weight tools struggle.
Detection: how do you know what mode the site is in?
Inspect the response from the protected page:
| signal | indication |
|---|---|
| HTML contains Turnstile widget | regular Turnstile mode |
| HTML contains “Just a moment…” with cf-mitigated header | Under Attack JS challenge |
| HTTP 403 with cf-ray header but no challenge body | passive failure, no escalation |
Cookie cf_clearance set after challenge | successful challenge solve |
Cookie __cf_bm set | basic Cloudflare Bot Management cookie |
Adapt your bypass strategy to the observed mode. Trying solver-based bypass on Under Attack mode does not work because there is no Turnstile to solve, just a JavaScript challenge.
Operational checklist
For production scrapers facing Turnstile in 2026:
- Use patchright or rebrowser-playwright as default browser
- Verify TLS, canvas, WebGL, audio fingerprints align with real Chrome
- Use clean residential or mobile proxies (no datacenter)
- Add humanization (mouse movement, scroll, pauses) for high-value targets
- Have a third-party solver as fallback for managed-mode failures
- Reuse the same proxy for token harvest and form submission
- Monitor for Cloudflare config changes (mode shifts) on your targets
- Cache and reuse
cf_clearancecookies within their valid window - Log Turnstile success/failure rates per target to detect regressions
Common failure modes
- Token returned but form still fails: token may be IP-bound or expired. Check that you used the same IP and submitted within 5 minutes.
- Token never appears in input: passive checks failed. Improve your stack (cleaner proxy, better fingerprinting).
- Form fails with “Invalid Turnstile response”: check the parameter name. Some sites use
cf-turnstile-response, others use a custom name. Inspect the form to find what is sent. - Solver returns token but verification fails server-side: site may be using Turnstile Enterprise with custom verification, which requires
cdataparameter. Check the widget config fordata-cdata. - Cloudflare Under Attack appears mid-session: the site escalated. Switch to Playwright if not already; the JavaScript challenge needs a real engine.
For broader CAPTCHA strategies, see best CAPTCHA solving services 2026 ranked.
What about Turnstile Enterprise?
Cloudflare Turnstile Enterprise (2024 launch) adds:
- Custom challenge parameters (cdata)
- Pre-clearance integration (pre-solve before form submission)
- Action-specific tokens (login vs registration vs comment)
- Risk score visibility for site operators
For scrapers, the practical impact is that Enterprise sites pass cdata parameters to the widget that must be submitted with the token. Extract cdata from the widget config and pass it to your solver:
cdata = await page.evaluate("""
() => {
const el = document.querySelector('[data-cdata]');
return el ? el.getAttribute('data-cdata') : null;
}
""")
# Pass to solver
result = solve_with_cdata(api_key, sitekey, page_url, cdata)
Solvers that support Enterprise (CapSolver, 2Captcha) accept cdata as an optional parameter.
FAQ
Q: is Turnstile easier or harder to bypass than reCAPTCHA?
Easier in some ways (no image challenges), harder in others (more passive fingerprinting). For scrapers with clean stacks, Turnstile often passes silently while reCAPTCHA at least shows a challenge to interact with. Net-net, Turnstile bypass success rates with quality solvers are higher than reCAPTCHA v3 with same-quality solvers.
Q: do I need to solve every Turnstile or just on form submissions?
Only on actions that require the token. Reading content protected by Cloudflare Bot Management does not need a Turnstile solve, you just need clean TLS and proxy. Form submissions and certain API calls require the token.
Q: can I bypass Turnstile by spoofing the response cookie?
No. The token is verified server-side via Cloudflare’s siteverify, which validates against the issuing flow. Spoofed tokens fail verification.
Q: what is the success rate I should expect from third-party solvers?
80-95% depending on the solver and the difficulty of the target site. CapSolver and 2Captcha both publish rates, and your real-world rate depends on how aggressive Cloudflare’s config is for your specific target.
Q: how do I tell if my Turnstile bypass is working?
Track form submission success rate over time. If it stays above 90% with stable input, your bypass works. If it drops, Cloudflare changed its rules or your stack drifted.
Common pitfalls in production Turnstile bypass
The first failure mode that catches teams off guard is the __cf_bm cookie lifecycle. Cloudflare issues __cf_bm (Bot Management cookie) on the first request that passes initial scoring, and Turnstile’s internal logic checks for its presence before issuing a token. If your Playwright context starts fresh on every request and discards cookies, Turnstile sees a “first-touch” session with no __cf_bm and runs the full passive battery, which is more likely to fail. The fix is to persist context state across requests within the same proxy IP: use browser.new_context(storage_state=stored_state) to carry cookies forward, and only reset state when you rotate to a new proxy.
The second pitfall is the action parameter mismatch. Turnstile widgets configured with data-action="login" produce tokens scoped to that action. Some sites verify server-side that the token’s action matches the endpoint being called. If you harvest a token from a “search” widget on the homepage and submit it to the “/login” endpoint, server-side verification fails with “action mismatch.” Extract data-action alongside data-sitekey and pass both to your solver, or harvest tokens from the exact widget instance on the exact page where you intend to use them.
The third pitfall is the script.js version drift. Cloudflare ships Turnstile’s challenge JS at https://challenges.cloudflare.com/turnstile/v0/api.js. The script self-updates and changes its internal challenge logic on a roughly biweekly cadence. Solvers like CapSolver track these changes and update their solving infrastructure within hours of each Cloudflare push. If your scraper has a custom solver implementation (rather than a third-party API), expect to spend half a day every two weeks reverse-engineering the new challenge format. For most teams the math favors paying CapSolver $0.80 per 1000 tokens over maintaining an in-house solver.
Real-world example: hybrid harvest-plus-solver pattern
A scraper running against 12 Cloudflare-protected travel sites, each with a different Turnstile configuration, hit the wall trying to use a single bypass strategy. Sites A through D passed with patchright + clean residential IP (zero solver cost). Sites E through I needed CapSolver because their Turnstile config had cdata action binding. Sites J through L used Turnstile Enterprise with pre-clearance, which neither pure-passive nor solver-only handled.
The fix was a tiered router that classified each site by its Turnstile config and routed accordingly:
async def solve_turnstile(page, sitekey: str, page_url: str, config: dict) -> str:
# Tier 1: clean-stack passive
if not config.get("cdata") and not config.get("preclearance"):
try:
await page.wait_for_function(
"""() => {
const i = document.querySelector('[name="cf-turnstile-response"]');
return i && i.value && i.value.length > 100;
}""",
timeout=8000,
)
return await page.evaluate(
"""() => document.querySelector('[name="cf-turnstile-response"]').value"""
)
except Exception:
pass # fall through to solver
# Tier 2: third-party solver with cdata if present
if not config.get("preclearance"):
return solve_with_capsolver(
CAPSOLVER_KEY, sitekey, page_url, cdata=config.get("cdata")
)
# Tier 3: harvest from a maintained real-browser pool with same-IP submission
return await harvester.get_token(sitekey, page_url, config.get("action"))
After deployment, average cost per successful submit dropped from $1.20 (pure CapSolver) to $0.34 (mixed), and overall success rate rose from 78 percent to 94 percent. The lesson: Turnstile is not one problem, it is several distinct problems sharing a brand name. Classify your targets and route accordingly.
Wrapping up
Turnstile bypass in 2026 is mostly a game of clean fingerprints plus a fallback solver. The simple cases (clean stack, residential IP, properly humanized) pass passively. The hard cases need a third-party solver or token harvesting from real browsers. Match your investment to your targets, monitor success rates, and adapt as Cloudflare rolls out config changes. Pair this guide with DataDome vs PerimeterX vs Akamai bot management and TLS fingerprinting for the surrounding context, and browse the anti-bot-captcha category on DRT for related tactics.