CAPTCHA Solving
CAPTCHAs blocking your automation? We handle them for you. Surfsky comes with built-in solving that works with all major CAPTCHA types.
We use a combination of third-party services and our own internal CAPTCHA solvers to provide the best success rates. You can choose to use all available solvers or limit to internal ones only based on your needs.
Better yet - our fingerprinting often prevents CAPTCHAs from appearing at all. But when they do show up, you've got options.
Prevention First: Good fingerprinting = fewer CAPTCHAs
Smart Solving: When they appear, we solve them fast
Getting too many? Check troubleshooting below or contact support.
Supported CAPTCHAs
- Google reCAPTCHA (v2, v3, Enterprise)
- hCaptcha
- Cloudflare Turnstile
- DataDome
- PerimeterX
- GeeTest
- Amazon WAF
- Tencent
- FaucetPay
- Imperva
- Prosopo
- Temu
- Yidun
- MTCaptcha
- BLS
- Click CAPTCHA
- Image CAPTCHA
- Text CAPTCHA
Need something else? Let us know - we add new types regularly.
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
- Perfect for known CAPTCHA locations
Auto Mode
We handle everything:
- Call
Captcha.autoSolve()
once - Browser monitors for CAPTCHAs
- Solves them automatically
- Fires events to keep you informed
- Great for unpredictable CAPTCHAs
Working well but still improving. Test with your specific sites.
Quick Start
Setup
- Enable
anti_captcha
in 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('#username', '[email protected]');
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/hcaptcha');
// 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: 'hcaptcha' });
// 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 robotic Playwright/Puppeteer actions, use our Human Emulation CDP commands for natural behavior:
# ❌ Robotic - triggers CAPTCHAs
await page.click("#login")
await page.type("#username", "[email protected]")
# ✅ Human-like - avoids detection
client = await page.context.new_cdp_session(page)
await client.send("Human.click", {"selector": "#login"})
await client.send("Human.type", {"text": "[email protected]"})
Natural mouse movements, realistic typing speeds, and smooth scrolling make sites treat you like a real user. See Human Emulation docs for full API.
Common Issues
"Captcha solver not enabled"
- Enable
anti_captcha
in 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.
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)hcaptcha
- hCaptchaturnstile
- 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
, ortimeout
type
- 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
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: