TL;DR
- Solving runs inside the browser session you are already driving. Add
solveCaptchas=trueto a connection URL, or call thesolvemutation in BrowserQL. There is no second vendor to integrate and no token for your code to catch and re-inject. - Solving is the third layer, not the first. Stealth routes and a clean residential IP stop most challenges appearing at all, and a challenge that never appears is a solve you never pay for.
- 10 units per successful solve. Attempts that do not clear the challenge are not charged.
- Measured against production telemetry: reCAPTCHA ran 91.6% to 94.3% and Cloudflare 89.9% to 96.6% across the four weeks to 20 July 2026, and hCaptcha hit 99.4% for the week to 26 July. DataDome and Akamai are supported but not yet as consistent, so we do not publish rates for them.
- Four things it does not cover: third-party validation handshakes, form-embedded challenges in Smart Scrape, the standard route on hardened Cloudflare, and a solved checkbox that still looks unsolved.
CAPTCHA solving in Browserless runs inside the browser session you are already driving. There is no second vendor to integrate, no screenshot to ship to an external API, and no token for your code to catch and re-inject. You add one parameter to a connection URL, or one mutation to a BrowserQL query, and the session keeps moving.
This guide covers how that works, what it costs, what the measured success rates are, and the cases it does not cover. It is written for engineers evaluating whether Browserless can replace a standalone CAPTCHA vendor, and for teams already on Browserless who want to tune a flow that is failing.
Solving is the last layer, not the first
The most reliable way to handle a CAPTCHA is to not trigger one. Browserless treats solving as the fallback after two cheaper layers have done their work.
Layer one is fingerprint. The stealth routes apply fingerprint mitigations and entropy injection at the connection level, so the session does not read as automated in the first place. Use /stealth for BaaS connections or /stealth/bql for BrowserQL. There are also browser-specific variants, /chromium/stealth and /chrome/stealth, when a site checks for a genuine Chrome user agent.
Layer two is IP reputation. Fingerprint and IP are the two signals that decide whether a session gets challenged, and IP reputation is often the stronger of the two. Residential proxies route through real residential addresses; datacenter proxies are cheaper and faster but easier to detect. Score-based defenses like reCAPTCHA v3 lean heavily on IP reputation, which is why a clean fingerprint alone will not always keep a v3 score high enough to pass.
Layer three is solving. When a challenge appears anyway, the solving engine handles it. Every CAPTCHA that reaches this layer costs units, so the layers above are also a cost-control mechanism: stealth and residential proxies reduce how often you pay to solve.
The practical sequence for a protected site is to start on the stealth route, add a residential proxy if you are still blocked, and enable solving for what gets through. Turning everything on at once works, but it makes it harder to tell which layer a given site actually requires.
How solving works
Browserless detects and solves CAPTCHAs programmatically, driven through the Chrome DevTools Protocol. Detection watches the session for the network and DOM patterns that known challenge providers produce. When a challenge is found, the solving engine identifies the type and routes it to the appropriate solver, which includes third-party solving services. The resulting token or cookie is applied to the page for you.
From your code, the whole thing is one surface. You do not specify the challenge type, choose a solver, or handle the token. You either wait for an event or read a response field, then carry on with the next action.
That is the important architectural difference from a bolt-on service. A standalone CAPTCHA vendor sits outside the browser: your code detects the challenge, extracts the payload, sends it over an API, polls for a result, injects the token back into the page, and hopes the session that submits the token still looks like the session that loaded the page. Every one of those steps is a place for a fingerprint or cookie mismatch to appear. Solving inside the session removes the seam.
Three ways to invoke it
Automatic, for Puppeteer and Playwright. Add solveCaptchas=true to the connection URL. Browserless then monitors the entire session and solves challenges as they appear, including on later navigations, popups, and asynchronously loaded forms. The solved token is injected into the page DOM automatically.
import puppeteer from "puppeteer-core";
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://production-sfo.browserless.io/stealth?token=${TOKEN}&proxy=residential&proxyCountry=us&solveCaptchas=true&timeout=300000`,
});
const page = await browser.newPage();
const cdp = await page.createCDPSession();
// Attach before navigation. The listeners survive the whole session,
// so there is no need to re-attach after each page load.
let challenged = false;
cdp.on("Browserless.captchaFound", () => {
challenged = true;
});
const captchaSolved = new Promise((resolve) => {
cdp.on("Browserless.captchaAutoSolved", resolve);
});
await page.goto("https://protected.example.com/login", {
waitUntil: "networkidle0",
});
// A page that was never challenged emits no solve event, so race the
// event against a deadline rather than awaiting it unconditionally.
const outcome = await Promise.race([
captchaSolved,
new Promise((resolve) => setTimeout(() => resolve(null), 60_000)),
]);
if (challenged && !outcome?.solved) {
await browser.close();
throw new Error("Challenge detected but not solved, so not submitting.");
}
await page.click('button[type="submit"]');
await browser.close();
Three details matter here. Attach the listeners before navigating, or you will miss the event when a challenge solves immediately on load. Never await the solve event unconditionally: an unchallenged page emits nothing, so a bare await hangs until the session timeout kills it. And you only need to wait at points where the next action depends on a solved challenge, which is usually just before a form submit.
Manual, when you want control. The Browserless.captchaFound event fires on detection, and Browserless.solveCaptcha triggers a solve on demand. Use this when you want to decide whether a given challenge is worth solving, or when you need to branch on the result.
BrowserQL, in a single request. The solve mutation detects and solves whatever is on the page, with no type argument required. It handles challenges inside iframes and shadow DOMs.
mutation SolveAndSubmit {
goto(url: "https://protected.example.com/login") {
status
}
solve {
found
solved
time
}
click(selector: "button[type='submit']") {
time
}
}
Passing type is optional and shaves a few milliseconds off detection when you already know what is on the page. For custom image challenges that detection cannot classify, typically legacy sites showing a distorted-text image, solveImageCaptcha takes CSS selectors for the image and the input field instead.
The verify mutation is deprecated and will be removed. Use solve.
To avoid paying for solves you do not need, wrap the mutation in a conditional so it only runs when a challenge is actually present:
if(selector: "#g-recaptcha-response") {
solve {
found
solved
}
}
What is supported
The BrowserQL schema enumerates every challenge type the solver can be pointed at:
Cloudflare, reCAPTCHA, reCAPTCHA v3, GeeTest, hCaptcha, DataDome, Akamai Bot Manager, PerimeterX (HUMAN press-and-hold), FunCaptcha (Arkose Labs), Amazon WAF, Yandex SmartCaptcha, mtCaptcha, friendlyCaptcha, Lemin, Capy, plus the generic families: normal, slider, click, audio, text, number, and math challenges.
Detection is automatic across all of them, so the enum matters mainly when you want to skip detection or debug a misclassification.
Measured success rates
These are live figures from our own production telemetry, not a marketing number. They move week to week, because the challenge providers change and so does the mix of sites our customers point at.
reCAPTCHA family, weekly solve rate for the four weeks ending 20 July 2026: 91.8%, 92.9%, 91.6%, 94.3%.
Cloudflare, weekly solve rate for the same four weeks: 89.9%, 95.9%, 93.9%, 96.6%.
hCaptcha, weekly solve rate: 99.4% for the week ending 26 July 2026, with the following week tracking at 98.4% as of 30 July.
Two caveats worth stating plainly. First, these are recent weeks, and earlier in the same quarter reCAPTCHA had a two-week stretch well below those numbers before a fix landed. A published rate is a description of current performance, not a service guarantee. Second, DataDome and Akamai are supported and solved, but performance on them is not yet as consistent as on reCAPTCHA, hCaptcha, and Cloudflare. We are actively working on both. If your workload depends on either, talk to us about your specific targets rather than planning against a headline number.
Solve time and timeouts
Solve time depends on the challenge. Simple token challenges resolve in a few seconds. Interactive and image-based challenges take longer, and the documented planning range runs from roughly 15 seconds to a minute, with complex image sets able to run longer still.
Design for that. Three concrete settings:
- Raise the session timeout. The default is 30 seconds, which is not enough for a session that includes a solve.
timeout=300000gives you five minutes. - Raise navigation timeouts too. A
page.goto()on its default 30-second timeout will fail while a solve is in flight. - Prefer awaiting the CDP event over sleeping.
Browserless.captchaAutoSolvedfires when the solve actually finishes, which is both faster and more reliable than a fixed delay. On the BQL side,solveaccepts its owntimeout(default 30 seconds), and omitting it uses the default.
If you are driving Browserless from an AI agent, tell the agent explicitly that CAPTCHAs are handled by the infrastructure and it should wait rather than clicking on them. Without that instruction, agents reliably try to solve the challenge themselves, or report a block, before the automatic solve completes.
What it costs
Each successful CAPTCHA solve costs 10 units. Attempts that do not clear the challenge are not charged. That is on top of browser time, which bills at 1 unit per 30 seconds for every connection method, and on top of proxy traffic, which is 6 units per MB residential and 2 units per MB datacenter.
A worked example. A 90-second session on a residential proxy that transfers 3 MB and solves one challenge successfully costs 3 units of browser time, 18 units of proxy, and 10 units of solving: 31 units.
Prevention is still the cheapest optimization available. A challenge that never appears is a solve you never pay for, and stealth plus a clean IP is what stops it appearing.
One exception, worth knowing before you model costs. Billing tracks whether we produced a valid token or cookie, not whether you got through. If the solver returns solved: true and the target site rejects that token during submission, the solve still counts and still bills. Sites reject technically valid tokens for reasons that have nothing to do with the challenge, such as an expired form nonce or a session cookie mismatch. The guarantee is that failed solves are free, not that failed submissions are.
What CAPTCHA solving does not cover
Precision here is more useful than optimism, and these are the four limits that actually generate support tickets.
Third-party validation handshakes. Solving gets you a valid token inside the browser session. If the target site requires that token to be independently validated through its own backend callback, with specific headers, cookies, or a client certificate, that handshake is outside what CAPTCHA solving does. This is a real architectural boundary, not a bug queue item. Government and customs portals layered on top of a digital-certificate auth flow are the usual place it shows up.
Form-embedded challenges in Smart Scrape. The Smart Scrape API solves challenges that gate access to the page, such as a Cloudflare interstitial or a reCAPTCHA blocking page load. It does not solve a challenge attached to a form on the page, because Smart Scrape does not fill or submit forms. It returns the rendered HTML with the challenge untouched. For form submission behind a challenge, use BrowserQL with solve.
The standard route on hardened Cloudflare. Fixes and mitigations land on the stealth paths first. If part of your flow runs on the standard route rather than a stealth route, it can still be blocked on a site where the stealth route works. Check which route each step of your pipeline is using before reporting a failure.
A solved checkbox that does not look solved. After a reCAPTCHA solve, the checkbox often does not appear ticked, and the page can look unchanged. This is expected. The token is already injected. Click the submit button and proceed. A flow that waits for a visual state change will appear to hang while nothing is actually wrong.
When a solve fails
Escalate in this order.
- Confirm you are on a stealth route with a proxy. This is the single highest-yield fix. Move the failing step to
/stealth(or/stealth/bql) and addproxy=residential. A large share of reported solve failures are really detection failures: the session was flagged before the challenge, and the challenge it was served is a harder one as a result. - Try a different browser binary. The stealth binary has the most comprehensive fingerprint mitigations, but some sites specifically check for a genuine Chrome user agent.
/chrome/bqlwith a residential proxy is the next thing to try. - Check the response fields before assuming the solver failed.
found: falseusually means the challenge was not in the DOM whensolveran, which awaitForSelectorfixes.solved: falsewith anerrorstring is the solver telling you what went wrong. - Open a support ticket. If a specific target still fails on stealth plus residential with generous timeouts, that is worth investigating on our side rather than working around.
There is also a documented human-in-the-loop path: on a failed solve you can mint a live URL and let a person complete the challenge in a real browser view. It works, and for low-volume flows with a human nearby it is a reasonable safety net. It is not our first recommendation, because a live interactive stream introduces fingerprinting signals of its own. Fix the stealth and proxy configuration first.
Self-hosted
CAPTCHA solving works in self-hosted deployments. It is not on by default.
Two things gate it. The open-source images ship Puppeteer, Playwright, and the REST APIs; stealth and CAPTCHA solving are part of the licensed Enterprise image, activated by the KEY environment variable. And solving calls out to solver services, so a self-hosted container needs solver credentials configured, for example CAPSOLVER_API_KEY, rather than inheriting the cloud's.
Whether solving is included depends on your contract. Where it is, we configure the deployment with the required solver keys, and it behaves the same as the cloud. This is a proven setup, not a theoretical one. If you are evaluating self-hosted specifically to get solving inside your own network, raise it during the contract conversation so the keys are part of the deployment from day one.
For everything else about running Browserless yourself, see the self-hosted deployment guide.
Reference
CDP events
| Event | Fires when |
|---|---|
Browserless.captchaFound | A challenge is detected on the page |
Browserless.captchaAutoSolved | An automatic solve completes (requires solveCaptchas=true) |
CDP command
Browserless.solveCaptcha detects a challenge on the current page and attempts to solve it. Returns ok, captchaFound, solveAttempted, solved, and optionally token, message, and error.
BrowserQL mutations
solve(type, timeout, wait) and solveImageCaptcha(captchaSelector, inputSelector, timeout). Both return a CaptchaResponse: found, solved, time, token, error.
Connection parameters
| Parameter | Effect |
|---|---|
solveCaptchas=true | Enables automatic solving for the session |
proxy=residential | Routes through residential IPs; strongest single lever on challenge frequency |
proxyCountry, proxyState, proxyCity | Geographic targeting for the proxy |
timeout=300000 | Session timeout in ms; raise it for any flow that includes a solve |
integrations=browseruse | Bridges solve events to the Browser Use CAPTCHA watchdog (requires browser-use 0.12.0 or later) |
FAQ
Do I need a separate CAPTCHA vendor? No. Solving is part of the platform and bills from the same unit balance as everything else.
Which challenge types are supported? Cloudflare, the reCAPTCHA family including v3 and invisible, hCaptcha, DataDome, Akamai, PerimeterX, FunCaptcha, GeeTest, Amazon WAF, Yandex, mtCaptcha, friendlyCaptcha, Lemin, Capy, and generic image, slider, audio, text, and math challenges.
Does it work with my existing proxies? Yes. Third-party proxies passed through launch arguments work and do not consume Browserless proxy units. Only the built-in networks are metered.
Does solving work with Puppeteer and Playwright, or only BrowserQL?
Both. solveCaptchas=true works on any CDP connection. BrowserQL adds solve as a first-class step inside a single request.
How long should I set my timeouts? Session timeout of at least 300000 ms for any flow that may solve, and navigation timeouts well above the 30-second default. Await the CDP event rather than sleeping.
What happens if a solve fails?
You are told: solved: false with an error message. Move the step to a stealth route with a residential proxy first, then try a different browser binary, then open a ticket.
Do failed solves cost units?
No. Only a successful solve bills, at 10 units. If the command returns solved: false or errors out, you are not charged. The one exception is a valid token that the target site rejects afterwards, which counts as a solve on our side and bills.
Does it work self-hosted? Yes, on the licensed Enterprise image with solver credentials configured, subject to your contract.
Where to go next
The honest way to evaluate this is against your own targets, because success rate depends far more on which sites you are hitting than on any published average. Point a stealth session with a residential proxy at the pages that are actually blocking you and read the solved and error fields.
- Test it. The connection string in the automatic-solving example above is the whole setup. Add
solveCaptchas=trueto what you already have. - Get the syntax. The CAPTCHA solving pages in the BrowserQL and BaaS docs carry the full reference and working examples in five languages.
- Model the cost. Units are shared across browser time, proxies, and solving, so the pricing page plus the worked example above will get you close.
- Talk to us if you are self-hosting, if DataDome or Akamai are central to your workload, or if your flow involves a validation-callback handshake. Those three cases are worth a conversation rather than a trial-and-error week.