Cloudflare Turnstile verifies visitors without showing a puzzle: no
image grid, no checkbox to click — the widget collects signals in the
background and hands the page a token when it trusts the session. That
design is what makes it strong, and it is also where an automated run
stalls: there is nothing to click, so there is nothing for a script to
replay. This guide wires Death By Captcha (DBC) into that gap with the
token flow: your script reads the widget's parameters from the page,
requests a solution from the DBC API, places the token where the widget
expects it, and the site verifies the session exactly as it would for a
browser that passed on its own.
Everything below runs against the public Turnstile demo that the
official samples point at, so you can test the loop end to end before
aiming it at your own target. The API calls are the ones from the
official client examples — nothing here is invented. The shape is the
same one the Playwright and Selenium integrations use: extract,
request, inject, submit, verify.
Why the token flow
Turnstile has one integration shape worth building on: the token. The
challenge is evaluated on the provider's side and comes back as a
single-submission response token; your script places it in the page and
continues. That is what keeps the flow headless and CI-safe — nothing
in your driver has to imitate a human pointer, because Turnstile never
asks for one.
The same shape repeats across the supported set — the payload fields
change, the loop does not.
What you need
- A DBC account — solutions are paid from your account balance, so check the balance before decoding: https://deathbycaptcha.com/register
- Python 3 or Node.js
- Credentials in environment variables, never in the code:
export DBC_USERNAME=your_username
export DBC_PASSWORD=your_password
Install
Python (the integration guide's line):
pip install deathbycaptcha-official
Node.js:
npm install deathbycaptcha-lib
The solve call
Turnstile is type=12 with a turnstile_params payload. The payload
carries:
-
sitekey— the widget's public key, read from the page -
pageurl— the exact URL the widget is rendered on -
proxyandproxytype— the same proxy your traffic uses, so the solve context matches the submit context
Aligning the proxy and the page URL with the request that submits the
token is the single biggest reliability lever: a token solved in one
context and submitted from another is the classic source of repeated
retries. The official Turnstile page makes the same point first — keep
the solve and submit flow synchronized, use bounded polling and a
retry policy, and validate in staging before scaling volume.
The full flow in Python
Adapted from the official runnable in the Python client repository
(examples/example.Turnstile.py):
import json
import os
import deathbycaptcha
USERNAME = os.getenv("DBC_USERNAME")
PASSWORD = os.getenv("DBC_PASSWORD")
PAGE_URL = "https://clifford.io/demo/cloudflare-turnstile"
SITEKEY = "0x4AAAAAAAGlwMzq_9z6S9Mh"
client = deathbycaptcha.HttpClient(USERNAME, PASSWORD)
def solve_turnstile(sitekey, page_url):
payload = {
"proxy": "http://user:password@127.0.0.1:1234",
"proxytype": "HTTP",
"sitekey": sitekey,
"pageurl": page_url,
}
result = client.decode(type=12, turnstile_params=json.dumps(payload))
return result.get("text") if result else None
# optional pre-flight: check the balance once, not per captcha
# client.get_balance()
token = solve_turnstile(SITEKEY, PAGE_URL)
if not token:
raise SystemExit("no solution yet - retry with backoff")
print(token)
The structure is the official sample's: build the payload, pass it to
decode with type=12, and read the token back from the text field
of the response. The socket client takes the same decode call if you
prefer the persistent transport.
The same flow in Node.js
Adapted from the official runnable in the Node.js client repository
(examples/example.Turnstile.js):
const dbc = require('deathbycaptcha-lib');
const USERNAME = process.env.DBC_USERNAME;
const PASSWORD = process.env.DBC_PASSWORD;
const PAGE_URL = 'https://clifford.io/demo/cloudflare-turnstile';
const SITEKEY = '0x4AAAAAAAGlwMzq_9z6S9Mh';
const client = new dbc.HttpClient(USERNAME, PASSWORD);
function solveTurnstile(sitekey, pageurl) {
const turnstile_params = JSON.stringify({
proxy: 'http://username:password@proxy.example:3128',
proxytype: 'HTTP',
sitekey: sitekey,
pageurl: pageurl,
});
return new Promise((resolve, reject) =>
client.decode(
{ extra: { type: 12, turnstile_params: turnstile_params } },
(captcha) =>
captcha && captcha.text
? resolve(captcha.text)
: reject(new Error('no solution yet - retry with backoff'))));
}
solveTurnstile(SITEKEY, PAGE_URL).then(console.log).catch(console.error);
The Node.js client takes the same two fields under an extra object,
and the token comes back on captcha.text. It is the same call shape
the Python client uses, so the two stay in lockstep.
Placing the token in the page
The token is not the end of the job — it has to land where the widget
expects it. Turnstile exposes the solved response through a hidden form
field named cf-turnstile-response and, on pages that read the token
in JavaScript, through the widget's own success callback. Your driver
does one of two things with the solved token:
- Pages that submit a form: write the token into the
cf-turnstile-responsefield so the normal submit carries it. - Pages that read the token from JavaScript: pass the token into the widget's success callback so the page continues on its own.
Keep this step synchronized with the solve call: the same proxy, the
same page URL, and no long gap between the solve finishing and the
token being submitted.
Beyond Turnstile
The same loop covers the rest of the supported set. reCAPTCHA v3,
GeeTest, DataDome, plus image and text captchas follow their own
documented payloads — each CAPTCHA type is routed to a model
specialized for that type. The official Python client ships a runnable
sample per type under examples/, and the Node.js client mirrors
them.
Both clients default to the encrypted HTTPS transport and also ship a
socket transport for high-throughput production runs; the method names
stay the same, so moving between them later is a constructor change,
not a rewrite.
On the library side, official clients cover Python, Node.js, C#, Java,
.NET, Go, C, C++ and Perl, and the browser extensions for Chrome/Brave
and Firefox cover the no-code path for manual sessions.
Fitting it into a bigger run
The official samples are built for pipelines, and the Turnstile page's
rollout guidance fits the same habits:
- Align proxy and verification context to reduce mismatches.
- Use bounded polling and a retry policy for predictable runs.
- Validate in staging before scaling request volume.
- Keep one code path for local and CI — only flags change, never the flow.
Notes that save runs
- Credentials stay in environment variables in both official samples.
- The balance call is a pre-flight, not part of the solve loop: check it once at pipeline start, not per captcha.
- The payload's
pageurlmust match the page the token is submitted from — that is the context sync the official page names first. - End with the honest branch: no solution -> retry with backoff. Wire that branch before production.
- Fail loudly on timeouts instead of letting a stalled challenge hang the pipeline.
Docs and sign-up
- API reference and client links: https://deathbycaptcha.com/api
- Turnstile solver page: https://deathbycaptcha.com/turnstile-solver
- Python API integration guide: https://deathbycaptcha.com/python-captcha-api
- Balance and pricing: https://deathbycaptcha.com/user-pay
- Create an account: https://deathbycaptcha.com/register
Top comments (0)