You solved the Turnstile CAPTCHA and got a valid token, but the target site still returns 403 Forbidden. This guide covers every cause.
Why 403 After Valid Token
| Cause | Likelihood |
|---|---|
| Missing cf_clearance cookie | Very common |
| Token expired | Common |
| Wrong submission endpoint | Common |
| Missing request headers | Moderate |
| IP mismatch between solve and submit | Moderate |
| Cloudflare Challenge (not Turnstile) | Sometimes confused |
Cause 1: Missing cf_clearance Cookie
Turnstile sets cookies during validation. If you don't include these cookies in your subsequent request, Cloudflare blocks you.
import requests
session = requests.Session()
# Step 1: Load the page to get initial cookies
session.get("https://example.com")
# Step 2: Solve Turnstile
token = solve_turnstile(
api_key="YOUR_API_KEY",
sitekey="TURNSTILE_SITEKEY",
pageurl="https://example.com",
)
# Step 3: Submit token to the validation endpoint
# This sets cf_clearance cookie
resp = session.post("https://example.com/api/verify", data={
"cf-turnstile-response": token,
}, headers={
"Content-Type": "application/x-www-form-urlencoded",
"Origin": "https://example.com",
"Referer": "https://example.com/",
})
# Step 4: Now make your actual request WITH the session cookies
resp = session.get("https://example.com/protected-page")
print(resp.status_code) # Should be 200 now
Cause 2: Token Expired
Turnstile tokens last ~300 seconds, but use them immediately for best results.
import time
# Solve
start = time.time()
token = solve_turnstile(...)
solve_time = time.time() - start
# Check if token is still fresh
if solve_time > 240: # > 4 minutes is risky
print("Token may be too old, solving again...")
token = solve_turnstile(...)
# Submit immediately
submit_token(token)
Cause 3: Wrong Submission Method
Find how the site submits the Turnstile token:
# Some sites use a hidden form field
data = {
"cf-turnstile-response": token,
"username": "user",
"password": "pass",
}
# Some sites use a custom header
headers = {
"X-Turnstile-Token": token,
}
# Some sites use JSON body
json_data = {
"turnstileToken": token,
"email": "user@example.com",
}
How to find the correct field name:
- Open browser DevTools → Network tab
- Complete the Turnstile challenge manually
- Find the form submission request
- Look at the request body for the token field name
Cause 4: Missing Headers
Cloudflare checks request headers for consistency:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Origin": "https://example.com",
"Referer": "https://example.com/login",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "same-origin",
}
session.headers.update(headers)
Cause 5: Cloudflare Challenge vs Turnstile
Turnstile and Cloudflare Challenge are different systems:
| Feature | Turnstile | Cloudflare Challenge |
|---|---|---|
| Widget | Visible checkbox on page | Full-page challenge screen |
| CaptchaAI method | turnstile |
cloudflare_challenge |
| Token field | cf-turnstile-response |
N/A (cookie-based) |
If you see a full-page challenge, use method=cloudflare_challenge instead.
Complete Working Example
import requests
import time
import re
def solve_turnstile_and_access(target_url, api_key):
"""Complete flow: solve Turnstile and access protected page."""
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
})
# Load page, get cookies and sitekey
resp = session.get(target_url)
match = re.search(r'data-sitekey="([^"]+)"', resp.text)
if not match:
raise RuntimeError("Turnstile sitekey not found")
sitekey = match.group(1)
# Solve via CaptchaAI
submit_resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": api_key,
"method": "turnstile",
"sitekey": sitekey,
"pageurl": target_url,
"json": 1,
}, timeout=30)
task_id = submit_resp.json()["request"]
# Poll
for _ in range(12):
time.sleep(5)
poll = requests.get("https://ocr.captchaai.com/res.php", params={
"key": api_key, "action": "get",
"id": task_id, "json": 1,
}, timeout=15)
data = poll.json()
if data.get("status") == 1:
token = data["request"]
break
else:
raise TimeoutError("Solve timeout")
# Submit token using the same session
form_resp = session.post(target_url, data={
"cf-turnstile-response": token,
}, headers={
"Origin": f"https://{requests.utils.urlparse(target_url).netloc}",
"Referer": target_url,
})
return session, form_resp
# Usage
session, resp = solve_turnstile_and_access(
"https://example.com/login",
"YOUR_API_KEY",
)
# session now has valid cookies for subsequent requests
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| 403 despite valid token | Missing session cookies | Use same session for all requests |
| 403 on subsequent pages | cf_clearance not set | Token validation must return cookie |
| Works once, then 403 | Cookie expired | Re-solve for fresh cookie |
| Always 403 | Full-page challenge, not Turnstile | Use cloudflare_challenge method |
FAQ
How long does cf_clearance last?
Typically 30 minutes to 24 hours. If subsequent requests start failing, re-solve the Turnstile.
Do I need a proxy for Turnstile?
Often no — CaptchaAI's 100% success rate on Turnstile usually works without proxies. Add a proxy only if the site checks IP consistency.
Can I pass cf_clearance to another session?
Yes, but it's tied to the User-Agent and may be tied to the IP. Keep both consistent.
Related Guides
Fix 403 errors — solve Turnstile with CaptchaAI.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.