Cuando tu pipeline de CAPTCHA se queda sin saldo o empieza a devolver errores, quieres enterarte en segundos, no a la mañana siguiente con miles de tareas ya sin resolver. La forma más rápida sin montar un panel aparte es enviar cada aviso a un canal de Discord mediante un webhook: un endpoint HTTP al que haces POST con un embed y listo. Sin bots, sin OAuth, sin infraestructura extra. Aquí conectas el estado del pipeline con CaptchaAI —saldo bajo, picos de error, cola saturada y resumen diario— con ejemplos en Python y JavaScript.
Por qué Discord funciona para monitorear el pipeline
Muchos equipos de automatización y scraping ya viven en Discord durante el día, así que el aviso llega donde la gente lo va a ver. Piensa en un equipo en Bogotá o Madrid que corre workers de noche: si el saldo de CaptchaAI baja del umbral a las 3 de la mañana, un embed rojo en #alertas deja el rastro para recargar antes de que se detenga la resolución. El saldo se mide en USD, igual que los planes (BASIC cuesta $15/mes con 5 threads), así que el umbral del código va directo en dólares. Para guardias on-call con escalado formal, combínalo con PagerDuty u Opsgenie; para visibilidad del equipo, un webhook basta.
Crea el webhook de Discord
El webhook se configura una sola vez desde el propio servidor. Sigue estos pasos en orden:
- Abre la configuración de tu servidor de Discord
- Entra en Integraciones -> Webhooks
- Haz clic en Nuevo webhook y ponle el nombre "Alertas CaptchaAI"
- Copia la URL del webhook
- Guárdala como variable de entorno
DISCORD_WEBHOOK_URL
Con esa URL en el entorno, cualquiera de los dos clientes de abajo puede publicar embeds.
Python: sistema de alertas para CaptchaAI
Esta clase encapsula el formato del embed (colores por gravedad, footer y timestamp) y expone un método por cada tipo de aviso. PipelineMonitor guarda una ventana de cinco minutos y respeta un cooldown para no saturar el canal.
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()
Los umbrales viven en un solo sitio: el saldo avisa por debajo de $10 (y sube a gravedad error por debajo de $2), y la tasa de error salta al superar el 15 % en la ventana móvil. Ajústalos al volumen y a los threads de tu plan. Los métodos queue_alert, daily_summary y solve_recovered cubren el resto del ciclo: cola pendiente, un balance diario con tasa de éxito y latencia, y el aviso de recuperación cuando la resolución vuelve a la normalidad tras un incidente.
JavaScript: cliente de webhook para Discord
Si tu pipeline corre en Node.js, la misma lógica cabe en unas funciones con Axios. El bucle de saldo consulta res.php con action=getbalance cada cinco minutos y solo avisa cuando baja del umbral.
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 };
Cómo se ven las alertas en el canal
Así se muestran los tres embeds más habituales:
Saldo bajo:
💰 Alerta de saldo El saldo de CaptchaAI es $8.42 Umbral: $10.00 | Gravedad: WARNING
Pico de error:
⚠️ Pico de tasa de error Tasa de error: 22.5% (45/200) •
ERROR_CAPTCHA_UNSOLVABLE: 30 •TIMEOUT: 15
Resumen diario:
📈 Resumen diario de CAPTCHA 12,450 tareas procesadas Tasa de éxito: 95.2% | Latencia promedio: 22.4 s | Saldo: $142.30
Solución de problemas frecuentes
| Problema | Causa | Solución |
|---|---|---|
| 400 Bad Request | Estructura de embed no válida | Verifica el formato del array de campos; asegúrate de que todos los valores sean strings |
| Rate limiting (429) | Demasiados mensajes por minuto | Agrega un cooldown entre alertas (mínimo 5 minutos) |
| Webhook eliminado | Alguien lo eliminó del servidor | Crea un nuevo webhook y actualiza la variable de entorno |
| Los embeds no se muestran | Falta el array embeds |
Envuelve el objeto embed en {"embeds": [...]} |
Preguntas frecuentes
¿Cada cuánto conviene consultar el saldo de CaptchaAI?
Cada cinco minutos equilibra bien reaccionar a tiempo sin bombardear la API. El ejemplo usa setInterval de 300000 ms más un cooldown para no repetir el mismo aviso.
¿Cómo evito que Discord me limite (429) al enviar muchas alertas?
Deja al menos cinco minutos de cooldown entre mensajes del mismo tipo y agrupa los eventos en un solo embed con varios campos, en vez de uno por cada tarea fallida.
¿Puedo separar las alertas por gravedad en varios canales?
Sí. Crea un webhook por canal (por ejemplo #saldo y #errores) y pásalo al constructor DiscordCaptchaAlerts. Los colores ya distinguen success, warning, error e info.
¿Discord me sirve para guardias on-call?
Como canal de visibilidad del equipo, sí. Para escalado con turnos y confirmación de recepción, apóyate en PagerDuty u Opsgenie y deja Discord como aviso secundario.
Guías relacionadas
- Notificaciones de Slack para pipelines CAPTCHA
- Alertas con PagerDuty para CaptchaAI
- Referencia de códigos de error de CaptchaAI