Cloudflare Turnstile
Solve Cloudflare Turnstile widgets. Required parameters, how to locate the site key, and how to use the returned token.
Cloudflare Turnstile
Turnstile is Cloudflare's lightweight bot check. It exposes a public site_key on the page and expects a token to be submitted back through a form or API call.
Request
| Field | Type | Required | Notes |
|---|---|---|---|
type | string | Yes | turnstile (or the alias cloudflare-turnstile). |
site_key | string | Yes | The data-sitekey value embedded in the page. [a-zA-Z0-9_-]{1,128}. |
url | string | Yes | Page URL — http/https, public host, max 2048 chars. |
action | string | No | Pass through if the site sets data-action. |
cdata | string | No | Pass through if the site sets data-cdata. |
proxy | string | No | Optional. Most Turnstile pages don't need one. |
user_agent | string | No | Use the same UA you'll replay the token with. |
Response
{
"success": true,
"type": "turnstile",
"token": "0.AkBr7...",
"cost": 0.0008
}Submit the token in the form field the site expects — usually cf-turnstile-response or the name of the input rendered next to the widget.
Finding the site key
Most Turnstile widgets render a div with data-sitekey:
<div class="cf-turnstile" data-sitekey="0x4AAAAAAAB..."></div>If you don't see it in the static HTML:
- Open DevTools, switch to Elements, and search for
cf-turnstileordata-sitekey. - If the widget is mounted by JavaScript, search the JS bundles for
sitekey:orturnstile.render(. - As a last resort, watch the Network tab and filter for
challenges.cloudflare.com— the site key appears in the URL of the iframe.
JS-rendered widgets
When turnstile.render() is called dynamically, the page may also pass action and cdata arguments. Capture those too — submitting a token solved without the matching action/cdata will be rejected.
Performance
| Metric | Typical value |
|---|---|
| Solve time | ~1 second |
| Token lifetime | ~300 seconds |
| Success rate | 99.9% |
Token lifetime is set by the target site — use it promptly.
Examples
import requests
r = requests.post(
"https://api.nslsolver.com/solve",
headers={"X-API-Key": "YOUR_KEY"},
json={
"type": "turnstile",
"site_key": "0x4AAAAAAAB...",
"url": "https://example.com/login",
"action": "login",
},
timeout=120,
)
token = r.json()["token"]
# Replay
requests.post(
"https://example.com/login",
data={
"username": "[email protected]",
"password": "password",
"cf-turnstile-response": token,
},
)const r = await fetch("https://api.nslsolver.com/solve", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.NSL_API_KEY,
},
body: JSON.stringify({
type: "turnstile",
site_key: "0x4AAAAAAAB...",
url: "https://example.com/login",
action: "login",
}),
signal: AbortSignal.timeout(120_000),
});
const { token } = await r.json();payload, _ := json.Marshal(map[string]string{
"type": "turnstile",
"site_key": "0x4AAAAAAAB...",
"url": "https://example.com/login",
"action": "login",
})
req, _ := http.NewRequest("POST", "https://api.nslsolver.com/solve", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", os.Getenv("NSL_API_KEY"))curl -X POST https://api.nslsolver.com/solve \
-H "Content-Type: application/json" \
-H "X-API-Key: $NSL_API_KEY" \
-d '{
"type": "turnstile",
"site_key": "0x4AAAAAAAB...",
"url": "https://example.com/login",
"action": "login"
}'Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Site rejects token immediately | Token expired | Use it within ~300s of receiving it. |
| Site rejects token after delay | Missing action or cdata | Inspect the widget for data-action/data-cdata and pass them. |
Repeated Solve failed (503) | Wrong site_key | Confirm against the live HTML — sites rotate keys occasionally. |
| Solves work but downstream login fails | UA mismatch | Pass the same user_agent you use on the form submission. |