Cara paling praktis memantau pipeline CAPTCHA adalah mengirim statusnya ke tempat tim Anda sudah berkumpul — bukan ke dashboard terpisah yang jarang dibuka. Discord webhook memungkinkan CaptchaAI mengirim embed kaya langsung ke channel: alert saldo menipis, lonjakan error, antrean menumpuk, sampai ringkasan harian, semuanya lewat satu integrasi Python atau Node.js yang bisa dipasang dalam hitungan menit.
Pendekatan ini cocok untuk tim automation kecil atau freelance yang menjalankan pipeline scraping maupun price-monitoring di region seperti ap-southeast-1 (Singapura) atau ap-southeast-3 (Jakarta) — satu channel Discord yang sudah dipakai untuk koordinasi harian cukup jadi pusat notifikasi, tanpa perlu berlangganan tool monitoring terpisah.
Kenapa Push Alert ke Discord, Bukan Dashboard Terpisah?
Membangun UI monitoring sendiri butuh waktu yang biasanya tidak sebanding dengan manfaatnya untuk tim kecil. Discord sudah punya struktur channel, riwayat pesan, dan notifikasi mobile — tinggal diarahkan. Empat jenis alert di bawah ini menutup kebutuhan paling umum: saldo API menipis, error rate melonjak, antrean task menumpuk, dan ringkasan performa harian. Semuanya dikirim sebagai embed berwarna sehingga severity langsung terlihat tanpa harus membuka log.
Cara Membuat Webhook Discord untuk CaptchaAI
Sebelum kode alert bisa mengirim apa pun, webhook-nya harus disiapkan dulu di sisi server Discord:
- Buka pengaturan server Discord Anda
- Masuk ke Integrations → Webhooks
- Klik New Webhook, beri nama "CaptchaAI Alerts" agar mudah dikenali di daftar integrasi
- Salin URL webhook yang dihasilkan
- Simpan URL tersebut sebagai environment variable
DISCORD_WEBHOOK_URL— jangan hardcode di kode maupun tempel langsung di channel publik
Implementasi Python: Class Alert Multi-Severity
Class DiscordCaptchaAlerts di bawah membungkus lima jenis notifikasi (balance, error spike, queue backup, daily summary, dan pipeline recovered) dalam satu helper send_embed, dengan warna embed yang berbeda per severity. PipelineMonitor di baris-baris setelahnya menyimpan hasil solve 5 menit terakhir dan hanya memicu alert kalau cooldown-nya sudah lewat — jadi channel tidak kebanjiran pesan setiap kali satu task gagal.
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()
Angka 0.15 (ambang error rate) dan 10 (ambang saldo dalam USD) adalah default yang wajar untuk volume solve menengah — sesuaikan dengan thread plan dan buffer saldo yang Anda pakai. cooldown = 300 yang sama dipakai untuk kedua jenis alert supaya channel tidak menerima pesan berulang selama masalah belum selesai.
Implementasi Node.js: Client Webhook Ringan
Kalau stack Anda JavaScript/Node.js, versi ini lebih ringkas: tiga fungsi alert (alertBalance, alertErrorSpike, sendDailySummary) plus satu loop setInterval yang mengecek saldo tiap 5 menit dan otomatis mengirim alert saat saldo turun di bawah ambang.
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 };
module.exports di baris terakhir membuat ketiga fungsi bisa dipanggil dari worker atau job queue lain, jadi tidak perlu duplikasi logic pengiriman embed di setiap script yang butuh alert.
Pemecahan Masalah
Empat masalah ini paling sering muncul saat pertama kali menyambungkan webhook, sebelum sempat melihat satu pun embed masuk ke channel:
| Masalah | Penyebab | Perbaikan |
|---|---|---|
| 400 Bad Request | Struktur embed tidak valid | Periksa format array fields; pastikan semua value adalah string |
| Rate limited (429) | Terlalu banyak pesan per menit | Tambahkan cooldown antar alert (minimal 5 menit) |
| Webhook dihapus | Seseorang menghapus dari server | Buat webhook baru; update env var |
| Embed tidak muncul | Array embeds tidak ada |
Bungkus objek embed dalam {"embeds": [...]} |
Kalau webhook masih sering kena rate limit meski cooldown sudah dipasang, penyebabnya biasanya alert dikirim terlalu granular — gabungkan beberapa event kecil jadi satu embed ringkasan per interval, alih-alih satu pesan per kejadian.
Contoh Tampilan Alert di Discord
Setelah setup di atas beres, begini bentuk embed yang muncul di channel:
Alert saldo:
💰 Balance Alert CaptchaAI balance is $8.42 Threshold: $10.00 | Severity: WARNING
Lonjakan error:
⚠️ Error Rate Spike Error rate: 22.5% (45/200) •
ERROR_CAPTCHA_UNSOLVABLE: 30 •TIMEOUT: 15
Ringkasan harian:
📈 Daily CAPTCHA Summary 12.450 task diproses Success Rate: 95.2% | Avg Latency: 22.4s | Balance: $142.30
Warna embed (hijau/oranye/merah/biru) langsung menunjukkan severity tanpa perlu membuka thread — cocok dipantau sekilas lewat HP saat sedang di luar meja kerja.
Discord vs PagerDuty: Kapan Cukup, Kapan Perlu Upgrade
Untuk tim automation kecil atau agency freelance yang mengurus beberapa klien scraping maupun price-monitoring sekaligus, Discord sering sudah jadi pusat koordinasi — satu server, channel terpisah per klien atau per pipeline. Menaruh alert CaptchaAI di channel yang sama berarti tidak perlu tool monitoring berbayar tambahan hanya untuk mengecek saldo atau error rate.
Batasnya: Discord bukan sistem paging. Kalau tidak ada anggota tim yang sedang membuka Discord saat pipeline berhenti total, notifikasinya bisa saja terlewat sampai jam kerja berikutnya. Pola yang umum dipakai: Discord untuk visibilitas harian (saldo, tren error, ringkasan), sementara PagerDuty menangani eskalasi saat pipeline benar-benar mati — kirim ke dua tujuan sekaligus dari titik alert yang sama di kode Anda (lihat tautan PagerDuty di bagian bawah).
Pertanyaan Umum
Berapa banyak webhook Discord yang bisa dibuat dalam satu channel?
Maksimal 15 webhook per channel. Cukup buat satu khusus CaptchaAI dan pakai ulang untuk semua jenis alert di atas — tidak perlu webhook terpisah per jenis notifikasi.
Apakah alert Discord ini cukup untuk on-call produksi, atau tetap perlu PagerDuty?
Untuk visibilitas tim sehari-hari, Discord sudah cukup. Untuk on-call produksi yang butuh eskalasi ke orang yang sedang tidak online (telepon, SMS), tetap gunakan PagerDuty atau Opsgenie sebagai lapisan tambahan — Discord tidak dirancang sebagai sistem paging.
Bagaimana menghindari API key CaptchaAI ikut ter-expose di channel Discord yang dibagikan ke banyak anggota tim?
Simpan CAPTCHAAI_API_KEY sebagai environment variable, bukan di dalam pesan atau kode yang ter-commit — kode di atas sudah memuatnya lewat os.environ/process.env. Embed yang dikirim ke Discord hanya berisi saldo dan status, tidak pernah nilai API key itu sendiri. Kalau channel dibagikan ke klien eksternal, batasi akses webhook ke channel internal saja.
Berapa cooldown yang ideal supaya notifikasi Discord tidak jadi spam saat error beruntun?
Kode contoh memakai 300 detik (5 menit), yang sudah cukup untuk menghindari rate limit Discord sekaligus tidak menunda respons tim terlalu lama. Untuk pipeline volume tinggi, cooldown 10–15 menit lebih nyaman; untuk pipeline kecil yang jarang error, 5 menit sudah pas.
Bisakah satu sistem alert ini mengirim ke lebih dari satu channel Discord sekaligus?
Bisa. Buat webhook terpisah per channel (misalnya satu untuk channel engineering, satu untuk channel ops harian), simpan masing-masing URL-nya, lalu panggil send_embed/sendDiscordEmbed dengan instance atau webhook URL yang sesuai untuk tiap jenis alert.
Langkah Selanjutnya
Dapatkan notifikasi pipeline di tempat tim Anda sudah berkumpul — mulai dengan API key CaptchaAI lalu sambungkan ke Discord.
Panduan terkait:
- notifikasi bot Slack untuk CAPTCHA
- menyambungkan alert ke PagerDuty
- referensi kode error CaptchaAI