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.
- Acesse api.slack.com/apps e clique em Create New App
- Escolha Incoming Webhooks e ative a opção
- Clique em Add New Webhook to Workspace e selecione o canal de destino
- 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 ambienteERROR_ZERO_BALANCE— conta sem saldoERROR_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.