A single CAPTCHA-solving path is a single point of failure. Fallback chains cascade through alternative strategies when the primary approach fails — different proxies, different solver methods, or degraded modes that keep your pipeline running.
Fallback chain architecture
Primary Path Fallback 1 Fallback 2 Degraded Mode
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Proxy Pool A │──▶ │ Proxy Pool B │──▶ │ No Proxy │──▶ │ Skip + Log │
│ + reCAPTCHA │ │ + reCAPTCHA │ │ + reCAPTCHA │ │ │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
✗ fail ✗ fail ✗ fail ✓ continue
Each level attempts the solve with different parameters. If all levels fail, the task enters a degraded mode (skip, queue for later, or alert).
Fallback levels
Level 1: Proxy rotation
Same solver method, different proxy:
| Attempt | Proxy | Expected outcome |
|---|---|---|
| 1 | Residential Pool A | High success rate |
| 2 | Residential Pool B | Alternate IP range |
| 3 | Datacenter proxy | Lower rate, but fast |
Level 2: Method change
Switch the solving approach:
| Original | Fallback | When |
|---|---|---|
| reCAPTCHA v2 (with proxy) | reCAPTCHA v2 (no proxy) | Proxy pool exhausted |
| Cloudflare Challenge | Cloudflare Turnstile | Challenge method unavailable |
| Standard solve | Image OCR (screenshot) | Token method fails |
Level 3: Degraded mode
When all solving paths fail:
- Skip and log — continue pipeline, record the failure
- Queue for manual review — hold the task for later
- Alert and pause — stop the pipeline, notify operator
Python implementation
import requests
import time
from dataclasses import dataclass, field
from typing import List, Optional, Callable
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
@dataclass
class FallbackStep:
name: str
method: str
params: dict
proxy: Optional[str] = None
proxytype: Optional[str] = None
max_retries: int = 1
timeout: int = 120
@dataclass
class FallbackChain:
steps: List[FallbackStep]
on_degraded: Optional[Callable] = None
class FallbackSolver:
def __init__(self, api_key: str):
self.api_key = api_key
def solve(self, chain: FallbackChain) -> Optional[str]:
errors = []
for step in chain.steps:
print(f"Trying: {step.name}")
for attempt in range(step.max_retries + 1):
try:
token = self._attempt(step)
if token:
print(f"Solved via: {step.name}")
return token
except Exception as e:
errors.append(f"{step.name} (attempt {attempt + 1}): {e}")
print(f" Failed: {e}")
# All steps exhausted — degraded mode
print(f"All fallbacks failed. Errors: {errors}")
if chain.on_degraded:
chain.on_degraded(errors)
return None
def _attempt(self, step: FallbackStep) -> Optional[str]:
data = {
"key": self.api_key,
"method": step.method,
"json": 1,
**step.params
}
if step.proxy:
data["proxy"] = step.proxy
data["proxytype"] = step.proxytype
resp = requests.post(SUBMIT_URL, data=data, timeout=15)
result = resp.json()
if result.get("status") != 1:
error = result.get("error_text", result.get("request", "UNKNOWN"))
raise Exception(error)
task_id = result["request"]
return self._poll(task_id, step.timeout)
def _poll(self, task_id: str, max_wait: int) -> Optional[str]:
elapsed = 0
while elapsed < max_wait:
time.sleep(5)
elapsed += 5
resp = requests.get(RESULT_URL, params={
"key": self.api_key,
"action": "get",
"id": task_id,
"json": 1
}, timeout=10)
result = resp.json()
if result.get("status") == 1:
return result["request"]
if result.get("request") == "CAPCHA_NOT_READY":
continue
error = result.get("error_text", result.get("request", ""))
raise Exception(error)
raise Exception(f"Timeout after {max_wait}s")
# Define fallback chain
solver = FallbackSolver(api_key="YOUR_API_KEY")
chain = FallbackChain(
steps=[
FallbackStep(
name="Residential Proxy A",
method="userrecaptcha",
params={"googlekey": "6Le-SITEKEY", "pageurl": "https://example.com"},
proxy="res-a:port:user:pass",
proxytype="HTTP",
max_retries=1,
timeout=90
),
FallbackStep(
name="Residential Proxy B",
method="userrecaptcha",
params={"googlekey": "6Le-SITEKEY", "pageurl": "https://example.com"},
proxy="res-b:port:user:pass",
proxytype="HTTP",
max_retries=1,
timeout=90
),
FallbackStep(
name="No Proxy",
method="userrecaptcha",
params={"googlekey": "6Le-SITEKEY", "pageurl": "https://example.com"},
max_retries=1,
timeout=120
),
],
on_degraded=lambda errors: print(f"DEGRADED: {len(errors)} failures logged")
)
token = solver.solve(chain)
if token:
print(f"Token: {token[:40]}...")
else:
print("All fallbacks exhausted")
Node.js implementation
const axios = require("axios");
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
class FallbackSolver {
constructor(apiKey) {
this.apiKey = apiKey;
}
async solve(steps, onDegraded) {
const errors = [];
for (const step of steps) {
console.log(`Trying: ${step.name}`);
for (let attempt = 0; attempt <= (step.maxRetries || 1); attempt++) {
try {
const token = await this._attempt(step);
if (token) {
console.log(`Solved via: ${step.name}`);
return token;
}
} catch (err) {
errors.push(`${step.name} (attempt ${attempt + 1}): ${err.message}`);
console.log(` Failed: ${err.message}`);
}
}
}
console.log(`All fallbacks failed. ${errors.length} errors.`);
if (onDegraded) onDegraded(errors);
return null;
}
async _attempt(step) {
const params = {
key: this.apiKey,
method: step.method,
json: 1,
...step.params,
};
if (step.proxy) {
params.proxy = step.proxy;
params.proxytype = step.proxytype;
}
const submitResp = await axios.post(SUBMIT_URL, null, {
params,
timeout: 15000,
});
if (submitResp.data.status !== 1) {
throw new Error(submitResp.data.error_text || submitResp.data.request);
}
return this._poll(submitResp.data.request, step.timeout || 120000);
}
async _poll(taskId, maxWait) {
let elapsed = 0;
const interval = 5000;
while (elapsed < maxWait) {
await new Promise((r) => setTimeout(r, interval));
elapsed += interval;
const resp = await axios.get(RESULT_URL, {
params: { key: this.apiKey, action: "get", id: taskId, json: 1 },
timeout: 10000,
});
if (resp.data.status === 1) return resp.data.request;
if (resp.data.request === "CAPCHA_NOT_READY") continue;
throw new Error(resp.data.error_text || resp.data.request);
}
throw new Error(`Timeout after ${maxWait / 1000}s`);
}
}
// Usage
(async () => {
const solver = new FallbackSolver("YOUR_API_KEY");
const steps = [
{
name: "Residential Proxy A",
method: "userrecaptcha",
params: { googlekey: "6Le-SITEKEY", pageurl: "https://example.com" },
proxy: "res-a:port:user:pass",
proxytype: "HTTP",
maxRetries: 1,
timeout: 90000,
},
{
name: "Residential Proxy B",
method: "userrecaptcha",
params: { googlekey: "6Le-SITEKEY", pageurl: "https://example.com" },
proxy: "res-b:port:user:pass",
proxytype: "HTTP",
maxRetries: 1,
timeout: 90000,
},
{
name: "No Proxy",
method: "userrecaptcha",
params: { googlekey: "6Le-SITEKEY", pageurl: "https://example.com" },
maxRetries: 1,
timeout: 120000,
},
];
const token = await solver.solve(steps, (errors) => {
console.error(`DEGRADED: ${errors.length} failures`);
});
if (token) {
console.log(`Token: ${token.slice(0, 40)}...`);
}
})();
Multi-type fallback chains
Some sites change CAPTCHA types. Build chains that try different types:
chain = FallbackChain(steps=[
FallbackStep(
name="Turnstile (primary)",
method="turnstile",
params={"sitekey": "0x4AAAA-KEY", "pageurl": "https://example.com"},
timeout=60
),
FallbackStep(
name="reCAPTCHA v2 (fallback)",
method="userrecaptcha",
params={"googlekey": "6Le-KEY", "pageurl": "https://example.com"},
timeout=90
),
])
Monitoring fallback usage
Track which fallback level solves most tasks. If fallback 2 or 3 fires frequently, investigate the root cause:
from collections import Counter
solve_stats = Counter()
def track_solve(step_name):
solve_stats[step_name] += 1
# After a period
for step, count in solve_stats.most_common():
print(f"{step}: {count} solves")
# Alert if primary drops below 80%
total = sum(solve_stats.values())
primary = solve_stats.get("Residential Proxy A", 0)
if total > 0 and primary / total < 0.8:
print("WARNING: Primary path solving below 80%")
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| Always falls to last step | Primary proxy pool down | Check proxy provider status |
| Degraded mode triggers constantly | All proxy pools exhausted | Add more proxies or raise retries |
| Slow overall solve time | Sequential fallback adds latency | Use parallel first-to-finish for non-dependent steps |
| Token works from fallback but rejected by site | Proxy IP mismatch | Use same proxy for solving and browsing |
FAQ
Can I run fallback steps in parallel instead of sequential?
Yes, for independent steps. Use asyncio.gather (Python) or Promise.race (Node.js) and take the first success. This trades cost for speed.
How many fallback levels should I have?
Two to three is typical. More than four adds complexity without proportional reliability gains.
Should I use different API keys for different fallback levels?
Not usually. CaptchaAI tracks usage per key, and spreading across keys complicates billing. Use one key with soft_id for tracking.
Build resilient CAPTCHA workflows with CaptchaAI
Start building fallback chains at captchaai.com.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.