Playwright makes browser automation fast — until a captcha challenge
stops the run mid-flow. This quickstart wires Death By Captcha (DBC)
into a Playwright script with one repeatable pattern: the script reads
the challenge parameters from the page, requests a solution from the
DBC API, injects the token back into the page, and continues — the
site verifies it exactly as if the widget had been solved in a real
browser. No click emulation, no brittle waits on challenge dialogs.
Everything below runs against Google's own public reCAPTCHA demo, so
you can test the whole loop end to end before pointing it at your own
target. The calls are the ones from the official runnable examples —
nothing here is invented. Beyond the client's few methods (balance,
upload, poll) there is no extra SDK surface to learn: the whole
integration is one request-and-inject cycle you can read in a single
sitting and reuse everywhere the same challenge type shows up.
Why the token flow
reCAPTCHA v2 has two integration shapes: replaying the interactive
challenge inside your browser context, or the token flow used here.
The token flow solves the challenge on the provider's side and returns
a response token that is valid for one submission; your script injects
it and moves on. That is what makes it work headless and inside CI:
nothing in your browser has to mimic a human pointer, and the page
sees a normal solved widget.
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, plus Playwright
- Credentials in environment variables, never in the code:
export DBC_USERNAME=your_username
export DBC_PASSWORD=your_password
Install
Python:
pip install deathbycaptcha-official playwright
playwright install
Node.js:
npm install deathbycaptcha-lib playwright
The full flow in Python
import json
import os
import time
import deathbycaptcha
from playwright.sync_api import sync_playwright
DEMO_URL = "https://www.google.com/recaptcha/api2/demo"
client = deathbycaptcha.HttpClient(
os.environ["DBC_USERNAME"], os.environ["DBC_PASSWORD"]
)
def solve_recaptcha_v2(page_url, sitekey, timeout=120):
"""Request a reCAPTCHA v2 token (API type=4), poll until solved."""
if not client.get_balance():
raise SystemExit("no balance - top up before decoding")
upload = client.upload(
type=4,
token_params=json.dumps(
{"googlekey": sitekey, "pageurl": page_url}
),
)
cid = upload["captcha"]
deadline = time.time() + timeout
while time.time() < deadline:
time.sleep(0.5)
detail = client.get_captcha(cid)
if isinstance(detail, dict) and detail.get("text"):
return detail["text"]
raise TimeoutError("no solution; retry with backoff")
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto(DEMO_URL, timeout=60000)
# 1. read the challenge parameters from the page
sitekey = page.get_attribute("#recaptcha-demo", "data-sitekey")
# 2. request the solution token from DBC
token = solve_recaptcha_v2(DEMO_URL, sitekey)
# 3. inject the token and trigger the page's own callback
page.evaluate(
"""(t) => {
const el = document.getElementById('g-recaptcha-response');
if (!el) return;
el.style.display = 'block';
el.value = t;
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
const cfg = window.___grecaptcha_cfg;
if (!cfg || !cfg.clients) return;
for (const k of Object.keys(cfg.clients)) {
const c = cfg.clients[k];
for (const key of Object.keys(c)) {
const item = c[key];
if (item && typeof item.callback === 'function')
return item.callback(t);
if (item && item.W &&
typeof item.W.callback === 'function')
return item.W.callback(t);
}
}
}""",
token,
)
# 4. submit and wait for the page to report success
page.click("#recaptcha-demo-submit")
page.wait_for_selector(".recaptcha-success", timeout=15000)
print("solved")
browser.close()
Run it:
python playwright_dbc_quickstart.py
If the token is accepted, the script prints solved and exits 0. If
the challenge is not solved within the timeout it raises — a broken
integration fails loudly instead of hanging your pipeline. That
failure path is the runnable test: wire the script into CI as-is, or
drop the last three lines into a test runner and assert on the
success selector like any other page state.
What each step does:
-
get_balance()— confirm the account can pay for the job before submitting anything. -
upload(type=4, ...)— type 4 is the reCAPTCHA v2 token API. Thegooglekey(the widget's site key) andpageurl(the exact URL where the challenge loads, path included) identify the challenge; the client pollsget_captcha()until the token text arrives. - The token goes into the page's hidden response field and the page's own reCAPTCHA callback fires — from the site's point of view the widget was solved normally.
- The submit click plus the success selector close the loop.
The same flow in Node.js
const { HttpClient } = require('deathbycaptcha-lib');
const client = new HttpClient(
process.env.DBC_USERNAME, process.env.DBC_PASSWORD);
function solveRecaptchaV2(pageUrl, sitekey) {
return new Promise((resolve, reject) =>
client.decode(
{
extra: {
type: 4,
token_params: JSON.stringify(
{ googlekey: sitekey, pageurl: pageUrl }),
},
},
(solution) => solution && solution.text
? resolve(solution.text)
: reject(new Error('no solution yet - retry with backoff'))
));
}
The page-side steps — extract the sitekey, request the token, inject
it, trigger the callback — are identical to the Python version. The
official sample also checks the balance first, and the full runnable
script ships in the official Node.js client repository under
examples/playwright.
Beyond reCAPTCHA v2
The same loop covers the rest of the supported set. Turnstile is the
same call with a different type and payload name — type=12 with a
turnstile_params payload carrying sitekey and pageurl — and
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/.
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
Nothing in the script has to run standalone. In a real scraper or test
suite the pattern sits next to the action that gets blocked: navigate,
detect the challenge widget on the page, call the solve step, inject,
continue. Because the token is requested only when a challenge is
actually present, unblocked runs submit no jobs at all, and the
balance check doubles as a first failure gate for scheduled jobs.
Keep the solve function in one shared module and every Playwright
entrypoint in the project can call it without copy-paste.
Notes that save runs
- Keep the solve path and the submit path on the same network. The
token is verified from the IP that submits it, and a mismatch
between that address and the context the challenge was solved in is
the usual cause of rejected tokens. If your automation already goes
through a proxy, pass it in the same params payload (
proxy+proxytype) so both hops align. - Retry with backoff on an empty result. "No solution yet" is transient by design — the official examples retry instead of failing immediately.
- Set explicit timeouts at every hop: navigation, upload, polling, page waits. Silent stalls are how one flaky challenge turns into a hung pipeline.
Docs and sign-up
The same shape applies to the other runners: the integration pages
for Selenium and Puppeteer walk the same request-and-inject loop, and
the per-type FAQ documents every payload field the clients accept.
- API reference and client links: https://deathbycaptcha.com/api
- Playwright integration page: https://deathbycaptcha.com/playwright-captcha-solver
- Balance and pricing: https://deathbycaptcha.com/user-pay
- Create an account: https://deathbycaptcha.com/register
Top comments (0)