Tutorials

Discord Webhook Alerts for CAPTCHA Pipeline Status

Discord is where many developer teams already communicate. Instead of building a separate alerting UI, push CAPTCHA pipeline status directly to a Discord channel — rich embeds for balance, errors, and daily summaries.

Setup

  1. Open your Discord server settings
  2. Go to Integrations → Webhooks
  3. Click New Webhook and name it "CaptchaAI Alerts"
  4. Copy the webhook URL
  5. Store it as DISCORD_WEBHOOK_URL environment variable

Python — Discord Alert System

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 — Discord Webhook Client

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

Discord Message Examples

Balance warning:

💰 Balance Alert CaptchaAI balance is $8.42 Threshold: $10.00 | Severity: WARNING

Error spike:

⚠️ Error Rate Spike Error rate: 22.5% (45/200) • ERROR_CAPTCHA_UNSOLVABLE: 30 • TIMEOUT: 15

Daily summary:

📈 Daily CAPTCHA Summary 12,450 tasks processed Success Rate: 95.2% | Avg Latency: 22.4s | Balance: $142.30

Troubleshooting

Issue Cause Fix
400 Bad Request Invalid embed structure Check fields array format; ensure all values are strings
Rate limited (429) Too many messages per minute Add cooldown between alerts (5 min minimum)
Webhook deleted Someone removed it from server Create a new webhook; update env var
Embeds not showing embeds array missing Wrap embed object in {"embeds": [...]}

FAQ

How many Discord webhooks can I have per channel?

15 per channel. Create one webhook for CaptchaAI and reuse it for all alert types.

Can I @mention users or roles in alerts?

Yes. Add "content": "<@USER_ID>" or "content": "<@&ROLE_ID>" to the webhook payload for critical alerts.

Should I use Discord for production alerting?

Discord works well as a secondary notification channel. For on-call paging, use PagerDuty or Opsgenie. For team visibility, Discord is excellent.

Next Steps

Get pipeline alerts where your team already lives — start with a CaptchaAI API key and connect to Discord.

Related guides:

Full Working Code

Complete runnable examples for this article in Python, Node.js, PHP, Go, Java, C#, Ruby, Rust, Kotlin & Bash.

View on GitHub →

Discussions (0)

No comments yet.