When Cloudflare presents a challenge page, a complex token flow begins — from initial page parameters through JavaScript execution to the final cf_clearance cookie. Understanding these parameters helps you diagnose solve failures, debug automation flows, and choose the right solving approach.
Challenge page anatomy
A Cloudflare challenge page (HTTP 503) contains several key elements:
<!DOCTYPE html>
<html>
<head>
<title>Just a moment...</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div id="challenge-stage">
<div id="challenge-body-text">
Checking if the site connection is secure
</div>
<div id="challenge-spinner">
<!-- Loading spinner -->
</div>
</div>
<div id="challenge-form" style="display:none">
<form id="challenge-form" action="/..." method="POST">
<!-- Hidden parameters -->
<input type="hidden" name="md" value="...">
<input type="hidden" name="r" value="...">
</form>
</div>
<script src="/cdn-cgi/challenge-platform/h/g/orchestrate/chl_page/v1?ray=...">
</script>
</body>
</html>
Key parameters
In the challenge page
| Parameter | Name | Purpose |
|---|---|---|
ray |
Cloudflare Ray ID | Unique request identifier, ties challenge to original request |
md |
Challenge metadata | Encrypted challenge state |
r |
Response token | Computed answer (filled by JavaScript) |
chl_opt |
Challenge options | Configuration for the challenge script |
cRay |
Challenge ray | Secondary ray for challenge tracking |
cZone |
Challenge zone | Cloudflare zone ID |
cUPMDTk |
Timestamp | Challenge issuance time |
cHash |
Challenge hash | Integrity validation |
In the challenge script URL
/cdn-cgi/challenge-platform/h/g/orchestrate/chl_page/v1?ray=ABC123
| Component | Meaning |
|---|---|
/cdn-cgi/challenge-platform/ |
Cloudflare challenge infrastructure |
h/g/ |
Challenge version/variant |
orchestrate/ |
Challenge orchestration endpoint |
chl_page/v1 |
Challenge page version |
ray=ABC123 |
Request Ray ID binding |
In the JavaScript payload
The challenge script loads additional parameters:
// Extracted from obfuscated challenge script
window._cf_chl_opt = {
cvId: '2', // Challenge version
cType: 'managed', // Challenge type
cNounce: '...', // Cryptographic nonce
cRay: '...', // Challenge Ray ID
cHash: '...', // Challenge hash
cUPMDTk: '...', // Timestamp
cFPWv: 'g', // Fingerprint version
cTTimeMs: '4000', // Minimum wait time (ms)
cTplV: 5, // Template version
cLt: '...', // Challenge lifetime
cRq: {}, // Challenge request data
};
Token flow from challenge to clearance
Step-by-step flow
1. CLIENT → CLOUDFLARE EDGE
GET /protected-page
↓
2. CLOUDFLARE → CLIENT
HTTP 503 + Challenge page HTML
Sets: __cf_bm cookie (bot management tracking)
Contains: ray ID, challenge script URL
↓
3. CLIENT (browser)
Loads challenge script from /cdn-cgi/challenge-platform/...
↓
4. CHALLENGE SCRIPT EXECUTES:
a. Collects browser fingerprint:
- Canvas hash
- WebGL renderer
- Screen dimensions
- Installed fonts
- Timezone
- Language
b. Runs proof-of-work:
- Iterates hash computations
- Must find answer matching difficulty
c. Computes timing:
- Enforces minimum wait (cTTimeMs)
- Records actual timing
d. Generates response token:
- Combines fingerprint + PoW answer + timing
- Encrypts with challenge nonce
↓
5. CLIENT → CLOUDFLARE
POST /cdn-cgi/challenge-platform/h/g/flow/ov1/...
Body: { r: "encrypted_response", md: "metadata", ... }
↓
6. CLOUDFLARE validates:
- Proof-of-work answer correct?
- Timing within acceptable range?
- Fingerprint consistent with real browser?
- No replay (nonce check)?
↓
7. CLOUDFLARE → CLIENT
HTTP 200 + Set-Cookie: cf_clearance=...; path=/; expires=...
+ HTTP redirect to original URL
↓
8. CLIENT → CLOUDFLARE
GET /protected-page
Cookie: cf_clearance=...
↓
9. CLOUDFLARE → CLIENT
HTTP 200 + Protected content
Cookie timeline
Request 1: No cookies
→ Challenge page (503)
→ __cf_bm cookie set
Challenge solve:
→ cf_clearance cookie set
Request 2+: cf_clearance + __cf_bm
→ Content served (200)
After ~30 mins: cf_clearance expires
→ Next request triggers new challenge
Challenge cookies
| Cookie | Purpose | Lifetime | Scope |
|---|---|---|---|
__cf_bm |
Bot management session tracking | 30 min | Domain |
cf_clearance |
Challenge clearance proof | 15 min – 24 hr (configurable) | Domain |
__cflb |
Load balancer affinity | Session | Domain |
_cfuvid |
Unique visitor ID | Session | Domain |
cf_clearance cookie constraints
The cf_clearance cookie is bound to:
- IP address — Must come from the same IP that solved the challenge
- User-Agent — Must match the UA used during the challenge
- Domain — Valid only for the domain that issued it
# ❌ FAILS — IP mismatch
# Solve challenge from IP A, then use cf_clearance from IP B
# ❌ FAILS — UA mismatch
# Solve with Chrome UA, then send requests with Firefox UA
# ✅ WORKS — Same IP + Same UA
session = requests.Session()
session.headers["User-Agent"] = "Mozilla/5.0 ... Chrome/120.0.0.0"
# Use same session for solving and subsequent requests
Extracting challenge parameters
Python
import re
import requests
def extract_challenge_params(url):
"""Extract Cloudflare challenge page parameters."""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/120.0.0.0",
"Accept": "text/html,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
}
response = requests.get(url, headers=headers, timeout=15, allow_redirects=False)
html = response.text
params = {
"status_code": response.status_code,
"cf_ray": response.headers.get("cf-ray", ""),
"is_challenge": response.status_code == 503,
}
if not params["is_challenge"]:
return params
# Extract Ray ID from page
ray_match = re.search(r"ray['\"]?\s*[:=]\s*['\"]([a-f0-9]+)['\"]", html, re.I)
if ray_match:
params["ray_id"] = ray_match.group(1)
# Extract challenge script URL
script_match = re.search(
r'src=["\'](/cdn-cgi/challenge-platform/[^"\']+)["\']', html
)
if script_match:
params["challenge_script"] = script_match.group(1)
# Extract challenge options
opt_match = re.search(r"_cf_chl_opt\s*=\s*\{([^}]+)\}", html)
if opt_match:
opt_text = opt_match.group(1)
# Parse individual options
for key in ["cType", "cRay", "cHash", "cTTimeMs", "cvId", "cFPWv"]:
val_match = re.search(
rf"{key}\s*:\s*['\"]?([^'\"', }}]+)", opt_text
)
if val_match:
params[key] = val_match.group(1)
# Extract form parameters
md_match = re.search(r'name=["\']md["\']\s+value=["\']([^"\']+)["\']', html)
if md_match:
params["md"] = md_match.group(1)
# Extract cookies from response
params["cookies"] = {
name: value
for name, value in response.cookies.items()
}
return params
# Usage
params = extract_challenge_params("https://protected-site.com")
if params["is_challenge"]:
print(f"Challenge type: {params.get('cType', 'unknown')}")
print(f"Ray ID: {params.get('ray_id', params['cf_ray'])}")
print(f"Min wait: {params.get('cTTimeMs', '?')}ms")
print(f"Script: {params.get('challenge_script', 'not found')}")
Node.js
const axios = require("axios");
async function extractChallengeParams(url) {
const response = await axios.get(url, {
headers: {
"User-Agent": "Mozilla/5.0 Chrome/120.0.0.0",
Accept: "text/html,*/*;q=0.8",
},
validateStatus: () => true,
maxRedirects: 0,
});
const html = response.data;
const params = {
statusCode: response.status,
cfRay: response.headers["cf-ray"] || "",
isChallenge: response.status === 503,
};
if (!params.isChallenge) return params;
// Extract challenge script URL
const scriptMatch = html.match(
/src=["'](\/cdn-cgi\/challenge-platform\/[^"']+)["']/
);
if (scriptMatch) params.challengeScript = scriptMatch[1];
// Extract challenge type
const typeMatch = html.match(/cType\s*:\s*['"]?(\w+)/);
if (typeMatch) params.challengeType = typeMatch[1];
// Extract timing
const timeMatch = html.match(/cTTimeMs\s*:\s*['"]?(\d+)/);
if (timeMatch) params.minWaitMs = parseInt(timeMatch[1]);
return params;
}
extractChallengeParams("https://protected-site.com").then(console.log);
Solving with CaptchaAI
CaptchaAI handles the entire token flow internally — you don't need to extract challenge parameters manually:
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_cloudflare_challenge(target_url):
"""Solve Cloudflare challenge page — CaptchaAI handles token flow."""
submit = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "cloudflare_challenge",
"sitekey": "managed",
"pageurl": target_url,
"json": 1,
})
task_id = submit.json()["request"]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY,
"action": "get",
"id": task_id,
"json": 1,
}).json()
if result.get("status") == 1:
return result["request"]
raise TimeoutError("Challenge solve timed out")
# CaptchaAI handles the full flow:
# 1. Loads the challenge page
# 2. Executes JavaScript
# 3. Solves proof-of-work
# 4. Returns clearance token/cookies
token = solve_cloudflare_challenge("https://protected-site.com/login")
Debugging challenge failures
Common failure points
| Failure point | Symptom | Root cause |
|---|---|---|
| Challenge page doesn't load | Timeout or empty response | Network/proxy issue |
| Script fails to execute | Challenge loops | Missing JavaScript APIs |
| Proof-of-work fails | Infinite spinner | Computational timeout |
| Response rejected | Redirect back to challenge | Timing violation or fingerprint mismatch |
| cf_clearance not set | Cookie missing after solve | Response parsing error |
| cf_clearance rejected | 403 on subsequent request | IP or UA mismatch |
Debugging checklist
def debug_challenge_flow(url, cf_clearance_cookie=None, user_agent=None):
"""Debug the challenge solve flow step by step."""
ua = user_agent or (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/120.0.0.0"
)
steps = []
# Step 1: Initial request
response = requests.get(
url,
headers={"User-Agent": ua, "Accept": "text/html,*/*;q=0.8"},
timeout=15,
allow_redirects=False,
)
steps.append({
"step": "initial_request",
"status": response.status_code,
"is_challenge": response.status_code == 503,
"cf_ray": response.headers.get("cf-ray", ""),
})
# Step 2: Test with cf_clearance
if cf_clearance_cookie:
session = requests.Session()
session.cookies.set("cf_clearance", cf_clearance_cookie)
session.headers["User-Agent"] = ua
response2 = session.get(url, timeout=15, allow_redirects=False)
steps.append({
"step": "with_clearance",
"status": response2.status_code,
"passed": response2.status_code == 200,
})
if response2.status_code != 200:
steps.append({
"step": "diagnosis",
"issue": "cf_clearance rejected",
"possible_causes": [
"Cookie expired",
"IP address changed",
"User-Agent mismatch",
"Cookie from different domain",
],
})
return steps
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Challenge type is "managed" but solve fails | Challenge requires Turnstile, not JS challenge | Try turnstile method instead of cloudflare_challenge |
| cf_clearance works once, then rejected | IP rotation changed your IP | Pin IP for the clearance lifetime |
| "Just a moment..." page never resolves | JavaScript blocked or malformed | Use CaptchaAI instead of manual solving |
| Challenge reappears after every request | cf_clearance not being sent | Ensure cookies are persisted in session |
| Different challenge on different paths | Per-path WAF rules | Solve for each path separately |
Frequently asked questions
What's inside the cf_clearance cookie?
It's an encrypted token containing the solve proof, IP hash, UA hash, and expiration time. You can't decode or forge it — only Cloudflare's edge can validate it.
How long does cf_clearance last?
Site operators configure the lifetime. Default is 30 minutes. Range is 15 minutes to 24 hours. Enterprise customers can set custom values.
Can I solve the challenge without JavaScript execution?
No. The challenge requires JavaScript to compute the proof-of-work and browser fingerprint. CaptchaAI handles this internally using real browsers.
What happens if the Ray ID changes?
Each request gets a new Ray ID. The challenge is bound to the Ray ID at the time of the challenge page. Once cf_clearance is issued, the Ray ID is no longer relevant.
Can I reuse cf_clearance across different domains?
No. cf_clearance is domain-scoped. Each domain requires its own challenge solve and clearance cookie.
Summary
Cloudflare challenge pages contain Ray IDs, challenge scripts, options objects, and form parameters that drive a proof-of-work token flow. The flow produces a cf_clearance cookie bound to IP and User-Agent, valid for 15 minutes to 24 hours. With CaptchaAI, you don't need to parse these parameters manually — the solver handles the entire flow. For debugging, understanding the parameters helps identify where the flow breaks.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.