Bir CAPTCHA çözme pipeline'ı sessizce bozulduğunda bunu genellikle ilk fark eden, işleri aksayan taraf olur: bakiye biter, hata oranı fırlar ve kimsenin haberi olmaz. Çözüm basit — uyarıları ekibinizin zaten açık tuttuğu yere, Discord'a gönderin. Bu rehberde bakiye düşüşünü, hata artışını, kuyruk birikimini ve günlük özetleri Discord webhook'una gönderen küçük bir izleme katmanını Python ve Node.js ile kuruyoruz. Ayrı bir panele ya da üçüncü taraf bir servise gerek yok; Discord'un renk kodlu embed mesajları durumu tek bakışta okunur kılar.
Örneğin İstanbul merkezli bir e-ticaret QA ekibi, toplu doğrulama işlerini gece Europe/Istanbul saatiyle çalıştırıyorsa, bakiye sıfıra inmeden sabah mesai başında Discord'da uyarıyı görmek pipeline'ın gün boyu durmasını önler.
Hangi sinyalleri izlemeli?
İyi bir uyarı sistemi her olayı değil, harekete geçmeniz gereken olayları bildirir. Bir CaptchaAI pipeline'ında izlemeye değer beş sinyal var:
- Bakiye düşüşü: Hesap bakiyeniz belirlediğiniz eşiğin altına indiğinde. Bakiye bittiğinde çözüm istekleri reddedilir, bu yüzden en kritik uyarı budur.
- Hata oranı artışı: Son birkaç dakikadaki başarısız isteklerin oranı normalin üstüne çıktığında. Çoğu zaman hedef sitedeki bir değişikliğin ya da yanlış bir
sitekeyparametresinin ilk işaretidir. - Kuyruk birikimi: Bekleyen görev sayısı thread kapasitenizi aştığında. Kuyruk büyümeye devam ediyorsa daha fazla thread'e ya da daha yüksek bir plana ihtiyacınız var demektir.
- Günlük özet: Gün sonunda toplam görev, başarı oranı, ortalama gecikme ve maliyet. Trendleri görmek ve haftalık raporlar çıkarmak için idealdir.
- Toparlanma bildirimi: Çözüm oranı bir düşüşün ardından normale döndüğünde. Bir sorunun kapandığını ekibe haber verir.
Aşağıdaki Python sınıfı bu beş sinyalin her biri için ayrı bir embed metodu içerir; kendi eşiklerinizi geçtiğinizde ilgili metodu çağırmanız yeterli.
Webhook'u oluşturun
- Discord sunucunuzun ayarlarını açın
- Entegrasyonlar → Webhook'lar bölümüne gidin
- Yeni Webhook'a tıklayın ve adını "CaptchaAI Uyarıları" yapın
- Webhook URL'sini kopyalayın
- Bunu
DISCORD_WEBHOOK_URLortam değişkeni olarak saklayın
Python ile Discord uyarı sistemi
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()
Node.js ile webhook istemcisi
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 };
Uyarılar Discord'da nasıl görünür
Her uyarı türü renk kodlu bir embed olarak gelir; böylece kırmızı bir hata artışını yeşil bir günlük özetten tek bakışta ayırırsınız.
Bakiye uyarısı:
💰 Bakiye Uyarısı CaptchaAI bakiyesi 8,42$ Eşik: 10,00$ | Önem derecesi: UYARI
Hata artışı:
⚠️ Hata Oranında Yükseliş Hata oranı: %22,5 (45/200) •
ERROR_CAPTCHA_UNSOLVABLE: 30 •TIMEOUT: 15
Günlük özet:
📈 Günlük CAPTCHA Özeti 12.450 görev işlendi Başarı Oranı: %95,2 | Ortalama Gecikme: 22,4s | Bakiye: 142,30$
Eşikleri ve renk kodlarını ayarlayın
Uyarı yorgunluğu (alert fatigue) gerçek bir risktir: çok fazla mesaj gelirse ekip bir süre sonra hepsini görmezden gelmeye başlar. Bu yüzden eşikleri iş yükünüze göre kalibre edin.
- Bakiye eşiği: Günlük ortalama tüketiminizin en az iki katını eşik olarak belirleyin. Günde 5 dolar harcıyorsanız 10 dolarlık bir eşik, bakiye bitmeden önce size en az bir günlük tampon bırakır.
- Hata oranı: Örnek kod eşiği %15 alır. Düşük hacimli bir pipeline'da tek bir başarısız istek oranı şişirebileceği için hata kontrolü yalnızca son beş dakikada 10'dan fazla istek varsa çalışır.
- Renk kodları: Yeşil başarıyı, turuncu uyarıyı, kırmızı kritik hatayı temsil eder. Embed rengi durumu metni okumadan iletir; nöbetteki kişi ekrana şöyle bir bakıp önceliği anlar.
Bakiye 2 doların altına indiğinde uyarı otomatik olarak turuncudan kırmızıya döner; böylece "azalıyor" ile "neredeyse bitti" arasındaki farkı anında görürsünüz.
İzleyiciyi sürekli çalıştırın
İzleme kodu yalnızca çalışırken işe yarar. Uyarıların gerçek zamanlı olması için süreci arka planda kesintisiz çalışacak biçimde konumlandırın.
Süreci nasıl çalıştırırım?
Python tarafında PipelineMonitor.run_checks() metodunu bir döngüde veya bir zamanlayıcıyla periyodik olarak çağırın; Node.js örneği bakiye kontrolünü setInterval ile beş dakikada bir tetikler. Üretimde bu süreci bir systemd servisi, bir cron görevi ya da mevcut worker'ınızın yanında ayrı bir thread olarak çalıştırabilirsiniz. Hangi yolu seçerseniz seçin, süreç çökerse otomatik yeniden başlayacak bir denetleyiciyle sarmalayın.
Webhook URL'sini güvende tutun
Webhook URL'sini asla kod deposuna göndermeyin — bağlantıya sahip olan herkes kanalınıza mesaj yollayabilir. Bunu DISCORD_WEBHOOK_URL ortam değişkeninde tutun ve .env dosyanızı sürüm kontrolünün dışında bırakın. Uyarı mesajlarına ham istek verisi veya kişisel veri koymaktan kaçının; kazınan veriler KVKK kapsamına girebilir, bu yüzden özetlerde yalnızca sayısal metrikleri paylaşın.
Sorun giderme
| Sorun | Sebep | Düzeltme |
|---|---|---|
| 400 Bad Request | Geçersiz embed yapısı | fields dizisini kontrol edin; tüm değerlerin string olduğundan emin olun |
| Hız sınırı (429) | Dakikada çok fazla mesaj | Uyarılar arasına bekleme koyun (en az 5 dakika) |
| Webhook silinmiş | Sunucudan biri kaldırmış | Yeni bir webhook oluşturun ve ortam değişkenini güncelleyin |
| Embed görünmüyor | embeds dizisi eksik |
Embed nesnesini {"embeds": [...]} içine sarın |
Sık sorulan sorular
Bakiye uyarısı hangi eşikte devreye girer?
Örnek kodda eşik 10 dolar; bakiye bunun altına düştüğünde uyarı gönderilir, 2 doların altında ise kırmızı (error) renkle işaretlenir. threshold değerini kendi tüketim hızınıza göre ayarlayın.
Uyarı spam'ini nasıl önlerim?
PipelineMonitor her uyarı türü için 5 dakikalık bir bekleme (cooldown) uygular; aynı sorun için art arda mesaj gönderilmez. Yoğun hata dönemlerinde bu süreyi artırabilirsiniz.
Aynı webhook'u birden fazla proje için kullanabilir miyim?
Kullanabilirsiniz ama önermeyiz. Her pipeline için ayrı bir webhook ve ayrı bir Discord kanalı açmak, uyarının hangi projeden geldiğini karıştırmadan filtrelemenizi sağlar. Discord kanal başına 15 webhook'a izin verir.
Discord'u üretimde çağrı (on-call) için tek başına kullanabilir miyim?
Discord ekip görünürlüğü için mükemmeldir, ancak gece yarısı birini uyandırması gereken kritik olaylar için PagerDuty veya Opsgenie gibi bir çağrı aracıyla birlikte kullanın. Kritik uyarılarda webhook verisine "content": "<@&ROLE_ID>" ekleyerek ilgili rolü etiketleyebilirsiniz.
Kuyruk uyarısını da ekleyebilir miyim?
Evet. Python sınıfındaki queue_alert metodu tam olarak bunun için var: bekleyen görev sayısını ve aktif thread'leri alıp tahmini boşalma süresiyle birlikte bir embed gönderir. Kuyruk derinliğini kendi görev üreticinizden okuyup eşiği aştığında bu metodu çağırmanız yeterli.
Sonraki adımlar
Pipeline uyarılarını ekibinizin zaten açık tuttuğu yere getirin: CaptchaAI API anahtarınızı alın ve webhook'u Discord'a bağlayın. BASIC planla ($15/ay, 5 thread) başlayıp izleme katmanını ilk günden kurabilirsiniz.
İlgili rehberler:
- Slack bot bildirimleri
- PagerDuty ile çağrı uyarıları
- Hata kodları referansı