CAPTCHA Solving
Surfsky comes with built-in CAPTCHA solving for all major types. It combines third-party services with our own internal solvers, and you can restrict it to internal solvers only.
Strong fingerprinting often prevents CAPTCHAs from appearing in the first place. When they do appear, you have two paths.
Prevention: Good fingerprinting means fewer CAPTCHAs. Solving: When a CAPTCHA appears, we solve it.
If you're seeing too many, check troubleshooting below or contact support.
Supported CAPTCHAs
- Google reCAPTCHA (v2, v3, Enterprise)
- Cloudflare Turnstile
- DataDome
- PerimeterX
- GeeTest
- Amazon WAF
- Tencent
- FaucetPay
- Imperva
- Prosopo
- Temu
- Yidun
- MTCaptcha
- BLS
- Click CAPTCHA
- Image CAPTCHA
- Text CAPTCHA
We add new types regularly. Contact us if you need one that isn't listed.
Two Solving Modes
Manual Mode
You control when to solve:
- Your code detects a CAPTCHA
- You call
Captcha.solve() - We solve it and return the result
- Best when you know where CAPTCHAs appear
Auto Mode
We handle everything:
- Call
Captcha.autoSolve()once - Browser monitors for CAPTCHAs
- Solves them automatically
- Fires events to keep you informed
- Best when CAPTCHA timing is unpredictable
Stable but still being improved. Test against your target sites before relying on it.
Quick Start
Setup
- Enable
anti_captchain your browser profile settings - Choose manual or auto mode based on your needs
- Run your automation
Code Examples
Manual Solving
- Python
- JavaScript
import asyncio
from playwright.async_api import async_playwright
async def solve_captcha_manually():
async with async_playwright() as p:
# Connect to your Surfsky browser
browser = await p.chromium.connect_over_cdp("ws://your-browser-url")
page = await browser.new_page()
# Create CDP session for captcha commands
client = await page.context.new_cdp_session(page)
# Navigate to page with CAPTCHA
await page.goto("https://example.com/login")
# Wait for CAPTCHA to appear (adjust selector as needed)
await page.wait_for_selector(".g-recaptcha", timeout=5000)
print("CAPTCHA detected, solving...")
# Solve with specific type
response = await client.send("Captcha.solve", {"type": "recaptcha"})
# Or let us auto-detect the type
# response = await client.send("Captcha.solve")
if response.get("status") == "success":
print("✓ CAPTCHA solved!")
# Continue with your automation
await page.click("#submit-button")
else:
print("✗ Failed to solve CAPTCHA")
# Run it
asyncio.run(solve_captcha_manually())
const { chromium } = require('playwright');
async function solveCaptchaManually() {
// Connect to your Surfsky browser
const browser = await chromium.connectOverCDP('ws://your-browser-url');
const context = await browser.newContext();
const page = await context.newPage();
// Create CDP session
const client = await context.newCDPSession(page);
// Navigate to page with CAPTCHA
await page.goto('https://example.com/login');
// Check if CAPTCHA exists
const captchaElement = await page.$('.g-recaptcha');
if (captchaElement) {
console.log('CAPTCHA detected, solving...');
// Solve with specific type
const response = await client.send('Captcha.solve', { type: 'recaptcha' });
// Or auto-detect type
// const response = await client.send('Captcha.solve');
if (response.status === 'success') {
console.log('✓ CAPTCHA solved!');
// Continue automation
await page.click('#submit-button');
} else {
console.log('✗ Failed to solve CAPTCHA');
}
}
await browser.close();
}
solveCaptchaManually().catch(console.error);
Auto Solving
- Python
- JavaScript
import asyncio
from playwright.async_api import async_playwright
async def solve_captcha_auto():
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp("ws://your-browser-url")
page = await browser.new_page()
client = await page.context.new_cdp_session(page)
# Start auto-solving for all CAPTCHA types
await client.send("Captcha.autoSolve")
# Or target specific type only
# await client.send("Captcha.autoSolve", {"type": "turnstile"})
print("Auto-solve activated, navigating...")
# Navigate - CAPTCHAs will be solved automatically
await page.goto("https://example.com/protected-page")
# Your automation continues normally
# Browser handles any CAPTCHAs in the background
asyncio.run(solve_captcha_auto())
const { chromium } = require('playwright');
async function solveCaptchaAuto() {
const browser = await chromium.connectOverCDP('ws://your-browser-url');
const context = await browser.newContext();
const page = await context.newPage();
const client = await context.newCDPSession(page);
// Enable auto-solving
await client.send('Captcha.autoSolve');
// Or for specific type only
// await client.send('Captcha.autoSolve', { type: 'turnstile' });
console.log('Auto-solve activated, navigating...');
// Navigate - CAPTCHAs solved automatically
await page.goto('https://example.com/protected-page');
// Continue automation normally
await page.fill('#password', 'password123');
await page.click('#login-button');
// Wait for success
await page.waitForSelector('.dashboard', { timeout: 30000 });
console.log('✓ Logged in successfully!');
await browser.close();
}
solveCaptchaAuto().catch(console.error);
With Event Listeners
- Python
- JavaScript
import asyncio
from contextlib import suppress
from playwright.async_api import async_playwright
async def solve_with_events():
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp("ws://your-browser-url")
page = await browser.new_page()
client = await page.context.new_cdp_session(page)
# Event to track solving completion
captcha_done = asyncio.Event()
results = {}
# Handle CAPTCHA events
async def handle_captcha_event(data):
event_type = data['type']
detail = data.get('detail', {})
print(f"[{event_type}] {detail}")
if event_type == "Captcha.solveStarted":
print(f"🔄 Solving {detail.get('type')} CAPTCHA...")
elif event_type == "Captcha.solveCompleted":
print(f"✓ CAPTCHA solved successfully!")
results['success'] = True
captcha_done.set()
elif event_type == "Captcha.solveFailed":
print(f"✗ Failed to solve: {detail.get('error')}")
results['success'] = False
captcha_done.set()
# Register event handler
await page.expose_function("captchaEventHandler", handle_captcha_event)
# Listen for CAPTCHA events
await page.evaluate("""
['solveStarted', 'solveCompleted', 'solveFailed'].forEach(eventName => {
window.addEventListener('Captcha.' + eventName, async (event) => {
await window.captchaEventHandler({
type: 'Captcha.' + eventName,
detail: event.detail || {}
});
});
});
""")
# Navigate to page
await page.goto("https://2captcha.com/demo/recaptcha-v2")
# Trigger manual solve
print("Triggering CAPTCHA solve...")
client.send("Captcha.solve", {"type": "recaptcha"})
# Wait for completion (with timeout)
with suppress(asyncio.TimeoutError):
await asyncio.wait_for(captcha_done.wait(), timeout=60)
if results.get('success'):
print("Proceeding with automation...")
# Continue your flow
else:
print("CAPTCHA solving failed, retrying...")
asyncio.run(solve_with_events())
const { chromium } = require('playwright');
async function solveWithEvents() {
const browser = await chromium.connectOverCDP('ws://your-browser-url');
const context = await browser.newContext();
const page = await context.newPage();
const client = await context.newCDPSession(page);
// Track solving status
let solvingComplete = false;
let solvingSuccess = false;
// Set up event listeners
await page.evaluateOnNewDocument(() => {
window.captchaEvents = [];
['solveStarted', 'solveCompleted', 'solveFailed'].forEach(eventName => {
window.addEventListener('Captcha.' + eventName, (event) => {
window.captchaEvents.push({
type: eventName,
detail: event.detail || {},
timestamp: Date.now()
});
console.log(`[CAPTCHA] ${eventName}:`, event.detail);
});
});
});
// Navigate to test page
await page.goto('https://2captcha.com/demo/recaptcha');
// Set up promise to wait for completion
const waitForSolve = page.waitForFunction(
() => {
const events = window.captchaEvents || [];
return events.some(e =>
e.type === 'solveCompleted' || e.type === 'solveFailed'
);
},
{ timeout: 60000 }
);
console.log('Triggering CAPTCHA solve...');
// Start solving (no need to await since we're listening for events)
client.send('Captcha.solve', { type: 'recaptcha' });
// Wait for completion event
try {
await waitForSolve;
} catch (error) {
console.log('Timeout waiting for CAPTCHA solve');
}
// Check results
const events = await page.evaluate(() => window.captchaEvents);
const completedEvent = events.find(e => e.type === 'solveCompleted');
const failedEvent = events.find(e => e.type === 'solveFailed');
if (completedEvent) {
console.log('✓ CAPTCHA solved successfully!');
// Continue with your automation
await page.click('#submit-button');
} else if (failedEvent) {
console.log('✗ CAPTCHA solving failed');
console.log('Error:', failedEvent?.detail);
} else {
console.log('⏱ CAPTCHA solving timed out');
}
await browser.close();
}
solveWithEvents().catch(console.error);
Best Practices
Choose the Right Mode
Use Manual Mode when:
- You know exactly where CAPTCHAs appear
- You need precise control over timing
- Testing specific CAPTCHA types
Use Auto Mode when:
- CAPTCHAs appear unpredictably
- Multiple CAPTCHAs on same page
- You want "set and forget" solving
Handle Timeouts Properly
# Good: Realistic timeouts with retry
try:
response = await client.send("Captcha.solve", {"type": "recaptcha"})
if response.get("status") != "success":
# Retry once
response = await client.send("Captcha.solve", {"type": "recaptcha"})
except TimeoutError:
print("CAPTCHA solving timed out")
Monitor Events
Always listen for events in production:
Captcha.solveStarted- Solving beganCaptcha.solveCompleted- Success!Captcha.solveFailed- Something went wrong
Troubleshooting
Too Many CAPTCHAs?
Quick Fixes:
- Better Proxies - Residential > Datacenter
- Slow Down - Add N second delays between actions
- Act Human - Use Human Emulation for natural behavior
- Reuse Profiles - Persistent profiles that passed CAPTCHAs have trusted cookies & higher limits
Use Human Emulation to Prevent CAPTCHAs:
Instead of standard Playwright/Puppeteer actions, use our Human Emulation CDP commands:
# ❌ Robotic - triggers CAPTCHAs
await page.click("#login")
# ✅ Human-like - avoids detection
client = await page.context.new_cdp_session(page)
await client.send("Human.click", {"selector": "#login"})
These commands simulate realistic mouse movement, typing speed, and scrolling, which reduces the rate at which sites flag the session as automated. See Human Emulation docs for the full API.
Common Issues
"Captcha solver not enabled"
- Enable
anti_captchain profile settings - Start browser after enabling
"No captcha detected"
- Check if page fully loaded
- Try manual mode with specific type
- Verify CAPTCHA is visible (not hidden)
Solving takes forever
- Normal for complex CAPTCHAs (up to 60s)
- Check your API credits
- Try different proxy
Third-party solvers failing?
- Use internal solvers only by setting
disable_external_providers: true:
{
"anti_captcha": {
"enabled": true,
"disable_external_providers": true
}
}
This disables third-party services and uses only our internal solving methods, which may provide better results for certain CAPTCHA implementations.
You can also toggle this at runtime via the Captcha.setExternalProvidersEnabled CDP command. See the API Reference below. Runtime toggling requires the profile to have been started with disable_external_providers: false. To start a profile with external providers loaded but initially off, set external_providers_initially_enabled: false.
API Reference
CDP Commands
Captcha.solve
Manually solve a CAPTCHA on the current page.
Parameters:
type(optional) - CAPTCHA type. Supported values:recaptcha- Google reCAPTCHA (v2, v3, Enterprise)turnstile- Cloudflare Turnstiledatadome- DataDomeperimeterx- PerimeterXgeetest- GeeTestbls- BLS CAPTCHAfuncaptcha- FunCaptchaclick- Click-based CAPTCHAsimage- Image CAPTCHAs
options(optional) - Solving options object:selector- CSS selector or [x, y] coordinates for click CAPTCHAsproxy- Proxy URL for captcha solving
timeout(optional) - Max time to wait in milliseconds
Returns:
status-success,failed, ortimeouttype- Detected CAPTCHA typesolution(for image CAPTCHAs) - Text solution
Captcha.autoSolve
Enable automatic CAPTCHA detection and solving.
Parameters:
type(optional) - Only solve specific CAPTCHA type (same values asCaptcha.solve)options(optional) - Solving options object:selector- CSS selector or [x, y] coordinates for click CAPTCHAsproxy- Proxy URL for captcha solving
Returns:
status-started
Captcha.setExternalProvidersEnabled
Toggle third-party CAPTCHA solving providers at runtime. When enabled: false, only internal solvers remain active.
The profile must have been started with disable_external_providers: false so that external providers are loaded; otherwise the call returns an error. To start a profile with external providers loaded but initially off, set external_providers_initially_enabled: false.
Parameters:
enabled(required) -trueto enable external providers,falseto disable them
Returns:
enabled- current state after the call
Example:
await client.send("Captcha.setExternalProvidersEnabled", {"enabled": True})
await client.send("Captcha.setExternalProvidersEnabled", {"enabled": False})
Events
All events are dispatched on the window object:
Captcha.detectionStarted- Looking for CAPTCHAsCaptcha.detectionCompleted- Found (or not found) CAPTCHACaptcha.solveStarted- Solving startedCaptcha.solveCompleted- Successfully solvedCaptcha.solveFailed- Solving failed
Need Help?
Questions? Issues? Contact us: