Tutorials

Discord-Webhook-Benachrichtigungen für den CAPTCHA-Pipeline-Status

Eine CAPTCHA-Pipeline fällt selten mit einem lauten Knall aus: Das Guthaben läuft leer, die Fehlerrate steigt schleichend, die Warteschlange staut sich – und niemand bemerkt es, bis die ersten Aufträge scheitern. Ein Discord-Webhook schließt diese Lücke. Er schiebt Guthabenwarnungen, Fehlerspitzen und Tagesberichte direkt in den Kanal, in dem Ihr Team ohnehin arbeitet – ohne eigenes Dashboard, mit wenigen Zeilen Code.

Webhook in Discord anlegen

  1. Öffnen Sie die Einstellungen Ihres Discord-Servers
  2. Gehen Sie zu Integrationen → Webhooks
  3. Klicken Sie auf Neuer Webhook und benennen Sie ihn „CaptchaAI Alerts“
  4. Kopieren Sie die Webhook-URL
  5. Hinterlegen Sie sie als Umgebungsvariable DISCORD_WEBHOOK_URL

Tipp: pro Umgebung ein eigener Webhook – ein Kanal für Staging, einer für die Produktion. So bleiben Testrauschen und echte Vorfälle getrennt.

Python: Alert-System für die Pipeline

Die Klasse DiscordCaptchaAlerts kapselt vier Meldungstypen – Guthaben, Fehlerspitze, Warteschlange und Tagesbericht – in farbcodierte Embeds. Jeder Typ hat eine eigene Methode, sodass Ihr Code nur die passende aufruft und sich nicht um das Payload-Format kümmern muss. Der PipelineMonitor sammelt die Ergebnisse der letzten fünf Minuten, prüft Guthaben und Fehlerrate und drosselt Wiederholungen über einen Cooldown.

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()

JavaScript: Webhook-Client für Node.js

Dieselbe Logik als schlanker Node.js-Client: sendDiscordEmbed baut das Embed, die Guthabenprüfung läuft über setInterval alle fünf Minuten.

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 };

Den Monitor dauerhaft betreiben

Damit die Prüfungen greifen, rufen Sie run_checks() in einer Schleife im Abstand von check_interval Sekunden auf – als eigener Prozess, als systemd-Timer oder als geplanter GitLab-CI-Job. Jede gelöste oder gescheiterte CAPTCHA-Abfrage meldet Ihr Code über record(success, error_code) an den PipelineMonitor; dieser behält nur die letzten fünf Minuten und berechnet daraus die Fehlerrate.

Der cooldown von 300 Sekunden stellt sicher, dass Guthaben- und Fehleralarme höchstens alle fünf Minuten erneut feuern – lange genug, damit ein kurzer Ausschlag nicht den halben Kanal füllt. Den Tagesbericht lösen Sie separat einmal täglich aus, etwa per Cron um 8 Uhr, und übergeben die aggregierten Kennzahlen an daily_summary().

So sehen die Meldungen im Kanal aus

Discord rendert die Embeds als kompakte, farbige Karten:

Guthabenwarnung:

Balance-Alarm Das CaptchaAI-Guthaben beträgt 8,42 $ Schwellenwert: 10,00 $ | Schweregrad: WARNUNG

Fehlerspitze:

Spitze der Fehlerrate Fehlerrate: 22,5 % (45/200) • ERROR_CAPTCHA_UNSOLVABLE: 30 • TIMEOUT: 15

Tägliche Zusammenfassung:

📈 Tägliche CAPTCHA-Zusammenfassung 12.450 Aufgaben bearbeitet Erfolgsquote: 95,2 % | Durchschnittliche Latenz: 22,4 s | Guthaben: 142,30 $

Die Farbe signalisiert den Schweregrad auf einen Blick: Grün steht für Erholung und eine gesunde Tagesbilanz, Orange für Warnungen wie ein knappes Guthaben, Rot für Fehlerspitzen. So erkennt das Team die Dringlichkeit schon am farbigen Rand der Karte, noch bevor es den Text liest.

Welche Alarme sich in der Praxis lohnen

Nicht jede Zahl gehört in den Kanal – sonst klickt das Team Meldungen nur noch weg. Vier Signale reichen:

  • Guthaben unter dem Schwellenwert
  • Fehlerrate über 15 % im Fünf-Minuten-Fenster
  • Warteschlange, die sich sichtbar staut
  • Tagesbericht mit Erfolgsquote, Latenz und Kosten

In vielen DACH-Teams läuft die Automatisierung auf einem eigenen Hetzner- oder netcup-Server, ausgerollt über GitLab CI; der Tagesbericht um 8 Uhr ersetzt dort das Nachsehen im Dashboard.

Fehlerbehebung

Problem Ursache Lösung
Discord-Webhook liefert HTTP 400 Payload-Format falsch oder Embed zu groß Nachricht kürzen und Embed-Struktur validieren
Rate-Limit (HTTP 429) Zu viele Nachrichten pro Minute Cooldown von mindestens 5 Minuten zwischen Alarmen einhalten
Keine Benachrichtigung bei CAPTCHA-Fehler Fehler löst keine Bedingung aus Schwellwerte für die Fehlerrate und Logging-Pfad prüfen
Zu viele Benachrichtigungen Alarmierung ohne Hysterese Mindestabstand zwischen gleichartigen Alarmen einfügen
Webhook liefert 404 Webhook wurde vom Server gelöscht Neuen Webhook anlegen, DISCORD_WEBHOOK_URL aktualisieren

Häufige Fragen

Wie verhindere ich, dass mich der Webhook mit Nachrichten überflutet?

Über einen Cooldown. Der PipelineMonitor merkt sich pro Alarmtyp die letzte Meldung und unterdrückt Wiederholungen für mindestens fünf Minuten – so löst eine Fehlerwelle genau eine Warnung aus, nicht eine pro Durchlauf.

Kann ich bei kritischen Alarmen eine Rolle oder Person taggen?

Ja. Ergänzen Sie im Payload das Feld "content" mit <@USER_ID> oder <@&ROLE_ID>. Discord löst die Erwähnung als Push-Benachrichtigung aus – sinnvoll nur für echte Vorfälle wie ein aufgebrauchtes Guthaben.

Wie oft sollte ich das Guthaben abfragen?

Fünf Minuten reichen in fast allen Fällen. Häufigere Abfragen bringen kaum Nutzen, da sich das Guthaben nur pro gelöster CAPTCHA-Abfrage ändert. Der Schwellenwert (im Beispiel 10 $) sollte früh genug vor dem Leerlauf warnen.

Werden über den Webhook sensible Kontodaten offengelegt?

Nein, solange Sie nur aufbereitete Kennzahlen senden. CAPTCHAAI_API_KEY und Webhook-URL gehören ausschließlich in Umgebungsvariablen, niemals in den Nachrichtentext.

Kann ich Guthaben, Fehler und Tagesbericht in getrennte Kanäle schicken?

Ja. Legen Sie mehrere Webhooks an und übergeben Sie jede URL an eine eigene DiscordCaptchaAlerts-Instanz. So landen laute Betriebsmeldungen und der ruhige Tagesbericht nicht im selben Kanal – praktisch, wenn nur das Bereitschaftsteam die roten Fehleralarme sehen soll.


Verwandte Leitfäden

Kommentare sind für diesen Artikel deaktiviert.