Fällt eine CAPTCHA-Pipeline aus, sollte die zuständige Person es sofort erfahren – nicht erst beim Blick ins Dashboard am nächsten Morgen. Das leistet eine direkte Anbindung an die PagerDuty Events API v2: Ein schlanker Monitor fragt Guthaben und Fehlerquote von CaptchaAI ab und erzeugt bei definierten Schwellenwerten automatisch einen Incident – dedupliziert und selbsttätig wieder aufgelöst, sobald sich die Werte normalisieren. Dieser Leitfaden zeigt die Einrichtung in Python und Node.js.
Welche Ereignisse einen Alert auslösen sollten
Der häufigste Fehler beim Aufsetzen von Alerts ist, alles gleich laut zu machen: Wer nachts wegen einer Warnung geweckt wird, deaktiviert die Benachrichtigungen bald ganz. Ordnen Sie deshalb jeden Zustand vorab einem Schweregrad zu und entscheiden Sie erst dann, ob er jemanden anruft, einen Incident anlegt oder nur protokolliert wird. Die folgende Matrix hat sich als Ausgangspunkt bewährt und lässt sich an Ihren Verbrauch und Ihre Team-Größe anpassen.
| Schweregrad | Bedingung | PagerDuty-Aktion |
|---|---|---|
| Kritisch | Guthaben < 2 $ | On-Call-Engineer per Page alarmieren |
| Kritisch | Alle Worker ausgefallen | On-Call-Engineer per Page alarmieren |
| Hoch | Fehlerquote > 20 % über 5 Min. | Dringenden Incident erstellen |
| Warnung | Guthaben < 10 $ | Incident mit niedriger Dringlichkeit |
| Warnung | Warteschlangentiefe > 100 über 10 Min. | Incident mit niedriger Dringlichkeit |
| Info | Lösungslatenz p95 > 120 s | An bestehenden Incident anhängen oder loggen |
PagerDuty-Dienst und Routing-Key vorbereiten
Der Monitor braucht nur einen einzigen Wert: den Routing-Key. Ihn erhalten Sie, indem Sie in PagerDuty einen Dienst mit Events-API-v2-Integration anlegen. Behandeln Sie ihn wie ein Passwort – legen Sie ihn in einer Umgebungsvariablen ab, niemals fest im Code, und rotieren Sie ihn, wenn er versehentlich in ein Repository gelangt.
| Schritt | Aktion |
|---|---|
| 1 | Legen Sie in PagerDuty einen Dienst „CaptchaAI Pipeline“ an. |
| 2 | Fügen Sie dem Dienst die Integration Events API v2 hinzu. |
| 3 | Kopieren Sie den Routing-Key in die Umgebungsvariable PAGERDUTY_ROUTING_KEY. |
| 4 | Richten Sie eine Eskalationsrichtlinie ein (On-Call → Teamleitung → Management). |
| 5 | Konfigurieren Sie die Benachrichtigungsregeln (Push, SMS, Anruf). |
| 6 | Hinterlegen Sie Wartungsfenster für geplante Ausfallzeiten. |
Testen Sie die Kette einmal aktiv, bevor Sie sich darauf verlassen: Rufen Sie trigger mit einer Test-Zusammenfassung und einem eigenen dedup_key auf, prüfen Sie, ob der Incident samt Benachrichtigung ankommt, und lösen Sie ihn anschließend mit resolve wieder auf. Eine stille Integration, die erst beim echten Ausfall versagt, ist schlimmer als gar keine.
Python: Guthaben und Fehlerquote überwachen
Die Klasse kapselt die drei Aktionen der Events API v2 – trigger, resolve, acknowledge – und kombiniert sie mit einem gleitenden Fenster für die Fehlerquote. Der stabile dedup_key sorgt dafür, dass wiederholte Prüfungen denselben Vorfall aktualisieren statt zu duplizieren. run_checks bündelt beide Prüfungen und wird typischerweise im Minutentakt aufgerufen; record_solve füttert das Fenster nach jeder Lösung mit dem Ergebnis, sodass die Fehlerquote immer die letzten fünf Minuten widerspiegelt.
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)
Node.js: Health-Monitor mit der Events API v2
Dieselbe Logik als PagerDutyAlerter plus CaptchaHealthMonitor: setInterval ruft die Prüfung im 60-Sekunden-Takt auf, mit identischen Schwellenwerten wie im Python-Beispiel.
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 };
Den Monitor sinnvoll betreiben
Betreiben Sie den Wächter als eigenen Prozess neben der Scraping-Flotte – etwa auf einem kleinen Hetzner- oder netcup-Server per systemd. Entscheidend ist die Trennung: Fällt die Pipeline aus, muss der Monitor unabhängig weiterlaufen. Da CaptchaAI Thread-basiert abrechnet und ein leeres Guthaben neue Lösungen stoppt, ist die Guthaben-Schwelle der wichtigste Alert. BASIC (15 $/Monat, 5 Threads) genügt für kleine Läufe, größere Flotten fahren mit ADVANCE (90 $/Monat, 50 Threads) oder höher.
Fehlerbehebung
| Problem | Ursache | Lösung |
|---|---|---|
| Alert wird nicht ausgelöst | Routing-Key falsch oder severity nicht gesetzt |
PAGERDUTY_ROUTING_KEY prüfen, severity auf "error" setzen |
| Keine Incidents in PagerDuty | Health-Monitor läuft nicht | setInterval- bzw. run_checks-Schleife starten, Konsolenausgabe prüfen |
| Doppelte Incidents | dedup_key fehlt oder ändert sich |
dedup_key stabil und eindeutig halten (z. B. "captcha-error-rate") |
| Resolve schließt Incident nicht | Incident bereits manuell geschlossen | PagerDuty-Status prüfen; resolve ist idempotent |
| Balance-Warnung bleibt aktiv | resolve für captcha-balance-warning fehlt |
Nach Normalisierung des Guthabens explizit auflösen |
Häufige Fragen
Wie fragt der Monitor das CaptchaAI-Guthaben ab?
Über den getbalance-Aufruf an https://ocr.captchaai.com/res.php. Bei status: 1 steht der Kontostand im Feld request und wird gegen die Schwellen 2 $ und 10 $ geprüft.
Ab welchem Guthaben sollte ich alarmieren?
Zwei Stufen haben sich bewährt: eine Warnung bei unter 10 $ als Erinnerung zum Aufladen und ein kritischer Incident bei unter 2 $. Passen Sie die Werte an Ihren täglichen Verbrauch an.
Kann ich Alerts nach Schweregrad an verschiedene Teams routen?
Ja. Der severity-Wert (critical, error, warning, info) steuert zusammen mit der Eskalationsrichtlinie, wer benachrichtigt wird – kritische Alerts direkt on-call, Warnungen nur als niedrig-dringlicher Incident.
Lösen sich Incidents automatisch wieder auf?
Ja, sofern Sie denselben dedup_key verwenden. Sinkt die Fehlerquote unter 5 % oder erholt sich das Guthaben, sendet der Monitor ein resolve-Event und schließt den Vorfall.
Verwandte Leitfäden
Kein CAPTCHA-Ausfall soll unbemerkt bleiben: CaptchaAI-API-Schlüssel holen und in wenigen Minuten mit PagerDuty verbinden.