PagerDuty convierte un fallo silencioso en tu pipeline de CAPTCHA —el saldo que se agota, los errores que se disparan, los workers que dejan de responder— en una llamada al teléfono de quien está de guardia. En esta guía conectas la API de eventos v2 de PagerDuty a tu monitoreo de CaptchaAI para que el aviso llegue en segundos y con contexto suficiente para diagnosticar sin abrir un solo registro.
El correo no sirve: nadie mira la bandeja a las 3 de la madrugada. PagerDuty enruta, escala y silencia el ruido; tu trabajo es decidir qué señales merecen despertar a una persona y cuáles solo necesitan quedar registradas.
Las cuatro señales que conviene vigilar
Un pipeline de resolución de CAPTCHA se degrada de formas concretas, y tu monitor debe observar estas señales:
- Saldo de la cuenta. Si el saldo prepago llega a cero, cada solicitud a la API falla. Es la única señal que justifica una llamada inmediata, porque detiene todo el flujo.
- Tasa de error. Un salto sostenido en las resoluciones fallidas suele indicar un cambio en el sitio objetivo o una degradación del servicio. Se mide sobre una ventana móvil, no por un fallo aislado.
- Workers activos. Si todos los procesos worker se caen, la cola deja de vaciarse aunque el saldo y la API estén bien.
- Latencia de resolución. Un p95 que se dispara no rompe nada de inmediato, pero anticipa colas que crecen y entregas que se incumplen.
Asigna a cada señal la gravedad correcta: despertar a alguien por una latencia alta genera fatiga; tratar un saldo en cero como advertencia genera pérdida de datos.
Estrategia de alertas por gravedad
| Gravedad | Condición | Acción en PagerDuty |
|---|---|---|
| Crítica | Saldo < $2 | Llamar al ingeniero de guardia |
| Crítica | Todos los workers caídos | Llamar al ingeniero de guardia |
| Alta | Tasa de error > 20% durante 5 min | Crear incidente urgente |
| Advertencia | Saldo < $10 | Crear incidente de baja urgencia |
| Advertencia | Profundidad de cola > 100 durante 10 min | Crear incidente de baja urgencia |
| Información | Latencia de resolución p95 > 120 s | Añadir a un incidente existente o registrar |
Los umbrales de saldo van en USD porque el saldo de CaptchaAI se factura en dólares; ajústalos a tu consumo real. Un pipeline que resuelve miles de CAPTCHA por hora quema saldo mucho más rápido que uno con pocas comprobaciones diarias.
Python: alertas con la API de eventos de PagerDuty v2
El cliente siguiente encapsula las tres acciones de PagerDuty —trigger, acknowledge y resolve— junto a un monitor que registra cada resolución en una ventana móvil de cinco minutos. La comprobación de saldo usa el endpoint res.php con action=getbalance.
import os
import time
import hashlib
import requests
from datetime import datetime
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
PAGERDUTY_ROUTING_KEY = os.environ["PAGERDUTY_ROUTING_KEY"]
session = requests.Session()
class CaptchaPagerDuty:
EVENTS_URL = "https://events.pagerduty.com/v2/enqueue"
def __init__(self, routing_key):
self.routing_key = routing_key
def trigger(self, summary, severity="error", source="captcha-pipeline",
details=None, dedup_key=None):
"""Trigger a new PagerDuty incident."""
payload = {
"routing_key": self.routing_key,
"event_action": "trigger",
"payload": {
"summary": summary,
"severity": severity, # critical, error, warning, info
"source": source,
"timestamp": datetime.utcnow().isoformat() + "Z",
"custom_details": details or {}
}
}
if dedup_key:
payload["dedup_key"] = dedup_key
resp = requests.post(self.EVENTS_URL, json=payload, timeout=10)
resp.raise_for_status()
return resp.json()
def resolve(self, dedup_key):
"""Resolve an existing incident."""
payload = {
"routing_key": self.routing_key,
"event_action": "resolve",
"dedup_key": dedup_key
}
resp = requests.post(self.EVENTS_URL, json=payload, timeout=10)
resp.raise_for_status()
return resp.json()
def acknowledge(self, dedup_key):
"""Acknowledge an existing incident."""
payload = {
"routing_key": self.routing_key,
"event_action": "acknowledge",
"dedup_key": dedup_key
}
resp = requests.post(self.EVENTS_URL, json=payload, timeout=10)
resp.raise_for_status()
return resp.json()
pagerduty = CaptchaPagerDuty(PAGERDUTY_ROUTING_KEY)
class CaptchaMonitor:
def __init__(self):
self.error_window = [] # (timestamp, is_error)
self.window_size = 300 # 5 minutes in seconds
def record_solve(self, success):
now = time.time()
self.error_window.append((now, not success))
# Prune old entries
self.error_window = [
(t, e) for t, e in self.error_window
if now - t < self.window_size
]
@property
def error_rate(self):
if not self.error_window:
return 0.0
errors = sum(1 for _, e in self.error_window if e)
return errors / len(self.error_window)
def check_balance(self):
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 None
return float(data["request"])
def run_checks(self):
"""Run all monitoring checks and trigger alerts."""
# Check balance
balance = self.check_balance()
if balance is not None:
if balance < 2:
pagerduty.trigger(
summary=f"CaptchaAI balance critically low: ${balance:.2f}",
severity="critical",
dedup_key="captcha-balance-critical",
details={"balance": balance, "threshold": 2}
)
elif balance < 10:
pagerduty.trigger(
summary=f"CaptchaAI balance low: ${balance:.2f}",
severity="warning",
dedup_key="captcha-balance-warning",
details={"balance": balance, "threshold": 10}
)
else:
# Resolve if balance recovered
try:
pagerduty.resolve("captcha-balance-critical")
pagerduty.resolve("captcha-balance-warning")
except Exception:
pass # No incident to resolve
# Check error rate
rate = self.error_rate
if rate > 0.20:
total = len(self.error_window)
errors = sum(1 for _, e in self.error_window if e)
pagerduty.trigger(
summary=f"CaptchaAI error rate {rate:.0%} "
f"({errors}/{total} in 5 min)",
severity="error",
dedup_key="captcha-error-rate-high",
details={
"error_rate": round(rate, 3),
"total_tasks": total,
"failed_tasks": errors,
"window_seconds": self.window_size
}
)
elif rate < 0.05 and len(self.error_window) > 10:
try:
pagerduty.resolve("captcha-error-rate-high")
except Exception:
pass
monitor = CaptchaMonitor()
# After each solve:
# monitor.record_solve(success=True)
# Run checks every 60 seconds:
# while True:
# monitor.run_checks()
# time.sleep(60)
Fíjate en el patrón de la dedup_key: cada tipo de alerta usa una clave estable (captcha-balance-critical, captcha-error-rate-high). PagerDuty agrupa por esa clave, así que cien comprobaciones fallidas seguidas se consolidan en un único incidente. Cuando el saldo se recupera, esa misma clave resuelve el incidente de forma automática.
JavaScript: monitor de salud e integración con PagerDuty
Si tu pipeline corre sobre Node.js, la lógica se traslada casi línea por línea. El monitor mantiene los resultados recientes en memoria, calcula la tasa de error sobre la ventana configurada y dispara o resuelve incidentes con las mismas claves de deduplicación.
const axios = require("axios");
const API_KEY = process.env.CAPTCHAAI_API_KEY;
const PD_ROUTING_KEY = process.env.PAGERDUTY_ROUTING_KEY;
const PD_EVENTS_URL = "https://events.pagerduty.com/v2/enqueue";
class PagerDutyAlerter {
constructor(routingKey) {
this.routingKey = routingKey;
}
async trigger(summary, severity = "error", details = {}, dedupKey = null) {
const payload = {
routing_key: this.routingKey,
event_action: "trigger",
payload: {
summary,
severity,
source: "captcha-pipeline",
timestamp: new Date().toISOString(),
custom_details: details,
},
};
if (dedupKey) payload.dedup_key = dedupKey;
const resp = await axios.post(PD_EVENTS_URL, payload, { timeout: 10000 });
return resp.data;
}
async resolve(dedupKey) {
await axios.post(PD_EVENTS_URL, {
routing_key: this.routingKey,
event_action: "resolve",
dedup_key: dedupKey,
}, { timeout: 10000 });
}
}
const alerter = new PagerDutyAlerter(PD_ROUTING_KEY);
class CaptchaHealthMonitor {
constructor(windowMs = 300000) {
this.results = [];
this.windowMs = windowMs;
}
record(success) {
this.results.push({ time: Date.now(), success });
const cutoff = Date.now() - this.windowMs;
this.results = this.results.filter((r) => r.time > cutoff);
}
get errorRate() {
if (this.results.length === 0) return 0;
const errors = this.results.filter((r) => !r.success).length;
return errors / this.results.length;
}
async checkAndAlert() {
// Balance check
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 < 2) {
await alerter.trigger(
`CaptchaAI balance critically low: $${balance.toFixed(2)}`,
"critical",
{ balance },
"captcha-balance-critical"
);
} else if (balance < 10) {
await alerter.trigger(
`CaptchaAI balance low: $${balance.toFixed(2)}`,
"warning",
{ balance },
"captcha-balance-warning"
);
} else {
await alerter.resolve("captcha-balance-critical").catch(() => {});
await alerter.resolve("captcha-balance-warning").catch(() => {});
}
}
} catch (err) {
console.error("Balance check failed:", err.message);
}
// Error rate check
const rate = this.errorRate;
if (rate > 0.2 && this.results.length > 10) {
await alerter.trigger(
`CaptchaAI error rate: ${(rate * 100).toFixed(1)}%`,
"error",
{ errorRate: rate, totalTasks: this.results.length },
"captcha-error-rate"
);
} else if (rate < 0.05 && this.results.length > 10) {
await alerter.resolve("captcha-error-rate").catch(() => {});
}
}
}
const monitor = new CaptchaHealthMonitor();
// Run checks every 60 seconds
setInterval(() => monitor.checkAndAlert(), 60000);
module.exports = { monitor, alerter };
Un setInterval de 60 segundos basta para la mayoría de los pipelines.
Configuración paso a paso de PagerDuty
Prepara el servicio en PagerDuty antes de enviar nada desde el código:
| Paso | Acción |
|---|---|
| 1 | Crea un servicio en PagerDuty llamado "CaptchaAI Pipeline" |
| 2 | Añade la integración Events API v2 al servicio |
| 3 | Copia la clave de enrutamiento a la variable de entorno PAGERDUTY_ROUTING_KEY |
| 4 | Define una política de escalamiento (guardia → líder de equipo → responsable) |
| 5 | Configura las reglas de notificación (push, SMS, llamada) |
| 6 | Añade ventanas de mantenimiento para las paradas planificadas |
La clave de enrutamiento (routing key) es lo único que el código necesita; guárdala como variable de entorno, nunca en el código.
Solución de problemas frecuentes
| Problema | Causa | Solución |
|---|---|---|
| La alerta no se dispara | Clave de enrutamiento incorrecta | Verifica que coincida con la integración Events API del servicio |
| Incidentes duplicados | Falta la dedup_key |
Asigna siempre una clave de deduplicación estable por tipo de alerta |
| Avalancha de alertas | Sin agrupación entre disparos | La dedup_key de PagerDuty suprime los duplicados; asegúrate de usarla |
| La resolución automática no funciona | Las claves de deduplicación no coinciden | Usa exactamente la misma clave al resolver que al disparar |
Un caso real: la agencia que factura en USD
Imagina una agencia que ejecuta scraping y QA para varios clientes y corre sus lotes de madrugada. Con el plan BASIC ($15/mes, 5 threads) el costo mensual es predecible en dólares, algo que agradecen los equipos que facturan en monedas volátiles. Pero un lote nocturno que agota el saldo prepago sin aviso arruina la entrega del día siguiente. Con la alerta de saldo < $2 enrutada como crítica, la persona de guardia recibe la llamada, recarga y el lote continúa. El mismo patrón sirve para un pipeline que vigila citas en portales tipo BLS, donde una ventana perdida no se recupera. Respeta siempre los términos de servicio y la normativa de protección de datos aplicable.
Preguntas frecuentes
¿Cada cuánto debo ejecutar las comprobaciones?
Un ciclo de 60 segundos equilibra frescura y ruido para casi todos los pipelines. Si un saldo en cero te resulta muy costoso, ejecuta solo esa comprobación con más frecuencia.
¿Qué gravedad le asigno a un saldo bajo?
Depende del umbral: < $10 es una advertencia de baja urgencia (crea el incidente, pero no llama a nadie); < $2 es crítico y justifica una llamada, porque a cero se detiene todo el pipeline.
¿Sirve esta integración si ya uso Datadog o New Relic?
Sí. Tanto Datadog como New Relic traen integraciones nativas con PagerDuty; úsalas si ya envías métricas allí. La integración directa por API de esta guía es mejor cuando quieres control fino de cada incidente.
¿La deduplicación evita de verdad la avalancha de alertas?
Sí, siempre que uses una dedup_key estable por tipo de alerta. PagerDuty consolida todos los disparos con la misma clave en un único incidente abierto, y la resolución automática se activa cuando la señal vuelve a la normalidad. Durante la investigación, acknowledge detiene las notificaciones sin cerrar el incidente; resolve lo cierra por completo.
Artículos relacionados
- Construir pipelines de CAPTCHA para clientes
- Automatización responsable con CaptchaAI
- Monitoreo con métricas y alertas de Datadog
Recibe el aviso en el instante en que tu pipeline de CAPTCHA falle. Empieza con tu API key de CaptchaAI y conéctalo a PagerDuty.
Guías relacionadas:
- Monitoreo con New Relic APM
- Referencia de códigos de error