Drei Alarme genügen für den Anfang: eine endgültig fehlgeschlagene Lösung, ein Guthaben unter der Schwelle und eine Fehlerquote, die im gleitenden Fenster ausreißt. Wer genau diese drei über einen Incoming Webhook nach Slack schickt, bemerkt eine gestörte CAPTCHA-Pipeline innerhalb von Minuten – und nicht erst, wenn am nächsten Morgen die Ergebnistabelle des Nacht-Jobs leer ist. Dieser Leitfaden baut den Alarmpfad auf: Webhook, zentraler Helper in Python, drei Alarme, Node.js-Variante und ein Tagesdigest, der niemanden weckt.
Was in den Slack-Alarmkanal gehört – und was nicht
Ein Alarmkanal lebt von seiner Trefferquote. Sortieren Sie CAPTCHA-Ereignisse deshalb vorab in drei Klassen:
- Sofort melden: Die Pipeline steht – der API-Schlüssel wird abgelehnt (
ERROR_WRONG_USER_KEY), das Guthaben ist aufgebraucht (ERROR_ZERO_BALANCE), Lösungen schlagen dauerhaft fehl. - Aggregiert melden: einzelne fehlgeschlagene Tasks, Timeouts, erneute Versuche. Sie gehören in eine Fehlerquote, nicht in eine eigene Nachricht.
- Nur protokollieren: jede erfolgreiche Lösung und jedes
CAPCHA_NOT_READYwährend des Pollings. Dafür ist das Log da, nicht Slack.
Genauso wichtig ist die Kanalstruktur: #captcha-alerts für Störungen, #captcha-daily für die Zusammenfassung. Wo außerhalb der Kernarbeitszeit eine Bereitschaft läuft, entscheidet genau diese Trennung darüber, ob nachts jemand geweckt wird.
Schritt 1: Incoming Webhook in Slack anlegen
- Öffnen Sie api.slack.com/apps und erstellen Sie eine neue App.
- Aktivieren Sie Incoming Webhooks.
- Wählen Sie Neuen Webhook zum Workspace hinzufügen und danach den Zielkanal.
- Kopieren Sie die Webhook-URL.
Behandeln Sie diese URL wie ein Passwort: Wer sie besitzt, schreibt in Ihren Kanal. Sie gehört in eine Umgebungsvariable (SLACK_WEBHOOK_URL) oder in GitLab CI in eine maskierte Variable – nicht ins Repository. In den Beispielen steht sie nur der Lesbarkeit halber im Klartext.
Schritt 2: Zentraler Alarm-Helper in Python
Alle Meldungen laufen über eine einzige Funktion. Das hält Farbwerte, Zeitstempel und Timeout an einer Stelle – und erspart vier Änderungen, wenn sich das Nachrichtenformat später ändert.
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
Auf drei Stellen kommt es an. timeout=10 verhindert, dass ein hängender Webhook den Solve-Worker blockiert. Der Farbwert steuert den Balken neben der Nachricht – Rot für Störungen, Orange für Warnungen, Grün für den Digest. Und fields rendert Slack zweispaltig: Task-ID, CAPTCHA-Typ und Fehlercode auf einen Blick.
Alarm 1: Endgültig fehlgeschlagene Lösung
Diese Meldung feuert erst, wenn eine Aufgabe final gescheitert ist – nicht bei jedem Zwischenstand des Pollings.
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")
Übernehmen Sie den Fehlercode wörtlich, statt ihn zu übersetzen – nur so ist er nachschlagbar und über mehrere Vorfälle vergleichbar. Die mitgeschickte Ziel-URL beantwortet zugleich die erste Rückfrage im Kanal: Betrifft die Störung alle Ziele oder nur eines?
Alarm 2: Guthaben unter der Schwelle
CaptchaAI rechnet pro gleichzeitigem Thread ab, nicht pro Lösung: Schon BASIC (15 $/Monat, 5 Threads) enthält unbegrenzte Lösungen pro Thread. Das Guthaben ist damit kein Verbrauchszähler pro CAPTCHA, sondern die Deckung für die nächste Verlängerung – und eine Warnung mit Vorlauf ist mehr wert als eine Fehlermeldung mitten im Lauf.
def check_balance_alert(api_key, threshold=5.0):
"""Alert when balance drops 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")
Der Timer läuft als Daemon-Thread im Fünf-Minuten-Takt mit; entscheidend ist, dass er dieselbe Lebensdauer hat wie Ihre Worker. Auf einem kleinen VPS bei Hetzner oder netcup genügt die kleinste Instanz. Wer ohnehin systemd nutzt, legt die Guthabenprüfung besser als eigenen Timer-Service an – dann überlebt die Überwachung auch einen Neustart des Scrapers.
Alarm 3: Fehlerquote im gleitenden Fenster
Einzelne Fehlschläge sind Betriebsrauschen, erst ihre Häufung ist ein Vorfall. Der dritte Alarm beobachtet deshalb eine Quote statt eines Ereignisses.
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
Die Klasse meldet frühestens ab 20 erfassten Tasks, betrachtet die letzten 50 und schweigt danach 300 Sekunden lang. Die Schwelle von 30 % ist ein Startwert – messen Sie eine Woche mit, bevor Sie sie festschreiben. Sinnvoll ist ein eigenes Fenster pro CAPTCHA-Typ: Cloudflare Turnstile wird typischerweise in unter 10 Sekunden gelöst, reCAPTCHA v2 darf länger brauchen. Beides in einem Topf zu messen verwischt genau die Abweichung, die Sie sehen wollen.
Derselbe Alarmweg in Node.js
Läuft Ihre Automatisierung unter Node.js, bleibt die Struktur identisch – nur der Transport wechselt zu axios:
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);
Zwei Unterschiede zur Python-Variante: setInterval ersetzt den Timer-Thread, und Object.entries baut die Felder direkt aus einem Objekt. Beenden Sie das Intervall beim Herunterfahren mit clearInterval – sonst hält der Prozess den Event-Loop offen.
Tagesdigest statt Dauerfeuer
Der Digest ist der Gegenpol zu den Störungsalarmen: eine Nachricht pro Tag, in Grün, mit Trend statt Aufregung.
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%}",
},
)
Planen Sie den Versand als eigenen Job: Ein cron-Eintrag oder eine geplante GitLab-CI-Pipeline um 9 Uhr reicht. Setzen Sie die Zeitzone auf Europe/Berlin – sonst steht im Digest UTC und die Zahlen passen nicht zum Arbeitstag.
Betrieb: Schwellenwerte und Eskalation
| Ereignis | Kanal | Startschwelle | Reaktion |
|---|---|---|---|
| Lösung endgültig fehlgeschlagen | #captcha-alerts |
jeder finale Fehlschlag | Fehlercode prüfen, Task erneut einreichen |
| Guthaben niedrig | #captcha-alerts |
5 $ Restguthaben | Plan verlängern, bevor der Lauf stoppt |
| Fehlerquote hoch | #captcha-alerts |
30 % über 50 Tasks | Sitekey, Page-URL und Proxy-Pfad prüfen |
| Tageszusammenfassung | #captcha-daily |
einmal täglich | Trend gegen die Vorwoche vergleichen |
Zwei Regeln halten den Kanal ruhig: Jeder Alarm braucht einen Cooldown, und jeder Alarm braucht einen Adressaten. Eine Meldung ohne Zuständigen wird nach zwei Wochen ignoriert – und mit ihr die daneben stehende, die wirklich wichtig war.
Fehlerbehebung
| Symptom | Ursache | Lösung |
|---|---|---|
| Webhook antwortet mit 403 | URL ungültig oder widerrufen | Webhook in Slack neu erzeugen und die Variable aktualisieren |
| Kanal wird geflutet | kein Cooldown gesetzt | Sperrzeit zwischen gleichartigen Meldungen erhöhen |
| Meldungen kommen verspätet an | Webhook-Timeout | Timeout auf 10 s begrenzen und den Versand in eine Warteschlange auslagern |
| Im Kanal kommt nichts an | Webhook zeigt auf einen anderen Kanal | Kanalzuordnung der Slack-App prüfen |
FAQ
Wo bewahre ich die Webhook-URL sicher auf?
In einer Umgebungsvariablen oder im Secret-Store Ihrer CI – nie im Repository. Wer die URL besitzt, schreibt in Ihren Kanal; im Zweifel löschen Sie den Webhook in Slack und legen einen neuen an.
Welche Schwellenwerte sind für den Start sinnvoll?
30 % Fehlerquote über ein Fenster von 50 Tasks, 300 Sekunden Cooldown, Guthabenwarnung bei 5 $. Nach zwei Wochen Betrieb kennen Sie Ihren Normalzustand und passen die Werte an Ihre CAPTCHA-Typen an.
Funktioniert derselbe Aufbau mit Microsoft Teams?
Ja. Teams nimmt ebenfalls eingehende Webhooks entgegen, nur der JSON-Aufbau unterscheidet sich – Karten statt attachments. Schwellen, Cooldown und Digest bleiben identisch. In vielen deutschen Unternehmen ist Teams gesetzt, während die Entwicklung parallel in Slack arbeitet.
Soll der Alarm den fehlgeschlagenen Task automatisch wiederholen?
Trennen Sie beides. Die Wiederholungslogik gehört in den Worker – zwei erneute Versuche mit exponentiellem Backoff –, Slack meldet erst, wenn auch der letzte Versuch scheitert. Sonst alarmieren Sie Vorgänge, die sich längst selbst erledigt haben.
Reicht ein kleiner VPS für den Monitoring-Prozess?
Ja. Pro Ereignis fällt ein einziger HTTP-POST an; die kleinste Instanz bei Hetzner oder netcup trägt das mühelos. Wichtig sind nur eine gesetzte Zeitzone und ein Prozess-Manager, der den Dienst nach einem Neustart hochfährt.
CAPTCHA-Ereignisse ab heute im Team-Kanal
Holen Sie sich Ihren API-Schlüssel unter captchaai.com, hängen Sie den Alarm-Helper an Ihren Solve-Worker und schicken Sie die erste Testmeldung nach #captcha-alerts.