Skip to main content

CAPTCHA Solving

Browserless detects and solves CAPTCHAs programmatically using Chrome DevTools Protocol events and commands. Use stealth routes to prevent CAPTCHAs from appearing in the first place. This page covers what to do when they still appear.

How it works

The CAPTCHA system operates through Chrome DevTools Protocol (CDP) events and commands:

  • Automatic CAPTCHA solving: Add solveCaptchas=true to your connection URL to monitor and solve CAPTCHAs in real-time without manual CDP commands
  • Detection: The Browserless.captchaFound event fires automatically when a CAPTCHA is detected
  • Solving: The Browserless.solveCaptcha command programmatically solves detected CAPTCHAs

Automatic CAPTCHA Solving

Add solveCaptchas=true to your connection URL. Browserless then monitors the entire session for CAPTCHAs and solves them as they appear — including on later navigations, popups, and async-loaded forms. Listeners you attach once at the start of the session keep working across every page, so there's no need to re-attach after navigation.

You only need to wait for Browserless.captchaAutoSolved at points where your next action depends on a solved CAPTCHA — typically before submitting a form. The solved token is automatically injected into the page DOM, so you don't need to read or pass it yourself.

The example below starts on the 2captcha homepage (no CAPTCHA there), then clicks through to a reCAPTCHA v2 demo page. The CAPTCHA only appears after the second navigation — but because the listener was attached at the start of the session, it catches the auto-solve event without any re-attachment.

import puppeteer from "puppeteer-core";

const TOKEN = "YOUR_API_TOKEN_HERE";

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 listener survives the whole session.
const captchaSolved = new Promise((resolve) => {
cdp.on("Browserless.captchaAutoSolved", resolve);
});

// No CAPTCHA on the homepage.
await page.goto("https://2captcha.com/", { waitUntil: "networkidle0" });

// Clicking through navigates to the reCAPTCHA v2 demo — a CAPTCHA appears here.
await page.click(
"#order-captchas > table > tbody > tr:nth-child(2) span > a:nth-child(2)"
);

// Wait for autosolve to finish before submitting.
const { solved, token, time } = await captchaSolved;
console.log({ solved, token, time });

await page.click("._actionsItem_151cx_41");
await browser.close();

Across multiple navigations

Because solveCaptchas=true runs for the lifetime of the connection, a single cdp.on("Browserless.captchaAutoSolved", ...) listener handles every CAPTCHA encountered on every subsequent page in the session. Use cdp.on (not cdp.once) when you expect more than one CAPTCHA — there's no need to re-attach after navigations, popups, or reloads. Re-create the awaitable promise each time you need to gate the next action on a fresh solve.

Pages without a CAPTCHA

If you await the autosolve event on a page that turns out to have no CAPTCHA, the promise will never resolve. Wrap the wait in a timeout (for example, Promise.race against a setTimeout) so your script can continue when nothing fires.

CAPTCHA Detection

The Browserless.captchaFound CDP event fires when Browserless detects a CAPTCHA on the page. Set up a listener before navigating so you don't miss the event — captchaFound can fire during page.goto() (often before it resolves), so attaching afterwards will sometimes drop the event.

The event payload includes a status field that distinguishes the two modes:

  • With solveCaptchas=true on the connection URL, captchaFound fires with { status: "solving" } — Browserless is already handling it and a captchaAutoSolved event will follow.
  • In manual mode (without solveCaptchas=true), it fires with { status: "found" } — your code is expected to call Browserless.solveCaptcha to trigger solving.

Passive Detection with Event Listeners

Set up automatic CAPTCHA detection using CDP event listeners. The system monitors network traffic patterns and automatically emits events when CAPTCHA services are detected:

const cdp = await page.createCDPSession();
await new Promise((resolve) =>
cdp.on("Browserless.captchaFound", () => {
console.log("Found a captcha!");
return resolve();
})
);

Detection Mechanism

The system automatically detects CAPTCHAs by monitoring network requests and matching against known CAPTCHA service patterns. When a CAPTCHA is detected, the Browserless.captchaFound event is emitted to all active CDP sessions for that page.

Playwright and CDP

Playwright uses its own browser protocols by default. To use Browserless CDP events, connect over CDP and reuse the existing context and page instead of creating new ones. See the full example below.

Here are complete scripts demonstrating CAPTCHA detection and solving in context:

import puppeteer from "puppeteer-core";

const waitForCaptcha = (cdpSession) => {
return new Promise((resolve) =>
cdpSession.on("Browserless.captchaFound", resolve)
);
};

const browserWSEndpoint =
"wss://production-sfo.browserless.io/stealth?token=YOUR_API_TOKEN_HERE&timeout=300000";

try {
const browser = await puppeteer.connect({ browserWSEndpoint });

const page = await browser.newPage();
const cdp = await page.createCDPSession();

// Attach BEFORE navigation — captchaFound can fire during page.goto.
const captchaFound = waitForCaptcha(cdp);

await page.goto("https://www.google.com/recaptcha/api2/demo", {
waitUntil: "networkidle0",
});

await captchaFound;
console.log("Captcha found!");

const { solved, error } = await cdp.send("Browserless.solveCaptcha");
console.log({ solved, error });

// Continue...
await page.click("#recaptcha-demo-submit");
await browser.close();
} catch (e) {
console.error("There was a big error :(", e);
process.exit(1);
}

CAPTCHA Solving

Use this approach when you need programmatic control over when CAPTCHAs are solved. Set up a Browserless.captchaFound event listener, then call Browserless.solveCaptcha to trigger solving on demand.

Programmatic Solving

Once a CAPTCHA is detected, use the Browserless.solveCaptcha command to solve it:

const cdp = await page.createCDPSession();
const { solved, error } = await cdp.send("Browserless.solveCaptcha");
console.log({
solved,
error,
});
Playwright and CDP

Playwright uses its own browser protocols by default. Connect over CDP and reuse the existing context and page instead of creating new ones.

Here are complete scripts demonstrating CAPTCHA detection and solving:

import puppeteer from "puppeteer-core";

const waitForCaptcha = (cdpSession) => {
return new Promise((resolve) =>
cdpSession.on("Browserless.captchaFound", resolve)
);
};

const browserWSEndpoint =
"wss://production-sfo.browserless.io/stealth?token=YOUR_API_TOKEN_HERE&timeout=300000";

try {
const browser = await puppeteer.connect({ browserWSEndpoint });

const page = await browser.newPage();
const cdp = await page.createCDPSession();

// Attach BEFORE navigation — captchaFound can fire during page.goto.
const captchaFound = waitForCaptcha(cdp);

await page.goto("https://www.google.com/recaptcha/api2/demo", {
waitUntil: "networkidle0",
});

await captchaFound;
console.log("Captcha found!");

const { solved, error } = await cdp.send("Browserless.solveCaptcha");
console.log({ solved, error });

// Continue...
await page.click("#recaptcha-demo-submit");
await browser.close();
} catch (e) {
console.error("There was a big error :(", e);
process.exit(1);
}

Response Fields

The solveCaptcha response includes information about the solving attempt:

  • found: Whether a CAPTCHA was detected on the page
  • solved: Whether the CAPTCHA was successfully solved
  • time: Time taken to solve the CAPTCHA, in milliseconds
  • token: The solved CAPTCHA token, if available
  • error: Any errors during execution

Integration with Live Sessions

CAPTCHA solving integrates with Browserless live URL sessions for hybrid automation workflows. This lets a human step in when automated solving fails, providing a fallback for complex CAPTCHAs.

Performance Considerations

  • CAPTCHA solving can take several seconds to minutes. Adjust timeouts accordingly.
  • Each CAPTCHA solve attempt costs 10 units (see your account dashboard for unit details)
  • Use stealth features and residential proxies to reduce CAPTCHA frequency

Next Steps