Tutorials

Notificações do Slack Bot para eventos CAPTCHA

Três avisos cobrem quase todo o risco de um pipeline de CAPTCHA: falha de resolução, saldo baixo e taxa de erro fora do normal. Com esses três chegando no canal certo do Slack, ninguém precisa descobrir na segunda-feira de manhã, olhando uma planilha vazia, que a automação parou na sexta à noite.

O tutorial monta os três do zero em Python e Node.js, com um webhook de entrada do Slack e a API da CaptchaAI. O cenário é conhecido de quem roda coleta autorizada de madrugada: os workers sobem em sa-east-1, a conta fica sem saldo às 3h12 e o processo devolve erro em silêncio até alguém entrar às 9h.


Passo 1: crie o webhook de entrada no Slack

O webhook é a única credencial necessária do lado do Slack: aponta para um canal fixo e aceita POST com JSON.

  1. Acesse api.slack.com/apps e clique em Create New App
  2. Escolha Incoming Webhooks e ative a opção
  3. Clique em Add New Webhook to Workspace e selecione o canal de destino
  4. Copie a URL do webhook gerada

Trate essa URL como segredo: quem a tiver posta no seu canal. E separe desde já #captcha-alertas (falhas e saldo) de #captcha-diario (resumo), ou o time silencia o canal inteiro e perde justamente o aviso que importava.


Passo 2: o helper de notificação em Python

Tudo o que vem depois chama esta função. Ela monta um attachment colorido, anexa campos em tabela e define timeout de 10 s, para que uma indisponibilidade do Slack nunca trave o worker.

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

A cor é semântica: vermelho para falha, laranja para saldo, verde para o resumo diário. O time aprende a ler o canal sem ler o texto.


Passo 3: alerta de falha na resolução

O primeiro gatilho dispara quando uma tarefa volta com código de erro. Envie o task_id, o tipo de CAPTCHA, o código de erro e a URL — os quatro campos que o plantão quer ver antes de abrir o log.

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")

Não alerte cada falha isolada: um ERROR_CAPTCHA_UNSOLVABLE esporádico é ruído normal. Reserve o aviso vermelho para o que exige ação humana:

  • ERROR_WRONG_USER_KEY — chave errada no ambiente
  • ERROR_ZERO_BALANCE — conta sem saldo
  • ERROR_KEY_DOES_NOT_EXIST — chave revogada

O resto vira estatística no Passo 5.


Passo 4: alerta de saldo baixo

Saldo é a causa mais comum de "parou tudo de uma vez sem ninguém mexer no código". A consulta usa action=getbalance no endpoint res.php, com json=1, e compara o valor com um limite.

def check_balance_alert(api_key, threshold=5.0):
    """Alert when balance lançamentos de teste 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")

A cobrança da CaptchaAI é por thread simultânea, com resoluções ilimitadas dentro do plano. O saldo que você acompanha aqui é o crédito que renova a assinatura, não um preço por CAPTCHA resolvido.

Deixe o threshold acima da próxima renovação: US$ 5 bastam no BASIC (US$ 15/mês, 5 threads), mas não no CORPORATE (US$ 240/mês, 150 threads).


Passo 5: alerta de taxa de erro em janela móvel

Este é o alerta que detecta degradação real. Em vez de reagir a uma falha, ele acompanha as últimas 50 tentativas e só avisa quando a proporção de erros passa de 30%, com cooldown de 5 minutos.

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

Dois detalhes evitam falso positivo: o mínimo de 20 amostras, sem o qual duas falhas após o deploy disparariam uma taxa absurda, e o cooldown, que impede um único incidente de gerar quarenta mensagens.

Com tipos em beta, como CaptchaFox (beta), Friendly Captcha (beta) e Lemin (beta), meça a linha de base no seu ambiente antes de fixar o threshold.


Passo 6: a mesma lógica em Node.js

Se o pipeline é JavaScript, o desenho é idêntico: um helper, os mesmos campos, o mesmo endpoint de saldo.

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);

O setInterval cumpre o papel da Timer do Python. Em ambiente serverless — AWS Lambda ou Google Cloud Functions com agendamento — descarte-o e deixe o agendador invocar checkBalance: timers em função efêmera não sobrevivem ao fim da execução.


Passo 7: resumo diário em vez de mais alertas

Alerta é para o que precisa de ação agora. O resto vira um digest verde no início do dia: total processado, resolvidos, falhas, tempo médio de resolução, custo e taxa de sucesso.

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%}",
        },
    )

O resumo também serve de histórico informal de capacidade. Quando o volume cresce e o tempo médio de resolução sobe junto, normalmente você chegou ao teto de threads do plano — a fila está esperando, não a resolução.


Solução de problemas

Problema Causa Correção
Webhook retorna 403 URL inválida ou revogada Recrie o webhook e atualize o segredo
Alertas demais no canal Sem cooldown configurado Aplique o cooldown do Passo 5
Alertas chegando atrasados Requisição sem timeout Mantenha o timeout de 10 s na chamada
Canal não recebe nada Webhook vinculado a outro canal Verifique o canal no app do Slack
Alerta de saldo nunca dispara Resposta lida como texto Confirme json=1 e converta para float

Uso autorizado e dados que vão para o canal

Esse monitoramento é para os seus próprios fluxos: QA autorizado, staging e monitoramento com permissão. Pense também no que o alerta carrega — o campo Site do Passo 3 vai para um canal que muita gente lê. Se a URL tiver identificador de cliente ou dado pessoal, considere as obrigações da LGPD (ou do RGPD, em Portugal) antes de publicá-la; mascarar a query string resolve sem perder utilidade diagnóstica.


Perguntas frequentes

Qual limite de taxa de erro devo usar no começo?

Comece em 30% com janela de 50 tarefas e ajuste após uma semana de dados reais. Se o canal disparar em dias normais, o limite está baixo; se um incidente evidente passou batido, está alto demais.

O bot precisa de permissões especiais no workspace?

Não. Um webhook de entrada basta para postar em um canal: não é preciso escopo de leitura, bot user nem OAuth completo.

Onde esses alertas devem rodar em produção?

No mesmo processo que já faz o polling do resultado — o helper é uma requisição HTTP curta e não justifica um serviço separado. A exceção é a verificação de saldo, que combina bem com um cron.

Consigo alertar por tipo de CAPTCHA separadamente?

Sim. Mantenha uma instância de ErrorRateNotifier por tipo e inclua o tipo no título do alerta. Assim uma queda pontual em um tipo não contamina a métrica geral.

E se a minha equipe usa Discord?

Funciona com pouca mudança: o webhook do Discord aceita JSON parecido — troque attachments por embeds e ajuste os nomes dos campos. A lógica dos três gatilhos continua idêntica.


Coloque o primeiro alerta no ar hoje

Pegue sua chave de API em captchaai.com, cole o helper do Passo 2 e dispare um alerta de teste antes de conectar qualquer gatilho: você valida o webhook em um minuto, e não durante o próximo incidente.


Guias relacionados

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