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=trueto your connection URL to monitor and solve CAPTCHAs in real-time without manual CDP commands - Detection: The
Browserless.captchaFoundevent fires automatically when a CAPTCHA is detected - Solving: The
Browserless.solveCaptchacommand programmatically solves detected CAPTCHAs
Automatic CAPTCHA Solving
Add solveCaptchas=true as a query parameter in your connection URL. Browserless monitors the session for CAPTCHA challenges and solves them in real-time, firing CDP events for programmatic monitoring. This approach requires no manual solveCaptcha calls.
- Puppeteer
- Playwright
import puppeteer from "puppeteer-core";
const TOKEN = "YOUR-API-TOKEN";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const waitForCaptchaResolved = (cdp, timeout = 180000) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(null);
}, timeout);
cdp.on("Browserless.captchaAutoSolved", resolve);
cdp.on("Browserless.captchaFound", (params) => {
console.log("Captcha found, type:", params.type, params.status);
});
});
};
(async () => {
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://production-sfo.browserless.io/stealth?token=${TOKEN}&solveCaptchas=true`,
});
const page = await browser.newPage();
const cdp = await page.target().createCDPSession();
await page.goto("https://www.google.com/recaptcha/api2/demo", {
waitUntil: "networkidle0",
});
if (await page.$(".g-recaptcha")) {
console.log("Captcha found in page, solving...");
const { token, found, solved, time } = await waitForCaptchaResolved(cdp);
console.log("CAPTCHA solved:");
console.log({ token, found, solved, time });
await page.click("[type='submit']");
await sleep(2500);
await page.screenshot({ path: "screenshot.png" });
}
await browser.close();
})();
- Javascript
- Python
- Java
- C#
import playwright from "playwright-core";
const TOKEN = "YOUR-API-TOKEN";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const waitForCaptchaResolved = (cdp, timeout = 180000) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(null);
}, timeout);
cdp.on("Browserless.captchaAutoSolved", resolve);
cdp.on("Browserless.captchaFound", (params) => {
console.log("Captcha found, type:", params.type, params.status);
});
});
};
(async () => {
const browser = await playwright.chromium.connectOverCDP(
`wss://production-sfo.browserless.io/stealth?token=${TOKEN}&solveCaptchas=true`
);
// Reuse the existing context and page
const context = browser.contexts()[0];
const page = context.pages()[0];
const cdp = await page.context().newCDPSession(page);
await page.goto("https://www.google.com/recaptcha/api2/demo", {
waitUntil: "networkidle",
});
const recaptcha = await page.$(".g-recaptcha");
if (recaptcha) {
console.log("Captcha found in page, solving...");
const result = await waitForCaptchaResolved(cdp);
console.log("CAPTCHA solved:", result);
await page.click("[type='submit']");
await sleep(2500);
await page.screenshot({ path: "screenshot.png" });
}
await browser.close();
})();
import asyncio
from playwright.async_api import async_playwright
TOKEN = "YOUR-API-TOKEN"
async def wait_for_captcha_resolved(cdp, timeout=180):
future = asyncio.Future()
def handle_solved(event):
if not future.done():
future.set_result(event)
def handle_found(event):
print(f"Captcha found, type: {event.get('type')}, status: {event.get('status')}")
cdp.on("Browserless.captchaAutoSolved", handle_solved)
cdp.on("Browserless.captchaFound", handle_found)
try:
return await asyncio.wait_for(future, timeout=timeout)
except asyncio.TimeoutError:
return None
async def main():
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp(
f"wss://production-sfo.browserless.io/stealth?token={TOKEN}&solveCaptchas=true"
)
# Reuse the existing context and page
context = browser.contexts[0]
page = context.pages[0]
cdp = await page.context.new_cdp_session(page)
await page.goto("https://www.google.com/recaptcha/api2/demo", wait_until="networkidle")
recaptcha = await page.query_selector(".g-recaptcha")
if recaptcha:
print("Captcha found in page, solving...")
result = await wait_for_captcha_resolved(cdp)
print("CAPTCHA solved:", result)
await page.click("[type='submit']")
await asyncio.sleep(2.5)
await page.screenshot(path="screenshot.png")
await browser.close()
asyncio.run(main())
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import java.nio.file.Paths;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class AutoCaptchaSolving {
public static void main(String[] args) throws Exception {
String TOKEN = "YOUR-API-TOKEN";
String endpoint = "wss://production-sfo.browserless.io/stealth?token=" + TOKEN + "&solveCaptchas=true";
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().connectOverCDP(endpoint);
// Reuse the existing context and page
BrowserContext context = browser.contexts().get(0);
Page page = context.pages().get(0);
CDPSession cdp = page.context().newCDPSession(page);
CompletableFuture<Void> captchaSolved = new CompletableFuture<>();
cdp.on("Browserless.captchaAutoSolved", event -> {
System.out.println("CAPTCHA solved: " + event);
captchaSolved.complete(null);
});
cdp.on("Browserless.captchaFound", event -> {
System.out.println("Captcha found: " + event);
});
page.navigate("https://www.google.com/recaptcha/api2/demo", new Page.NavigateOptions()
.setWaitUntil(LoadState.NETWORKIDLE));
if (page.querySelector(".g-recaptcha") != null) {
System.out.println("Captcha found in page, solving...");
captchaSolved.get(180, TimeUnit.SECONDS);
System.out.println("CAPTCHA solved!");
page.click("[type='submit']");
page.waitForTimeout(2500);
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("screenshot.png")));
}
browser.close();
}
}
}
using System;
using System.Threading.Tasks;
using Microsoft.Playwright;
class Program
{
static async Task Main(string[] args)
{
var TOKEN = "YOUR-API-TOKEN";
var endpoint = $"wss://production-sfo.browserless.io/stealth?token={TOKEN}&solveCaptchas=true";
var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.ConnectOverCDPAsync(endpoint);
// Use the first context and page
var context = browser.Contexts[0];
var page = context.Pages[0];
var cdp = await page.Context.NewCDPSessionAsync(page);
var captchaSolved = new TaskCompletionSource<bool>();
cdp.On("Browserless.captchaAutoSolved", _ =>
{
Console.WriteLine("CAPTCHA solved!");
captchaSolved.TrySetResult(true);
});
cdp.On("Browserless.captchaFound", _ =>
{
Console.WriteLine("Captcha found!");
});
await page.GotoAsync("https://www.google.com/recaptcha/api2/demo", new PageGotoOptions
{
WaitUntil = WaitUntilState.NetworkIdle
});
var recaptcha = await page.QuerySelectorAsync(".g-recaptcha");
if (recaptcha != null)
{
Console.WriteLine("Captcha found in page, solving...");
await captchaSolved.Task;
Console.WriteLine("CAPTCHA solved!");
await page.ClickAsync("[type='submit']");
await Task.Delay(2500);
await page.ScreenshotAsync(new PageScreenshotOptions { Path = "screenshot.png" });
}
await browser.CloseAsync();
}
}
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.
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:
- Puppeteer
- Playwright
const cdp = await page.createCDPSession();
await new Promise((resolve) =>
cdp.on("Browserless.captchaFound", () => {
console.log("Found a captcha!");
return resolve();
})
);
- Javascript
- Python
- Java
- C#
const cdp = await page.context().newCDPSession(page);
await new Promise((resolve) =>
cdp.on("Browserless.captchaFound", () => {
console.log("Found a captcha!");
return resolve();
})
);
cdp = await page.context.new_cdp_session(page)
async def handle_captcha_found(event):
print("Found a captcha!")
return
cdp.on("Browserless.captchaFound", handle_captcha_found)
# Wait for the event to resolve
await asyncio.Future()
CDPSession cdp = page.context().newCDPSession(page);
cdp.addListener("Browserless.captchaFound", event -> {
System.out.println("Found a captcha!");
// You can add additional handling logic here
});
var cdp = await page.Context.NewCDPSessionAsync(page);
cdp.On("Browserless.captchaFound", e =>
{
Console.WriteLine("Found a captcha!");
// Additional logic can be added here
});
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 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:
- Puppeteer
- Playwright
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();
await page.goto("https://www.google.com/recaptcha/api2/demo", {
waitUntil: "networkidle0",
});
await waitForCaptcha(cdp);
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);
}
- Javascript
- Python
- Java
- C#
import playwright from "playwright-core";
const waitForCaptcha = (cdpSession) => {
return new Promise((resolve) =>
cdpSession.on("Browserless.captchaFound", resolve)
);
};
const pwEndpoint = `wss://production-sfo.browserless.io/stealth?token=YOUR_API_TOKEN_HERE`;
try {
const browser = await playwright.chromium.connectOverCDP(pwEndpoint);
// 👇 Queue we're re-using the existing context and page
const context = browser.contexts()[0];
const page = context.pages()[0];
await page.goto("https://www.google.com/recaptcha/api2/demo", {
waitUntil: "networkidle0",
});
const cdp = await page.context().newCDPSession(page);
await waitForCaptcha(cdp);
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);
}
import asyncio
from playwright.async_api import async_playwright
async def wait_for_captcha(cdp_session):
# Wait for the "Browserless.captchaFound" event
future = asyncio.Future()
def handle_captcha_found(event):
print("Captcha found!")
future.set_result(event)
cdp_session.on("Browserless.captchaFound", handle_captcha_found)
return await future
async def main():
pw_endpoint = "wss://production-sfo.browserless.io/stealth?token=YOUR_API_TOKEN_HERE"
async with async_playwright() as p:
try:
# Connect to the browser
browser = await p.chromium.connect_over_cdp(pw_endpoint)
# Use the first context and page
context = browser.contexts[0]
page = context.pages[0]
# Navigate to the captcha demo page
await page.goto("https://www.google.com/recaptcha/api2/demo", wait_until="networkidle")
# Create a CDP session
cdp = await page.context.new_cdp_session(page)
# Wait for captcha to be found
await wait_for_captcha(cdp)
# Solve the captcha
result = await cdp.send("Browserless.solveCaptcha")
solved, error = result.get("solved"), result.get("error")
print({"solved": solved, "error": error})
# Continue after solving captcha
await page.click("#recaptcha-demo-submit")
await browser.close()
except Exception as e:
print("There was a big error :(", e)
asyncio.run(main())
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class PlaywrightCaptchaExample {
public static void main(String[] args) {
String pwEndpoint = "wss://production-sfo.browserless.io/stealth?token=YOUR_API_TOKEN_HERE";
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().connectOverCDP(pwEndpoint);
// Reuse the existing context and page
BrowserContext context = browser.contexts().get(0);
Page page = context.pages().get(0);
// Navigate to the captcha demo page
page.navigate("https://www.google.com/recaptcha/api2/demo", new Page.NavigateOptions()
.setWaitUntil(LoadState.NETWORKIDLE));
// Create a CDP session
CDPSession cdp = page.context().newCDPSession(page);
// Wait for captcha to be found
CompletableFuture<Void> captchaFound = new CompletableFuture<>();
cdp.on("Browserless.captchaFound", event -> {
System.out.println("Captcha found!");
captchaFound.complete(null);
});
captchaFound.get(); // Wait for the event
// Solve the captcha
Map<String, Object> result = cdp.send("Browserless.solveCaptcha");
System.out.println("Result: " + result);
// Continue after solving captcha
page.click("#recaptcha-demo-submit");
browser.close();
} catch (ExecutionException | InterruptedException e) {
System.err.println("There was a big error :(" + e.getMessage());
e.printStackTrace();
}
}
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Playwright;
class Program
{
static async Task Main(string[] args)
{
var pwEndpoint = "wss://production-sfo.browserless.io/stealth?token=YOUR_API_TOKEN_HERE";
try
{
var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.ConnectOverCDPAsync(pwEndpoint);
// Use the first context and page
var context = browser.Contexts[0];
var page = context.Pages[0];
// Navigate to the captcha demo page
await page.GotoAsync("https://www.google.com/recaptcha/api2/demo", new PageGotoOptions
{
WaitUntil = WaitUntilState.NetworkIdle
});
// Create a CDP session
var cdp = await page.Context.NewCDPSessionAsync(page);
// Wait for captcha to be found
var captchaFound = new TaskCompletionSource<bool>();
cdp.On("Browserless.captchaFound", _ =>
{
Console.WriteLine("Captcha found!");
captchaFound.TrySetResult(true);
});
await captchaFound.Task;
// Solve the captcha
var result = await cdp.SendAsync<Dictionary<string, object>>("Browserless.solveCaptcha");
Console.WriteLine($"Solved: {result["solved"]}, Error: {result["error"]}");
// Continue after solving captcha
await page.ClickAsync("#recaptcha-demo-submit");
await browser.CloseAsync();
}
catch (Exception e)
{
Console.WriteLine("There was a big error :(");
Console.WriteLine(e.Message);
}
}
}
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:
- Puppeteer
- Playwright
const cdp = await page.createCDPSession();
const { solved, error } = await cdp.send("Browserless.solveCaptcha");
console.log({
solved,
error,
});
- Javascript
- Python
- Java
- C#
const cdp = await page.context().newCDPSession(page);
const { solved, error } = await cdp.send("Browserless.solveCaptcha");
console.log({
solved,
error,
});
cdp = await page.context.new_cdp_session(page)
result = await cdp.send("Browserless.solveCaptcha")
solved = result.get("solved")
error = result.get("error")
print({
"solved": solved,
"error": error,
})
CDPSession cdp = page.context().newCDPSession(page);
Map<String, Object> result = cdp.send("Browserless.solveCaptcha");
Boolean solved = (Boolean) result.get("solved");
String error = (String) result.get("error");
System.out.println(Map.of(
"solved", solved,
"error", error
));
var cdp = await page.Context.NewCDPSessionAsync(page);
var result = await cdp.SendAsync<Dictionary<string, object>>("Browserless.solveCaptcha");
var solved = result.ContainsKey("solved") ? result["solved"] : null;
var error = result.ContainsKey("error") ? result["error"] : null;
Console.WriteLine(new {
solved,
error
});
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:
- Puppeteer
- Playwright
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();
await page.goto("https://www.google.com/recaptcha/api2/demo", {
waitUntil: "networkidle0",
});
await waitForCaptcha(cdp);
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);
}
- Javascript
- Python
- Java
- C#
import playwright from "playwright-core";
const waitForCaptcha = (cdpSession) => {
return new Promise((resolve) =>
cdpSession.on("Browserless.captchaFound", resolve)
);
};
const pwEndpoint = `wss://production-sfo.browserless.io/stealth?token=YOUR_API_TOKEN_HERE`;
try {
const browser = await playwright.chromium.connectOverCDP(pwEndpoint);
// 👇 Queue we're re-using the existing context and page
const context = browser.contexts()[0];
const page = context.pages()[0];
await page.goto("https://www.google.com/recaptcha/api2/demo", {
waitUntil: "networkidle0",
});
const cdp = await page.context().newCDPSession(page);
await waitForCaptcha(cdp);
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);
}
import asyncio
from playwright.async_api import async_playwright
async def wait_for_captcha(cdp_session):
# Wait for the "Browserless.captchaFound" event
future = asyncio.Future()
def handle_captcha_found(event):
print("Captcha found!")
future.set_result(event)
cdp_session.on("Browserless.captchaFound", handle_captcha_found)
return await future
async def main():
pw_endpoint = "wss://production-sfo.browserless.io/stealth?token=YOUR_API_TOKEN_HERE"
async with async_playwright() as p:
try:
# Connect to the browser
browser = await p.chromium.connect_over_cdp(pw_endpoint)
# Use the first context and page
context = browser.contexts[0]
page = context.pages[0]
# Navigate to the captcha demo page
await page.goto("https://www.google.com/recaptcha/api2/demo", wait_until="networkidle")
# Create a CDP session
cdp = await page.context.new_cdp_session(page)
# Wait for captcha to be found
await wait_for_captcha(cdp)
# Solve the captcha
result = await cdp.send("Browserless.solveCaptcha")
solved, error = result.get("solved"), result.get("error")
print({"solved": solved, "error": error})
# Continue after solving captcha
await page.click("#recaptcha-demo-submit")
await browser.close()
except Exception as e:
print("There was a big error :(", e)
asyncio.run(main())
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class PlaywrightCaptchaExample {
public static void main(String[] args) {
String pwEndpoint = "wss://production-sfo.browserless.io/stealth?token=YOUR_API_TOKEN_HERE";
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().connectOverCDP(pwEndpoint);
// Reuse the existing context and page
BrowserContext context = browser.contexts().get(0);
Page page = context.pages().get(0);
// Navigate to the captcha demo page
page.navigate("https://www.google.com/recaptcha/api2/demo", new Page.NavigateOptions()
.setWaitUntil(LoadState.NETWORKIDLE));
// Create a CDP session
CDPSession cdp = page.context().newCDPSession(page);
// Wait for captcha to be found
CompletableFuture<Void> captchaFound = new CompletableFuture<>();
cdp.on("Browserless.captchaFound", event -> {
System.out.println("Captcha found!");
captchaFound.complete(null);
});
captchaFound.get(); // Wait for the event
// Solve the captcha
Map<String, Object> result = cdp.send("Browserless.solveCaptcha");
System.out.println("Result: " + result);
// Continue after solving captcha
page.click("#recaptcha-demo-submit");
browser.close();
} catch (ExecutionException | InterruptedException e) {
System.err.println("There was a big error :(" + e.getMessage());
e.printStackTrace();
}
}
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Playwright;
class Program
{
static async Task Main(string[] args)
{
var pwEndpoint = "wss://production-sfo.browserless.io/stealth?token=YOUR_API_TOKEN_HERE";
try
{
var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.ConnectOverCDPAsync(pwEndpoint);
// Use the first context and page
var context = browser.Contexts[0];
var page = context.Pages[0];
// Navigate to the captcha demo page
await page.GotoAsync("https://www.google.com/recaptcha/api2/demo", new PageGotoOptions
{
WaitUntil = WaitUntilState.NetworkIdle
});
// Create a CDP session
var cdp = await page.Context.NewCDPSessionAsync(page);
// Wait for captcha to be found
var captchaFound = new TaskCompletionSource<bool>();
cdp.On("Browserless.captchaFound", _ =>
{
Console.WriteLine("Captcha found!");
captchaFound.TrySetResult(true);
});
await captchaFound.Task;
// Solve the captcha
var result = await cdp.SendAsync<Dictionary<string, object>>("Browserless.solveCaptcha");
Console.WriteLine($"Solved: {result["solved"]}, Error: {result["error"]}");
// Continue after solving captcha
await page.ClickAsync("#recaptcha-demo-submit");
await browser.CloseAsync();
}
catch (Exception e)
{
Console.WriteLine("There was a big error :(");
Console.WriteLine(e.Message);
}
}
}
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