Tutorials

Alertas Discord Webhook para status do pipeline CAPTCHA

Quer saber quando o saldo da CaptchaAI está acabando ou quando a taxa de erro dispara — sem sair do Discord? Um webhook resolve isso em poucos minutos: você cria uma URL no servidor da equipe, aponta os scripts abaixo para ela e o canal passa a receber avisos de saldo baixo, picos de erro, fila travada e o resumo diário do pipeline. Não é preciso registrar um bot, gerenciar token de bot nem manter um processo com permissões amplas — é só uma requisição HTTP POST. Este guia mostra primeiro o resultado final, depois a configuração do webhook, um sistema completo em Python e um cliente equivalente em JavaScript, prontos para colar no seu monitor de pipeline.

Como as mensagens aparecem no canal

Antes de configurar qualquer coisa, veja o que a equipe recebe no Discord assim que o pipeline dispara um alerta:

Aviso de saldo:

💰 Alerta de saldo O saldo da CaptchaAI está em US$ 8,42 Limite: US$ 10,00 | Gravidade: AVISO

Pico de erro:

⚠️ Pico na taxa de erro Taxa de erro: 22,5% (45/200) • ERROR_CAPTCHA_UNSOLVABLE: 30 • TIMEOUT: 15

Resumo diário:

📈 Resumo diário de CAPTCHA 12.450 tarefas processadas Taxa de sucesso: 95,2% | Latência média: 22,4 s | Saldo: US$ 142,30

Cada bloco colorido acima vem de um método diferente da classe Python (ou das funções equivalentes em JavaScript) que você vai montar nas próximas seções.

Crie o webhook do Discord em 5 passos

A configuração é feita inteiramente na interface do Discord — não é preciso nenhum código nesta etapa:

  1. Abra as configurações do servidor Discord
  2. Vá em Integrações → Webhooks
  3. Clique em Novo Webhook e nomeie-o "Alertas CaptchaAI"
  4. Copie a URL do webhook
  5. Salve-a como variável de ambiente DISCORD_WEBHOOK_URL

Guarde essa URL como guardaria uma chave de API: quem tiver acesso a ela consegue postar no canal em nome do webhook.

Python: alertas automáticos do pipeline CAPTCHA

O exemplo abaixo define uma classe DiscordCaptchaAlerts com um método por tipo de alerta — saldo baixo, pico na taxa de erro, fila de tarefas acumulada, resumo diário e recuperação do pipeline — todos montados como embeds coloridos (verde para sucesso, laranja para aviso, vermelho para erro). A classe PipelineMonitor guarda os resultados dos últimos 5 minutos, recalcula a taxa de erro corrente e aplica um cooldown de 5 minutos entre alertas do mesmo tipo, para não inundar o canal enquanto o problema persiste. A checagem de saldo consulta res.php com action=getbalance e dispara o alerta assim que o valor cai abaixo de US$ 10.

import os
import time
import requests
from datetime import datetime

API_KEY = os.environ["CAPTCHAAI_API_KEY"]
DISCORD_WEBHOOK = os.environ["DISCORD_WEBHOOK_URL"]

session = requests.Session()


class DiscordCaptchaAlerts:
    COLORS = {
        "success": 0x2ECC71,   # Green
        "warning": 0xF39C12,   # Orange
        "error": 0xE74C3C,     # Red
        "info": 0x3498DB,      # Blue
    }

    def __init__(self, webhook_url):
        self.webhook_url = webhook_url

    def send_embed(self, title, description, color_key="info", fields=None):
        embed = {
            "title": title,
            "description": description,
            "color": self.COLORS.get(color_key, self.COLORS["info"]),
            "timestamp": datetime.utcnow().isoformat() + "Z",
            "footer": {"text": "CaptchaAI Pipeline Monitor"}
        }
        if fields:
            embed["fields"] = fields

        payload = {"embeds": [embed]}
        resp = requests.post(
            self.webhook_url, json=payload, timeout=10
        )
        resp.raise_for_status()

    def balance_alert(self, balance, threshold):
        severity = "error" if balance < 2 else "warning"
        self.send_embed(
            title="💰 Balance Alert",
            description=f"CaptchaAI balance is **${balance:.2f}**",
            color_key=severity,
            fields=[
                {"name": "Threshold", "value": f"${threshold:.2f}", "inline": True},
                {"name": "Severity", "value": severity.upper(), "inline": True},
                {"name": "Action", "value": "Top up your balance at captchaai.com", "inline": False}
            ]
        )

    def error_spike(self, error_rate, error_count, total_count, top_errors):
        error_list = "\n".join(
            f"• `{code}`: {count}" for code, count in top_errors.items()
        )
        self.send_embed(
            title="⚠️ Error Rate Spike",
            description=f"Error rate: **{error_rate:.1%}** ({error_count}/{total_count})",
            color_key="error",
            fields=[
                {"name": "Error Breakdown", "value": error_list or "No details", "inline": False},
                {"name": "Window", "value": "Last 5 minutes", "inline": True}
            ]
        )

    def queue_alert(self, depth, workers_active):
        self.send_embed(
            title="📊 Queue Backup",
            description=f"Queue depth: **{depth}** pending tasks",
            color_key="warning",
            fields=[
                {"name": "Active Workers", "value": str(workers_active), "inline": True},
                {"name": "Est. Drain Time", "value": f"{depth // max(workers_active, 1)} min", "inline": True}
            ]
        )

    def daily_summary(self, stats):
        self.send_embed(
            title="📈 Daily CAPTCHA Summary",
            description=f"**{stats['total']}** tasks processed",
            color_key="success" if stats["success_rate"] > 0.92 else "warning",
            fields=[
                {"name": "Success Rate", "value": f"{stats['success_rate']:.1%}", "inline": True},
                {"name": "Avg Latency", "value": f"{stats['avg_latency']:.1f}s", "inline": True},
                {"name": "Total Cost", "value": f"${stats['cost']:.2f}", "inline": True},
                {"name": "Errors", "value": str(stats["errors"]), "inline": True},
                {"name": "Balance", "value": f"${stats['balance']:.2f}", "inline": True},
                {"name": "Peak Queue", "value": str(stats["peak_queue"]), "inline": True},
            ]
        )

    def solve_recovered(self, previous_rate, current_rate):
        self.send_embed(
            title="✅ Pipeline Recovered",
            description=f"Solve rate recovered: {previous_rate:.1%} → {current_rate:.1%}",
            color_key="success"
        )


alerts = DiscordCaptchaAlerts(DISCORD_WEBHOOK)


class PipelineMonitor:
    def __init__(self, check_interval=60):
        self.check_interval = check_interval
        self.results = []  # (timestamp, success, error_code)
        self.last_balance_alert = 0
        self.last_error_alert = 0
        self.cooldown = 300  # 5 minutes between alerts

    def record(self, success, error_code=None):
        self.results.append((time.time(), success, error_code))
        # Keep last 5 min
        cutoff = time.time() - 300
        self.results = [r for r in self.results if r[0] > cutoff]

    def run_checks(self):
        now = time.time()

        # Balance check
        if now - self.last_balance_alert > self.cooldown:
            balance = self._check_balance()
            if balance is not None and balance < 10:
                alerts.balance_alert(balance, threshold=10)
                self.last_balance_alert = now

        # Error rate check
        if now - self.last_error_alert > self.cooldown and len(self.results) > 10:
            total = len(self.results)
            errors = [r for r in self.results if not r[1]]
            error_rate = len(errors) / total

            if error_rate > 0.15:
                error_breakdown = {}
                for _, _, code in errors:
                    if code:
                        error_breakdown[code] = error_breakdown.get(code, 0) + 1
                alerts.error_spike(error_rate, len(errors), total, error_breakdown)
                self.last_error_alert = now

    def _check_balance(self):
        try:
            resp = session.get("https://ocr.captchaai.com/res.php", params={
                "key": API_KEY, "action": "getbalance", "json": 1
            })
            data = resp.json()
            if data.get("status") == 1:
                return float(data["request"])
        except Exception:
            pass
        return None


monitor = PipelineMonitor()

Chame monitor.run_checks() dentro do loop principal do seu worker (a cada check_interval segundos) para manter as duas checagens — saldo e taxa de erro — sempre ativas.

JavaScript: cliente de webhook para monitorar o pipeline

A versão em Node.js segue a mesma lógica com axios: uma função central, sendDiscordEmbed, monta o payload, e as funções alertBalance, alertErrorSpike e sendDailySummary chamam essa base com os campos específicos de cada alerta. O setInterval no final roda a checagem de saldo a cada 5 minutos — ajuste esse intervalo conforme o volume do seu pipeline; pipelines com poucas centenas de tarefas por dia raramente precisam de algo mais frequente do que isso.

const axios = require("axios");

const API_KEY = process.env.CAPTCHAAI_API_KEY;
const DISCORD_WEBHOOK = process.env.DISCORD_WEBHOOK_URL;

const COLORS = {
  success: 0x2ecc71,
  warning: 0xf39c12,
  error: 0xe74c3c,
  info: 0x3498db,
};

async function sendDiscordEmbed(title, description, colorKey = "info", fields = []) {
  await axios.post(DISCORD_WEBHOOK, {
    embeds: [
      {
        title,
        description,
        color: COLORS[colorKey] || COLORS.info,
        timestamp: new Date().toISOString(),
        footer: { text: "CaptchaAI Pipeline Monitor" },
        fields,
      },
    ],
  }, { timeout: 10000 });
}

async function alertBalance(balance, threshold = 10) {
  const severity = balance < 2 ? "error" : "warning";
  await sendDiscordEmbed(
    "💰 Balance Alert",
    `CaptchaAI balance is **$${balance.toFixed(2)}**`,
    severity,
    [
      { name: "Threshold", value: `$${threshold.toFixed(2)}`, inline: true },
      { name: "Severity", value: severity.toUpperCase(), inline: true },
    ]
  );
}

async function alertErrorSpike(errorRate, details = {}) {
  await sendDiscordEmbed(
    "⚠️ Error Rate Spike",
    `Error rate: **${(errorRate * 100).toFixed(1)}%**`,
    "error",
    [
      { name: "Total Tasks", value: String(details.total || 0), inline: true },
      { name: "Errors", value: String(details.errors || 0), inline: true },
    ]
  );
}

async function sendDailySummary(stats) {
  const color = stats.successRate > 0.92 ? "success" : "warning";
  await sendDiscordEmbed(
    "📈 Daily CAPTCHA Summary",
    `**${stats.total}** tasks processed`,
    color,
    [
      { name: "Success Rate", value: `${(stats.successRate * 100).toFixed(1)}%`, inline: true },
      { name: "Avg Latency", value: `${stats.avgLatency.toFixed(1)}s`, inline: true },
      { name: "Balance", value: `$${stats.balance.toFixed(2)}`, inline: true },
    ]
  );
}

// Balance monitoring loop
async function monitorBalance() {
  try {
    const resp = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "getbalance", json: 1 },
    });
    if (resp.data.status === 1) {
      const balance = parseFloat(resp.data.request);
      if (balance < 10) await alertBalance(balance);
    }
  } catch (err) {
    console.error("Balance check failed:", err.message);
  }
}

setInterval(monitorBalance, 300000); // Every 5 minutes

module.exports = { alertBalance, alertErrorSpike, sendDailySummary };

Erros comuns no webhook do Discord (e como corrigir)

Problemas com webhooks do Discord quase sempre caem em um destes quatro casos:

Problema Causa Correção
400 Bad Request Estrutura do embed inválida Confira o formato do array fields; garanta que todos os valores sejam strings
Rate limit (429) Excesso de mensagens por minuto Adicione um cooldown entre alertas (mínimo de 5 minutos)
Webhook excluído Alguém removeu o webhook do servidor Crie um novo webhook e atualize a variável de ambiente
Embed não aparece Array embeds ausente no payload Envolva o objeto do embed em {"embeds": [...]}

Perguntas frequentes

Quantos webhooks o Discord permite por canal?

Até 15 por canal. Não crie um webhook por tipo de alerta — um único webhook "Alertas CaptchaAI", reutilizado por toda a classe, já é suficiente.

Alertas via Discord consomem thread do meu plano CaptchaAI?

Não. O webhook conversa apenas com a API do Discord — ele não passa pela CaptchaAI e não ocupa thread do seu plano (BASIC, STANDARD, ADVANCE...). O consumo de threads depende só de quantos CAPTCHAs você resolve em paralelo.

Preciso manter um servidor rodando 24 horas para os alertas funcionarem?

Não necessariamente. Se o seu pipeline já roda como um worker de longa duração, basta chamar monitor.run_checks() dentro do loop existente. Para pipelines mais esporádicos, um cron job ou uma function serverless que executa a checagem a cada poucos minutos funciona igual de bem — inclusive rodando em uma região como sa-east-1 para reduzir a latência até seus próprios serviços.

Dá para usar o mesmo webhook em mais de um pipeline?

Sim. O footer do embed (por exemplo "CaptchaAI Pipeline Monitor") identifica a origem do alerta — troque esse texto por pipeline se quiser diferenciar vários projetos no mesmo canal do Discord.

O Discord serve para alertas de produção ou só para visibilidade da equipe?

Para acompanhar saldo, erros e volume em tempo real, o Discord funciona bem como canal secundário. Para paging de plantão com escalonamento — alguém precisa ser acordado às 3h se a fila travar — use PagerDuty ou Opsgenie; o Discord não substitui isso.

Próximos passos

Leve os alertas do pipeline para o canal onde a equipe já está — comece com uma chave de API CaptchaAI e conecte o webhook em poucos minutos.

Guias relacionados:

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