Cloudflare Turnstile CAPTCHA: How to Solve and Bypass
Cloudflare Turnstile is Cloudflare’s CAPTCHA replacement, designed to verify human visitors without the friction of traditional CAPTCHAs. For regular users, it’s nearly invisible. For web scrapers, it’s a significant obstacle because it combines browser environment checks, behavioral analysis, and cryptographic challenges into a single verification step.
This guide covers how Turnstile works under the hood and the most effective methods to handle it in your scraping workflows.
What Is Cloudflare Turnstile?
Turnstile launched in 2022 as a free alternative to reCAPTCHA and hCAPTCHA. Unlike traditional CAPTCHAs that require users to solve visual puzzles, Turnstile runs a series of invisible browser challenges that verify the visitor is a real human using a real browser.
How Turnstile Differs from Traditional CAPTCHAs
| Feature | reCAPTCHA v2 | hCaptcha | Turnstile |
|---|---|---|---|
| Visual puzzle | Yes | Yes | Rarely |
| Browser checks | Basic | Moderate | Extensive |
| Privacy | Low (Google tracking) | Medium | High |
| User friction | High | High | Very Low |
| Bot difficulty | Medium | Medium | High |
Turnstile’s Verification Process
When a page loads with Turnstile, the following happens:
- Widget loads: A JavaScript file from
challenges.cloudflare.comis loaded - Environment profiling: Turnstile checks the browser environment (canvas, WebGL, audio context, etc.)
- Behavioral analysis: Mouse movements, timing patterns, and interaction signals are analyzed
- Proof of Work: The browser performs a lightweight computational challenge
- Token generation: If all checks pass, a
cf-turnstile-responsetoken is generated - Server verification: The token is sent to the server, which validates it with Cloudflare’s API
The critical piece for scrapers is the cf-turnstile-response token. This token must be included in form submissions or API requests for the server to accept them.
Identifying Turnstile on a Page
Turnstile widgets are embedded via a specific HTML pattern:
<!-- Turnstile widget -->
<div class="cf-turnstile"
data-sitekey="0x4AAAAAAXXXXXXXXXXXXXXXXX"
data-callback="onTurnstileSuccess">
</div>
<!-- Or explicit rendering -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>You can detect it programmatically:
import requests
from bs4 import BeautifulSoup
response = requests.get("https://target-site.com")
soup = BeautifulSoup(response.text, 'html.parser')
# Check for Turnstile widget
turnstile_div = soup.find('div', class_='cf-turnstile')
if turnstile_div:
sitekey = turnstile_div.get('data-sitekey')
print(f"Turnstile detected! Sitekey: {sitekey}")
# Check for Turnstile script
turnstile_scripts = soup.find_all('script', src=lambda s: s and 'challenges.cloudflare.com/turnstile' in s)
if turnstile_scripts:
print("Turnstile script found")Method 1: Browser Automation
The most reliable way to solve Turnstile is with a real browser. Since Turnstile checks the browser environment, a properly configured headless browser can pass the challenge naturally.
Using Undetected ChromeDriver
import undetected_chromedriver as uc
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
def solve_turnstile_with_uc():
options = uc.ChromeOptions()
options.add_argument("--window-size=1920,1080")
driver = uc.Chrome(options=options)
try:
driver.get("https://target-site.com/login")
# Wait for Turnstile to load and solve
time.sleep(5)
# Check if Turnstile iframe is present
iframes = driver.find_elements(By.CSS_SELECTOR, 'iframe[src*="challenges.cloudflare.com"]')
if iframes:
print("Turnstile iframe detected, waiting for auto-solve...")
# Turnstile usually solves within 2-5 seconds for real browsers
time.sleep(8)
# Extract the turnstile response token
token = driver.execute_script(
"return document.querySelector('[name=\"cf-turnstile-response\"]')?.value"
)
if token:
print(f"Turnstile token obtained: {token[:50]}...")
return token
else:
print("Token not found. Turnstile may not have been solved.")
return None
finally:
driver.quit()
token = solve_turnstile_with_uc()Using Playwright
const { chromium } = require('playwright');
async function solveTurnstile() {
const browser = await chromium.launch({
headless: false, // Turnstile checks for headless mode
args: ['--window-size=1920,1080']
});
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
viewport: { width: 1920, height: 1080 }
});
const page = await context.newPage();
await page.goto('https://target-site.com/login');
// Wait for Turnstile to appear and resolve
try {
await page.waitForFunction(() => {
const input = document.querySelector('[name="cf-turnstile-response"]');
return input && input.value.length > 0;
}, { timeout: 30000 });
const token = await page.evaluate(() => {
return document.querySelector('[name="cf-turnstile-response"]').value;
});
console.log(`Token: ${token.substring(0, 50)}...`);
return token;
} catch (e) {
console.log('Turnstile did not resolve in time');
return null;
} finally {
await browser.close();
}
}For deeper stealth configurations, see our Playwright Stealth guide and Puppeteer Stealth guide.
Method 2: CAPTCHA Solving Services
CAPTCHA solving services can solve Turnstile challenges, either through human solvers or specialized AI systems.
How It Works
- You send the sitekey and page URL to the service
- The service solves the challenge (either with a real browser or human worker)
- You receive the
cf-turnstile-responsetoken - You include the token in your request to the target site
Example with a Generic CAPTCHA Service API
import requests
import time
def solve_turnstile_with_service(sitekey, page_url, api_key):
# Step 1: Submit the challenge
create_response = requests.post(
"https://api.captchaservice.com/createTask",
json={
"clientKey": api_key,
"task": {
"type": "TurnstileTaskProxyless",
"websiteURL": page_url,
"websiteKey": sitekey,
}
}
)
task_id = create_response.json()["taskId"]
print(f"Task created: {task_id}")
# Step 2: Poll for result
for _ in range(30): # Max 30 attempts
time.sleep(3)
result_response = requests.post(
"https://api.captchaservice.com/getTaskResult",
json={
"clientKey": api_key,
"taskId": task_id
}
)
result = result_response.json()
if result["status"] == "ready":
token = result["solution"]["token"]
print(f"Solved! Token: {token[:50]}...")
return token
print(f"Status: {result['status']}, waiting...")
return None
# Usage
sitekey = "0x4AAAAAAXXXXXXXXXXXXXXXXX" # From the cf-turnstile div
page_url = "https://target-site.com/login"
api_key = "YOUR_API_KEY"
token = solve_turnstile_with_service(sitekey, page_url, api_key)Using the Token
Once you have the token, include it in your form submission or API request:
# Method A: Form submission
form_data = {
"username": "user@example.com",
"password": "password123",
"cf-turnstile-response": token
}
response = requests.post(
"https://target-site.com/login",
data=form_data,
headers=headers
)
# Method B: JSON API
json_data = {
"email": "user@example.com",
"cf-turnstile-response": token
}
response = requests.post(
"https://target-site.com/api/login",
json=json_data,
headers={**headers, "Content-Type": "application/json"}
)For a comparison of CAPTCHA solving providers, see our CAPTCHA solving services review.
Method 3: Token Harvesting
Token harvesting involves solving Turnstile in a real browser and extracting the token for use in automated requests.
import undetected_chromedriver as uc
from selenium.webdriver.common.by import By
import time
import queue
import threading
class TurnstileTokenHarvester:
def __init__(self, target_url, num_browsers=3):
self.target_url = target_url
self.num_browsers = num_browsers
self.token_queue = queue.Queue()
self.running = True
def harvest_loop(self, browser_id):
options = uc.ChromeOptions()
options.add_argument("--window-size=1920,1080")
driver = uc.Chrome(options=options)
while self.running:
try:
driver.get(self.target_url)
time.sleep(6)
token = driver.execute_script(
"return document.querySelector('[name=\"cf-turnstile-response\"]')?.value"
)
if token:
self.token_queue.put({
"token": token,
"timestamp": time.time(),
"browser": browser_id
})
print(f"Browser {browser_id}: Token harvested")
# Refresh page for next token
time.sleep(2)
except Exception as e:
print(f"Browser {browser_id} error: {e}")
time.sleep(5)
driver.quit()
def start(self):
threads = []
for i in range(self.num_browsers):
t = threading.Thread(target=self.harvest_loop, args=(i,))
t.daemon = True
t.start()
threads.append(t)
return threads
def get_token(self, timeout=30):
try:
entry = self.token_queue.get(timeout=timeout)
# Tokens expire, typically within 300 seconds
age = time.time() - entry["timestamp"]
if age > 280:
return self.get_token(timeout)
return entry["token"]
except queue.Empty:
return None
# Usage
harvester = TurnstileTokenHarvester("https://target-site.com/login")
harvester.start()
# Use harvested tokens in your scraper
for url in urls_to_scrape:
token = harvester.get_token()
if token:
response = requests.post(url, data={"cf-turnstile-response": token})Method 4: FlareSolverr for Turnstile Pages
FlareSolverr can handle Turnstile challenges since it uses a full browser internally:
import requests
def solve_turnstile_flaresolverr(url):
payload = {
"cmd": "request.get",
"url": url,
"maxTimeout": 60000
}
response = requests.post("http://localhost:8191/v1", json=payload)
result = response.json()
if result["status"] == "ok":
cookies = result["solution"]["cookies"]
user_agent = result["solution"]["userAgent"]
html = result["solution"]["response"]
return {
"cookies": {c["name"]: c["value"] for c in cookies},
"user_agent": user_agent,
"html": html
}
return NoneSee our FlareSolverr guide for setup instructions.
Turnstile Modes and Difficulty Levels
Turnstile has three modes that site owners can configure:
Managed Mode
The default mode that decides whether to show an interactive challenge or pass invisibly. Most scraping scenarios encounter this mode.
Non-Interactive Mode
Always runs invisibly in the background. Easier for automation since no click is needed, but still requires a real browser environment.
Invisible Mode
Completely invisible to users. Similar to non-interactive but designed for sites that don’t want any visual widget. The cf-turnstile-response is still required server-side.
Tips for Reliable Turnstile Solving
1. Avoid Pure Headless Mode
Turnstile actively detects headless browsers. Use headed mode or configure your headless browser to pass headless detection tests:
options = uc.ChromeOptions()
# Don't use --headless flag
# Use virtual display instead for servers
# pip install pyvirtualdisplay
from pyvirtualdisplay import Display
display = Display(visible=0, size=(1920, 1080))
display.start()2. Use Residential Proxies
Turnstile factors in IP reputation. Datacenter IPs receive extra scrutiny. Pair your browser automation with residential proxies for higher success rates.
options = uc.ChromeOptions()
options.add_argument("--proxy-server=http://user:pass@residential-proxy:port")3. Simulate Human Behavior
Turnstile checks for mouse movements and interaction patterns. Add realistic mouse movement to your automation:
from selenium.webdriver.common.action_chains import ActionChains
import random
def simulate_human(driver):
"""Add realistic mouse movements before Turnstile check."""
actions = ActionChains(driver)
# Random mouse movements
for _ in range(random.randint(3, 7)):
x = random.randint(100, 800)
y = random.randint(100, 600)
actions.move_by_offset(x, y)
actions.pause(random.uniform(0.1, 0.5))
actions.perform()4. Token Expiration
Turnstile tokens expire after approximately 300 seconds (5 minutes). Always use tokens immediately after obtaining them, and implement expiration checking in your token harvesting system.
Troubleshooting Common Issues
Token Obtained But Server Rejects It
The token may have expired, or the server validates additional parameters (like cookies or IP address). Ensure your automated request matches the environment where the token was generated.
Turnstile Loops (Challenge Keeps Reappearing)
This usually indicates failed browser environment checks. Try:
- Using a newer Chrome version
- Ensuring JavaScript execution environment is complete
- Checking that WebGL and Canvas are not blocked
Inconsistent Success Rates
Turnstile’s difficulty adapts based on traffic patterns. What works at 95% today might drop to 50% tomorrow. Build monitoring and fallback mechanisms:
def fetch_with_fallback(url, sitekey):
# Try browser automation first
token = solve_turnstile_with_uc()
if token:
return submit_with_token(url, token)
# Fallback to CAPTCHA service
token = solve_turnstile_with_service(sitekey, url, api_key)
if token:
return submit_with_token(url, token)
# Last resort: manual token harvesting
return NoneConclusion
Cloudflare Turnstile is one of the more challenging anti-bot systems to handle because it combines environmental checks, behavioral analysis, and cryptographic challenges. Browser automation with stealth plugins remains the most reliable approach, though CAPTCHA solving services provide a useful fallback.
The key is to maintain a real browser environment, use residential proxies, and build redundancy into your solving pipeline. As Turnstile evolves, staying current with the latest browser automation tools is essential.
For related guides, see our Cloudflare bypass overview, browser fingerprinting guide, and how websites detect bots.
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research
Related Reading
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research