Tutoriales

Notificaciones de Slack Bot para eventos CAPTCHA

La forma más corta de enterarte de que tu flujo de CAPTCHA se rompió es un webhook entrante de Slack y unas líneas de Python: el aviso cae en el canal del equipo en segundos, con el task ID y el código de error dentro. Aquí montas los tres avisos que cubren casi todos los incidentes reales — un fallo al resolver, el saldo por debajo del umbral y una tasa de error que se dispara — más un resumen diario para ver tendencias sin generar ruido.

Qué merece una alerta y qué solo genera ruido

Antes de escribir código, decide qué justifica interrumpir a una persona. La regla que mejor funciona: alerta de lo que cambia una decisión y registra todo lo demás.

  • Interrumpe ahora: el saldo está a punto de agotarse o la tasa de error supera tu umbral durante una ventana completa.
  • Agrupa en un resumen: fallos aislados, reintentos que acaban resolviendo, tiempos de resolución algo altos.
  • Solo al log: cada tarea enviada y cada token entregado. Eso va a tu registro estructurado, no al canal.

Traducido a umbrales concretos, esta es la tabla que puedes copiar tal cual y calibrar con tu volumen real durante la primera semana:

Evento Umbral sugerido Destino
Fallo individual Sin alerta Registro estructurado
Tasa de error en ventana de 50 tareas > 30% Canal #captcha-alertas
Saldo de la cuenta Por debajo de $5 Canal + aviso a facturación
Tiempo de resolución medio Duplica tu línea base Resumen diario

Paso 1: crear el webhook entrante en Slack

  1. Entra en api.slack.com/apps y crea una aplicación nueva.
  2. Activa Incoming Webhooks en la configuración de la app.
  3. Pulsa Add New Webhook to Workspace y elige el canal, por ejemplo #captcha-alertas.
  4. Copia la URL del webhook y guárdala como variable de entorno, nunca dentro del repositorio.

Quien tenga esa URL puede publicar en tu canal: si se filtra, revócala y genera otra.

Paso 2: el helper de notificaciones en Python

Todo lo demás se apoya en una sola función: recibe título, mensaje, color y un diccionario de campos, y devuelve True cuando Slack acepta la publicación. El timeout de 10 s evita que un webhook lento bloquee el hilo desde el que envías las tareas.

import requests
import json
from datetime import datetime

SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/T00/B00/xxx"


def send_slack_alert(title, message, color="#ff0000", fields=None):
    """Send a formatted Slack alert."""
    attachment = {
        "color": color,
        "title": title,
        "text": message,
        "ts": int(datetime.now().timestamp()),
    }
    if fields:
        attachment["fields"] = [
            {"title": k, "value": str(v), "short": True}
            for k, v in fields.items()
        ]

    payload = {"attachments": [attachment]}
    resp = requests.post(SLACK_WEBHOOK_URL, json=payload, timeout=10)
    return resp.status_code == 200

Las tres alertas que cubren casi todos los incidentes

Alerta 1: fallo al resolver un CAPTCHA

El aviso útil no dice "algo falló": dice qué tarea, de qué tipo y con qué código de error. Etiqueta siempre el tipo, porque reCAPTCHA v2 y v3, Cloudflare Turnstile, Cloudflare Challenge, GeeTest v3 y el OCR de imagen tienen perfiles de error distintos, y a los tipos en beta (CaptchaFox, Friendly Captcha, Lemin) conviene ponerles un umbral aparte.

def notify_solve_failure(task_id, captcha_type, error_code, site_url):
    send_slack_alert(
        title="CAPTCHA Solve Failed",
        message=f"Task `{task_id}` failed with `{error_code}`",
        color="#ff0000",
        fields={
            "Type": captcha_type,
            "Error": error_code,
            "Site": site_url,
            "Time": datetime.now().strftime("%H:%M:%S"),
        },
    )

# Use after a failed solve
result = poll_for_result(task_id)
if result.get("error"):
    notify_solve_failure(task_id, "recaptcha_v2", result["error"], "https://example.com")

Alerta 2: saldo bajo antes de que se pare la cola

Es la alerta que más incidentes evita, porque avisa de un problema administrativo, no técnico. Los planes de CaptchaAI se facturan por threads concurrentes — BASIC ($15/mes, 5 threads), ADVANCE ($90/mes, 50 threads) o PREMIUM ($170/mes, 100 threads) — con resoluciones ilimitadas por thread. El endpoint res.php con action=getbalance devuelve el saldo, y un umbral conservador te da margen para renovar antes de que el pipeline se detenga un domingo por la noche.

def check_balance_alert(api_key, threshold=5.0):
    """Alert when balance falls below threshold."""
    resp = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": api_key, "action": "getbalance", "json": "1"
    }).json()

    balance = float(resp.get("request", 0))

    if balance < threshold:
        send_slack_alert(
            title="Low CaptchaAI Balance",
            message=f"Balance is ${balance:.2f} (threshold: ${threshold:.2f})",
            color="#ff9900",
            fields={
                "Current Balance": f"${balance:.2f}",
                "Threshold": f"${threshold:.2f}",
            },
        )
    return balance

# Run periodically
import threading

def balance_monitor(api_key, interval=300):
    """Check balance every 5 minutes."""
    check_balance_alert(api_key)
    timer = threading.Timer(interval, balance_monitor, args=[api_key, interval])
    timer.daemon = True
    timer.start()

balance_monitor("YOUR_API_KEY")

Alerta 3: cuando la tasa de error se dispara

Aquí se evita la fatiga por alerta. En vez de notificar cada fallo, acumulas los últimos resultados en una ventana deslizante y solo publicas cuando el porcentaje supera el umbral. El período de recuperación de 300 segundos impide que un incidente largo repita el mismo mensaje.

from collections import deque

class ErrorRateNotifier:
    def __init__(self, window=50, threshold=0.3, cooldown=300):
        self.results = deque(maxlen=window)
        self.threshold = threshold
        self.cooldown = cooldown
        self.last_alert = 0

    def record(self, success):
        self.results.append(success)

        if len(self.results) < 20:
            return

        error_rate = 1 - sum(self.results) / len(self.results)

        import time
        now = time.time()
        if error_rate > self.threshold and (now - self.last_alert) > self.cooldown:
            self.last_alert = now
            send_slack_alert(
                title="High CAPTCHA Error Rate",
                message=f"Error rate: {error_rate:.0%} over last {len(self.results)} tasks",
                color="#ff0000",
                fields={
                    "Error Rate": f"{error_rate:.1%}",
                    "Window": f"{len(self.results)} tasks",
                    "Threshold": f"{self.threshold:.0%}",
                },
            )

notifier = ErrorRateNotifier()

# After each solve attempt
notifier.record(success=True)   # solved
notifier.record(success=False)  # failed

La misma lógica en Node.js

Si tus workers viven en Node.js, la traducción es directa: axios en lugar de requests y setInterval en lugar de threading.Timer. El payload de Slack es idéntico, así que mantén los mismos colores y nombres de campo en ambos servicios.

const axios = require('axios');

const SLACK_WEBHOOK = 'https://hooks.slack.com/services/T00/B00/xxx';

async function sendSlackAlert(title, message, color = '#ff0000', fields = {}) {
  const attachment = {
    color,
    title,
    text: message,
    ts: Math.floor(Date.now() / 1000),
    fields: Object.entries(fields).map(([k, v]) => ({
      title: k, value: String(v), short: true,
    })),
  };

  await axios.post(SLACK_WEBHOOK, { attachments: [attachment] });
}

// Failure alert
async function notifySolveFailure(taskId, type, error) {
  await sendSlackAlert(
    'CAPTCHA Solve Failed',
    `Task \`${taskId}\` failed: \`${error}\``,
    '#ff0000',
    { Type: type, Error: error }
  );
}

// Balance alert
async function checkBalance(apiKey, threshold = 5.0) {
  const resp = await axios.get('https://ocr.captchaai.com/res.php', {
    params: { key: apiKey, action: 'getbalance', json: 1 },
  });
  const balance = parseFloat(resp.data.request);

  if (balance < threshold) {
    await sendSlackAlert(
      'Low CaptchaAI Balance',
      `Balance: $${balance.toFixed(2)}`,
      '#ff9900',
      { Balance: `$${balance.toFixed(2)}`, Threshold: `$${threshold.toFixed(2)}` }
    );
  }
  return balance;
}

// Periodic check
setInterval(() => checkBalance('YOUR_API_KEY'), 5 * 60 * 1000);

Resumen diario para el canal del equipo

Las alertas cuentan lo que se rompe; el resumen cuenta lo que es normal. Enviarlo cada mañana te da la línea base con la que comparar cuando algo se degrada poco a poco sin llegar a disparar ningún umbral.

def send_daily_summary(stats):
    """Send a daily digest to Slack."""
    send_slack_alert(
        title="Daily CAPTCHA Summary",
        message=f"{stats['total']} tasks processed",
        color="#36a64f",
        fields={
            "Solved": stats["solved"],
            "Failed": stats["failed"],
            "Avg Solve Time": f"{stats['avg_time_ms']}ms",
            "Total Cost": f"${stats['total_cost']:.2f}",
            "Success Rate": f"{stats['success_rate']:.1%}",
        },
    )

Un equipo repartido entre Madrid y Bogotá

Caso habitual en la región: el pipeline corre en un servidor europeo y el equipo de datos está dividido entre Madrid y Bogotá, siete horas de diferencia. Sin alertas, un fallo a las 22:00 hora peninsular se descubre a la mañana siguiente con las tablas vacías; con el canal conectado, el aviso llega a quien todavía está de turno al otro lado del Atlántico y el reproceso empieza esa noche.

Ese equipo monitoriza formularios públicos protegidos por CAPTCHA — cita previa, trámites administrativos, fichas de producto en marketplaces regionales — siempre sobre flujos autorizados y respetando los términos de servicio y la normativa de protección de datos aplicable (RGPD y LOPDGDD en España, LFPDPPP en México).

Errores frecuentes y cómo solucionarlos

Síntoma Causa probable Solución
El webhook devuelve 403 URL revocada o mal copiada Vuelve a crear el webhook y actualiza la variable de entorno
El webhook devuelve 429 Demasiados mensajes por minuto Agrupa avisos y respeta el período de recuperación
Llegan demasiadas alertas Falta período de recuperación Aplica el cooldown de ErrorRateNotifier
Las alertas llegan tarde Se agota el tiempo de espera Mantén el timeout en 10 s y envía desde un worker aparte
El canal no recibe nada El webhook apunta a otro canal Comprueba el canal asociado a la app

Preguntas frecuentes

¿Puedo enviar las alertas a un canal privado o a un mensaje directo?

Sí. El webhook entrante queda ligado al canal que elijas al crearlo, incluidos los privados donde esté instalada la app. Para mensajes directos necesitas un bot token y chat.postMessage, no un webhook.

¿Cada cuánto conviene consultar el saldo?

Cada cinco minutos basta, y es el intervalo del ejemplo. Consultarlo en cada tarea gasta llamadas a la API sin darte información nueva: el saldo se mueve despacio comparado con el ritmo de resolución.

¿Cómo distingo un problema mío de uno del sitio objetivo?

Compara tipos. Si solo falla un tipo de CAPTCHA en un dominio concreto, casi siempre cambió algo en esa página. Si la tasa de error sube a la vez en varios tipos y dominios, mira primero tu red, tus threads disponibles y tus tiempos de espera.

¿Merece la pena separar los canales por severidad?

Sí, en cuanto pasas de un par de pipelines. Un canal para lo que exige acción inmediata y otro para resúmenes mantiene alta la señal.

Supervisa tus flujos de CAPTCHA con CaptchaAI

Obtén tu clave API en captchaai.com y deja el primer aviso de fallo publicando en tu canal hoy mismo.

Guías relacionadas

Los comentarios están deshabilitados para este artículo.