Os retornos de chamada (pingbacks) eliminam a pesquisa, mas introduzem um novo modo de falha: o que acontece quando o servidor está inativo, retorna um erro ou expira quando CaptchaAI tenta entregar o resultado? Este tutorial aborda padrões para lidar com falhas de retorno de chamada sem perder soluções CAPTCHA.
O que pode dar errado
| Modo de falha | Sintoma | Result |
|---|---|---|
| Servidor inativo | CaptchaAI tem conexão recusada | Solução não entregue |
| Servidor retorna 5xx | CaptchaAI recebe resposta de erro | Não pode tentar novamente (depende da implementação) |
| Tempo limite da rede | Conexão CaptchaAI trava | Solução potencialmente perdida |
| Falhas no manipulador | Solicitação aceita, mas resultado não armazenado | Solução caiu silenciosamente |
A solução: nunca confie apenas em retornos de chamada. Sempre tenha um substituto.
Padrão 1: Retorno de Chamada + Pesquisa de Fallback
A abordagem mais confiável: aceitar retornos de chamada quando eles chegarem, mas pesquisar quaisquer tarefas que não recebam retornos de chamada dentro do tempo limite.
Pitão
import os
import time
import threading
import requests
from flask import Flask, request
app = Flask(__name__)
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
# Track task state
pending_tasks = {} # task_id -> {"submitted_at": timestamp, "status": "pending"}
results = {}
lock = threading.Lock()
def submit_captcha(sitekey, pageurl, callback_url):
"""Submit with callback, but track for fallback polling."""
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"pingback": callback_url,
"json": 1
})
data = resp.json()
if data.get("status") == 1:
task_id = data["request"]
with lock:
pending_tasks[task_id] = {
"submitted_at": time.time(),
"status": "pending"
}
return task_id
return None
@app.route("/callback")
def captcha_callback():
"""Primary result delivery — CaptchaAI sends results here."""
task_id = request.args.get("id")
solution = request.args.get("code")
with lock:
results[task_id] = solution
pending_tasks.pop(task_id, None)
return "OK", 200
def fallback_poller():
"""Poll for any tasks that missed their callback."""
while True:
time.sleep(30) # Check every 30 seconds
with lock:
stale_tasks = [
tid for tid, info in pending_tasks.items()
if time.time() - info["submitted_at"] > 120 # 2 min callback timeout
and info["status"] == "pending"
]
for task_id in stale_tasks:
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY,
"action": "get",
"id": task_id,
"json": 1
})
data = resp.json()
if data.get("status") == 1:
with lock:
results[task_id] = data["request"]
pending_tasks.pop(task_id, None)
print(f"Fallback poll recovered: {task_id}")
elif data.get("request") != "CAPCHA_NOT_READY":
# Permanent error — remove from pending
with lock:
pending_tasks.pop(task_id, None)
print(f"Task failed: {task_id} — {data.get('request')}")
# Start fallback poller in background
poller_thread = threading.Thread(target=fallback_poller, daemon=True)
poller_thread.start()
JavaScript
const express = require("express");
const axios = require("axios");
const app = express();
const API_KEY = process.env.CAPTCHAAI_API_KEY;
const pendingTasks = new Map(); // taskId -> { submittedAt, status }
const results = new Map();
async function submitCaptcha(sitekey, pageurl, callbackUrl) {
const resp = await axios.post("https://ocr.captchaai.com/in.php", null, {
params: {
key: API_KEY,
method: "userrecaptcha",
googlekey: sitekey,
pageurl: pageurl,
pingback: callbackUrl,
json: 1,
},
});
if (resp.data.status === 1) {
const taskId = resp.data.request;
pendingTasks.set(taskId, {
submittedAt: Date.now(),
status: "pending",
});
return taskId;
}
return null;
}
// Primary callback endpoint
app.get("/callback", (req, res) => {
const taskId = req.query.id;
const solution = req.query.code;
results.set(taskId, solution);
pendingTasks.delete(taskId);
res.sendStatus(200);
});
// Fallback poller
setInterval(async () => {
const now = Date.now();
const staleTasks = [];
for (const [taskId, info] of pendingTasks) {
if (now - info.submittedAt > 120000 && info.status === "pending") {
staleTasks.push(taskId);
}
}
for (const taskId of staleTasks) {
try {
const resp = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: taskId, json: 1 },
});
if (resp.data.status === 1) {
results.set(taskId, resp.data.request);
pendingTasks.delete(taskId);
console.log(`Fallback recovered: ${taskId}`);
} else if (resp.data.request !== "CAPCHA_NOT_READY") {
pendingTasks.delete(taskId);
console.log(`Task failed: ${taskId} — ${resp.data.request}`);
}
} catch (err) {
console.error(`Poll error for ${taskId}: ${err.message}`);
}
}
}, 30000);
app.listen(3000);
Padrão 2: fila de mensagens mortas
Quando seu manipulador de retorno de chamada processa um resultado, mas encontra um erro (banco de dados inativo, falha de validação), mova o problema para uma fila de mensagens mortas em vez de perder os dados.
Pitão
import json
import os
import time
from pathlib import Path
DEAD_LETTER_DIR = Path("dead_letter")
DEAD_LETTER_DIR.mkdir(exist_ok=True)
@app.route("/callback")
def captcha_callback_with_dlq():
task_id = request.args.get("id")
solution = request.args.get("code")
try:
# Attempt normal processing
store_result(task_id, solution)
return "OK", 200
except Exception as e:
# Processing failed — save to dead-letter queue
dead_letter = {
"task_id": task_id,
"solution": solution,
"error": str(e),
"received_at": time.time()
}
dlq_path = DEAD_LETTER_DIR / f"{task_id}.json"
dlq_path.write_text(json.dumps(dead_letter))
print(f"DLQ: {task_id} — {e}")
return "OK", 200 # Still return 200 to CaptchaAI
def reprocess_dead_letters():
"""Retry processing dead-letter items."""
for dlq_file in DEAD_LETTER_DIR.glob("*.json"):
item = json.loads(dlq_file.read_text())
try:
store_result(item["task_id"], item["solution"])
dlq_file.unlink() # Remove after successful processing
print(f"DLQ reprocessed: {item['task_id']}")
except Exception:
pass # Leave in DLQ for next retry
JavaScript
const fs = require("fs");
const path = require("path");
const DLQ_DIR = path.join(__dirname, "dead_letter");
if (!fs.existsSync(DLQ_DIR)) fs.mkdirSync(DLQ_DIR);
app.get("/callback-dlq", (req, res) => {
const taskId = req.query.id;
const solution = req.query.code;
try {
storeResult(taskId, solution);
res.sendStatus(200);
} catch (err) {
// Save to dead-letter queue
const deadLetter = {
task_id: taskId,
solution: solution,
error: err.message,
received_at: Date.now(),
};
fs.writeFileSync(
path.join(DLQ_DIR, `${taskId}.json`),
JSON.stringify(deadLetter)
);
console.log(`DLQ: ${taskId} — ${err.message}`);
res.sendStatus(200); // Still acknowledge to CaptchaAI
}
});
function reprocessDeadLetters() {
const files = fs.readdirSync(DLQ_DIR).filter((f) => f.endsWith(".json"));
for (const file of files) {
const filePath = path.join(DLQ_DIR, file);
const item = JSON.parse(fs.readFileSync(filePath, "utf8"));
try {
storeResult(item.task_id, item.solution);
fs.unlinkSync(filePath);
console.log(`DLQ reprocessed: ${item.task_id}`);
} catch (err) {
// Leave in DLQ
}
}
}
// Retry DLQ every 5 minutes
setInterval(reprocessDeadLetters, 300000);
Padrão 3: manipulador de retorno de chamada idempotente
Os retornos de chamada podem ser entregues mais de uma vez. Torne seu manipulador idempotente:
@app.route("/callback")
def idempotent_callback():
task_id = request.args.get("id")
solution = request.args.get("code")
with lock:
# Only process if not already handled
if task_id in results:
return "OK", 200 # Already processed — skip silently
results[task_id] = solution
pending_tasks.pop(task_id, None)
return "OK", 200
Matriz de decisão: qual padrão usar
| Cenário | mais adequado Padrão |
|---|---|
| Baixo volume, tempo de inatividade ocasional | Retorno de chamada + pesquisa substituta |
| Alto volume, possíveis interrupções do banco de dados | Fila de mensagens mortas |
| Vários consumidores podem processar o mesmo resultado | Manipulador Idempotente |
| Sistema de produção com SLAs | Todos os três combinados |
Solução de problemas
| Problema | Causa | Correção |
|---|---|---|
| O poller substituto encontra tarefas que já foram entregues | Corrida entre callback e poller | Adicionar verificação de idempotência - pule se já estiver nos resultados |
| DLQ crescendo sem ser processado | O reprocessador não está funcionando ou falhando | Verifique os logs do reprocessador; garantir que o problema subjacente (DB) seja corrigido |
| O retorno de chamada retorna 200, mas o resultado é perdido | O manipulador falha após o envio da resposta | Processe antes de responder ou use o padrão DLQ |
| Muitas solicitações de pesquisa substituta | Muitas tarefas obsoletas | Aumentar o limite de tempo limite de retorno de chamada; verifique o tempo de atividade do servidor |
Perguntas frequentes
Devo sempre retornar 200 para retornos de chamada CaptchaAI?
Sim. Retornar um código de erro (4xx/5xx)não ajuda - CaptchaAI não pode tentar novamente retornos de chamada. Sempre aceite a entrega (200 OK) e lide com falhas internamente com DLQ ou pesquisa de fallback.
Quanto tempo devo esperar antes da votação alternativa?
Aguarde pelo menos 120 segundos após o envio. A maioria dos CAPTCHAs resolve dentro de 10 a 60 segundos, mais latência de rede para entrega de retorno de chamada. Dois minutos dão tempo suficiente para o retorno de chamada chegar.
Posso desativar retornos de chamada e apenas pesquisar?
Sim, simplesmente não inclua o parâmetro pingback. Mas os retornos de chamada reduzem significativamente as chamadas de API em escala (duas chamadas por tarefa em vez de mais de 10 solicitações de pesquisa).
Artigos relacionados
- Python Captcha resolvendo padrões de erro de nova tentativa
- Validação de retorno de chamada de segurança do Captchaai Webhook
- Referência de códigos de erro Captchaai
Próximas etapas
Crie um tratamento confiável de retorno de chamada CAPTCHA -obtenha sua chave API CaptchaAIe implementar esses padrões de resiliência.
Guias relacionados:
- URL de retorno de chamada e guia de webhook
- Padrões de notificação de tarefas de Pingback
- Segurança do Webhook: Validando retornos de chamada