Tutoriais

Tratamento de erros de URL de retorno de chamada CaptchaAI: padrões de repetição e mensagens mortas

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

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:

Os comentários estão desativados para este artigo.