Tutorials

CaptchaAI Callback Hata Yönetimi: Yeniden Deneme ve Dead-Letter Queue Desenleri

Callback'iniz teslimatı başarısız olursa çözüm otomatik olarak kaybolmaz — doğru fallback deseniniz varsa. CaptchaAI sonucu callback URL'inize göndermeye çalışırken sunucunuz kapalıysa, 5xx dönerse veya bağlantı zaman aşımına uğrarsa üç desenden biri devreye girmeli:

  • Callback + yedek sorgulama — callback zamanında gelmezse arka planda sonucu sorgulayın.
  • Dead-letter queue (DLQ) — işleyici hata verirse sonucu kaybetmeyin, kuyruğa taşıyın.
  • Idempotent işleyici — aynı callback iki kez gelse bile veriyi bozmayın.

Bu rehber üçünü de çalışan kod örnekleriyle gösterir ve hangi senaryoda hangisini seçeceğinizi netleştirir.

Callback Teslimatı Neden Başarısız Olur?

Üretimde callback güvenilirliği tipik olarak dört noktada kırılır:

Arıza Noktası Belirti Sonuç
Sunucu kapalı CaptchaAI bağlantı reddi alır Çözüm teslim edilmez
Sunucu 5xx döner CaptchaAI hata yanıtı alır Yeniden deneme uygulamaya bağlıdır — garanti değil
Ağ zaman aşımı CaptchaAI bağlantısı asılı kalır Çözüm kaybolma riski taşır
İşleyici çöker İstek kabul edilir, sonuç kaydedilmez Çözüm sessizce kaybolur

Callback'e asla tek başına güvenmeyin. Her zaman bir geri dönüş mekanizmanız olsun.

Hangi Deseni Ne Zaman Kullanmalısınız?

Üç deseni ayrı ayrı ya da birlikte kullanabilirsiniz; seçim trafik hacminize ve SLA'nıza bağlıdır. Aşağıda hızlı bir özet, ardından her deseni kod örnekleriyle görebilirsiniz.

Senaryo Önerilen Desen
Düşük hacim, ara sıra kesinti Callback + Yedek Sorgulama
Yüksek hacim, veritabanı kesintisi olasılığı var Dead-Letter Queue
Aynı sonucu birden fazla tüketici işleyebilir Idempotent İşleyici
SLA'lı üretim sistemi Üçü birlikte

Desen 1: Callback + Yedek Sorgulama

En dayanıklı yaklaşım, callback'leri geldiği anda kabul etmek ama zaman aşımı süresi içinde callback almayan görevleri arka planda periyodik olarak sorgulamaktır. Aşağıdaki Python ve Node.js örnekleri aynı mantığı uygular: görevi gönderin, bekleyen listesine ekleyin, callback gelirse listeden çıkarın, gelmezse 30 saniyede bir arka planda sorgulayın.

Python

import os
import time
import threading
import requests
from flask import Flask, request

app = Flask(__name__)
API_KEY = os.environ["CAPTCHAAI_API_KEY"]

# Track task state
pending_tasks = {}  # task_id -> {"submitted_at": timestamp, "status": "pending"}
results = {}
lock = threading.Lock()


def submit_captcha(sitekey, pageurl, callback_url):
    """Submit with callback, but track for fallback polling."""
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": pageurl,
        "pingback": callback_url,
        "json": 1
    })
    data = resp.json()

    if data.get("status") == 1:
        task_id = data["request"]
        with lock:
            pending_tasks[task_id] = {
                "submitted_at": time.time(),
                "status": "pending"
            }
        return task_id
    return None


@app.route("/callback")
def captcha_callback():
    """Primary result delivery — CaptchaAI sends results here."""
    task_id = request.args.get("id")
    solution = request.args.get("code")

    with lock:
        results[task_id] = solution
        pending_tasks.pop(task_id, None)

    return "OK", 200


def fallback_poller():
    """Poll for any tasks that missed their callback."""
    while True:
        time.sleep(30)  # Check every 30 seconds

        with lock:
            stale_tasks = [
                tid for tid, info in pending_tasks.items()
                if time.time() - info["submitted_at"] > 120  # 2 min callback timeout
                and info["status"] == "pending"
            ]

        for task_id in stale_tasks:
            resp = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": API_KEY,
                "action": "get",
                "id": task_id,
                "json": 1
            })
            data = resp.json()

            if data.get("status") == 1:
                with lock:
                    results[task_id] = data["request"]
                    pending_tasks.pop(task_id, None)
                print(f"Fallback poll recovered: {task_id}")
            elif data.get("request") != "CAPCHA_NOT_READY":
                # Permanent error — remove from pending
                with lock:
                    pending_tasks.pop(task_id, None)
                print(f"Task failed: {task_id} — {data.get('request')}")


# Start fallback poller in background
poller_thread = threading.Thread(target=fallback_poller, daemon=True)
poller_thread.start()

JavaScript

const express = require("express");
const axios = require("axios");

const app = express();
const API_KEY = process.env.CAPTCHAAI_API_KEY;

const pendingTasks = new Map(); // taskId -> { submittedAt, status }
const results = new Map();

async function submitCaptcha(sitekey, pageurl, callbackUrl) {
  const resp = await axios.post("https://ocr.captchaai.com/in.php", null, {
    params: {
      key: API_KEY,
      method: "userrecaptcha",
      googlekey: sitekey,
      pageurl: pageurl,
      pingback: callbackUrl,
      json: 1,
    },
  });

  if (resp.data.status === 1) {
    const taskId = resp.data.request;
    pendingTasks.set(taskId, {
      submittedAt: Date.now(),
      status: "pending",
    });
    return taskId;
  }
  return null;
}

// Primary callback endpoint
app.get("/callback", (req, res) => {
  const taskId = req.query.id;
  const solution = req.query.code;

  results.set(taskId, solution);
  pendingTasks.delete(taskId);

  res.sendStatus(200);
});

// Fallback poller
setInterval(async () => {
  const now = Date.now();
  const staleTasks = [];

  for (const [taskId, info] of pendingTasks) {
    if (now - info.submittedAt > 120000 && info.status === "pending") {
      staleTasks.push(taskId);
    }
  }

  for (const taskId of staleTasks) {
    try {
      const resp = await axios.get("https://ocr.captchaai.com/res.php", {
        params: { key: API_KEY, action: "get", id: taskId, json: 1 },
      });

      if (resp.data.status === 1) {
        results.set(taskId, resp.data.request);
        pendingTasks.delete(taskId);
        console.log(`Fallback recovered: ${taskId}`);
      } else if (resp.data.request !== "CAPCHA_NOT_READY") {
        pendingTasks.delete(taskId);
        console.log(`Task failed: ${taskId} — ${resp.data.request}`);
      }
    } catch (err) {
      console.error(`Poll error for ${taskId}: ${err.message}`);
    }
  }
}, 30000);

app.listen(3000);

Örnek senaryo: Europe/Istanbul saat diliminde çalışan bir e-ticaret entegrasyon projesinde callback sunucunuz gece bakım penceresinde birkaç dakika erişilemez durumda kalıyorsa, yedek sorgulama o pencerede kaybolan görevleri kurtarır; callback tekrar ayağa kalktığında akış kendiliğinden normale döner.

Desen 2: Dead-Letter Queue (Teslim Edilemeyen Görev Kuyruğu)

Callback işleyiciniz sonucu aldı ama veritabanı kapalıysa veya doğrulama hatası oluşuyorsa, veriyi kaybetmek yerine dead-letter queue'ya (DLQ) taşıyın. Üretimde en çok tercih edilen desen budur, çünkü hatayı gözlemlenebilir hale getirir — sessizce kaybetmek yerine dead_letter/ klasöründe (veya gerçek bir kuyruk servisinde) saklar.

Python

import json
import os
import time
from pathlib import Path

DEAD_LETTER_DIR = Path("dead_letter")
DEAD_LETTER_DIR.mkdir(exist_ok=True)


@app.route("/callback")
def captcha_callback_with_dlq():
    task_id = request.args.get("id")
    solution = request.args.get("code")

    try:
        # Attempt normal processing
        store_result(task_id, solution)
        return "OK", 200
    except Exception as e:
        # Processing failed — save to dead-letter queue
        dead_letter = {
            "task_id": task_id,
            "solution": solution,
            "error": str(e),
            "received_at": time.time()
        }
        dlq_path = DEAD_LETTER_DIR / f"{task_id}.json"
        dlq_path.write_text(json.dumps(dead_letter))

        print(f"DLQ: {task_id} — {e}")
        return "OK", 200  # Still return 200 to CaptchaAI


def reprocess_dead_letters():
    """Retry processing dead-letter items."""
    for dlq_file in DEAD_LETTER_DIR.glob("*.json"):
        item = json.loads(dlq_file.read_text())

        try:
            store_result(item["task_id"], item["solution"])
            dlq_file.unlink()  # Remove after successful processing
            print(f"DLQ reprocessed: {item['task_id']}")
        except Exception:
            pass  # Leave in DLQ for next retry

JavaScript

const fs = require("fs");
const path = require("path");

const DLQ_DIR = path.join(__dirname, "dead_letter");
if (!fs.existsSync(DLQ_DIR)) fs.mkdirSync(DLQ_DIR);

app.get("/callback-dlq", (req, res) => {
  const taskId = req.query.id;
  const solution = req.query.code;

  try {
    storeResult(taskId, solution);
    res.sendStatus(200);
  } catch (err) {
    // Save to dead-letter queue
    const deadLetter = {
      task_id: taskId,
      solution: solution,
      error: err.message,
      received_at: Date.now(),
    };

    fs.writeFileSync(
      path.join(DLQ_DIR, `${taskId}.json`),
      JSON.stringify(deadLetter)
    );

    console.log(`DLQ: ${taskId} — ${err.message}`);
    res.sendStatus(200); // Still acknowledge to CaptchaAI
  }
});

function reprocessDeadLetters() {
  const files = fs.readdirSync(DLQ_DIR).filter((f) => f.endsWith(".json"));

  for (const file of files) {
    const filePath = path.join(DLQ_DIR, file);
    const item = JSON.parse(fs.readFileSync(filePath, "utf8"));

    try {
      storeResult(item.task_id, item.solution);
      fs.unlinkSync(filePath);
      console.log(`DLQ reprocessed: ${item.task_id}`);
    } catch (err) {
      // Leave in DLQ
    }
  }
}

// Retry DLQ every 5 minutes
setInterval(reprocessDeadLetters, 300000);

KVKK notu: DLQ kayıtları hata ayıklama içindir, kalıcı arşiv değildir. Özellikle test/QA verisi içeren görevleri makul bir saklama süresinden sonra otomatik temizleyin — gereksiz veri birikimi KVKK kapsamında ayrı bir risk oluşturur.

Desen 3: Idempotent Callback İşleyicisi

Callback'ler bazen birden fazla kez teslim edilebilir — ağ tekrarları veya yük dengeleyici davranışları yüzünden. İşleyicinizi idempotent yapın: aynı task_id ikinci kez geldiğinde sessizce 200 dönün, veriyi tekrar yazmayın.

@app.route("/callback")
def idempotent_callback():
    task_id = request.args.get("id")
    solution = request.args.get("code")

    with lock:
        # Only process if not already handled
        if task_id in results:
            return "OK", 200  # Already processed — skip silently

        results[task_id] = solution
        pending_tasks.pop(task_id, None)

    return "OK", 200

Sık Sorulan Sorular

CaptchaAI callback davranışı hakkında geliştiricilerin en çok sorduğu beş soru:

Callback URL'imi yerel ortamda (localhost) nasıl test ederim?

Flask veya Express sunucunuzu bir tünel aracıyla (örneğin ngrok veya cloudflared) dışarı açın ve pingback parametresine geçici tünel adresini verin. Callback'i canlıya almadan önce uçtan uca yerelde doğrulamış olursunuz.

CaptchaAI callback'lerine her zaman HTTP 200 mü dönmeliyim?

Evet. Hata kodu (4xx/5xx) döndürmek işe yaramaz — CaptchaAI callback'i yeniden denemeyebilir. Teslimatı her zaman kabul edin (200 OK), hatayı DLQ veya yedek sorgulama ile dahili olarak yönetin.

Yedek sorgulamayı gönderimden kaç saniye sonra başlatmalıyım?

En az 120 saniye bekleyin. CAPTCHA'ların çoğu 10–60 saniyede çözülür, buna callback teslim gecikmesi eklenir; iki dakika callback'in ulaşması için yeterli pay bırakır.

Dead-letter queue'da biriken görevleri ne kadar süre saklamalıyım?

Sabit bir kural yok, ama gereksiz veri birikimini önlemek için makul bir saklama süresi (örneğin 7–30 gün) belirleyip otomatik temizleyin — özellikle DLQ kayıtları test/QA verisi içeriyorsa.

Aynı görev için callback iki kez gelirse ne olur?

CaptchaAI ağ katmanında aynı sonucu iki kez gönderebilir. İdempotent işleyici deseniyle ikinci teslimat sessizce 200 döner ve veriyi tekrar yazmaz — bkz. Desen 3.

Uygulamaya Nasıl Başlarsınız?

  1. Görev gönderirken pingback parametresine callback URL'inizi ekleyin ve uç noktayı yerelde ngrok gibi bir tünel aracıyla test edin.
  2. Düşük trafikte bile Desen 1'i (callback + yedek sorgulama) devreye alın — tek başına temel bir güvence sağlar.
  3. Hacim büyüdükçe Desen 2'yi (DLQ) ve Desen 3'ü (idempotent işleyici) ekleyin; SLA'lı üretim sistemlerinde üçünü birlikte kullanmak önerilir.

Sorun Giderme

Üretimde callback işleyicilerinde en sık karşılaşılan dört sorun ve çözümü:

  • Yedek sorgulama zaten teslim edilmiş görevleri tekrar buluyor — callback ile sorgulama arasındaki yarış durumundandır; idempotency kontrolü ekleyin, sonuçlarda varsa atlayın.
  • DLQ işlenmeden büyüyor — yeniden işleyici çalışmıyor veya hata veriyordur; loglarını kontrol edin ve kök nedeni (DB) düzeltin.
  • Callback 200 dönüyor ama sonuç kayboluyor — işleyici yanıt gönderildikten sonra çöküyordur; yanıtı göndermeden önce işleyin veya DLQ desenini kullanın.
  • Çok fazla yedek sorgulama isteği geliyor — çok fazla eski görev birikmiştir; callback zaman aşımı eşiğini artırın ve sunucu çalışma süresini kontrol edin.

İlgili Makaleler

Konuyu derinleştirmek isterseniz Python captcha çözümlerinde yeniden deneme ve hata desenlerine göz atın, callback imzalarını doğrulamak için webhook güvenliği rehberine bakın ve hata kodlarının tam listesi için CaptchaAI hata kodları referansını kaynak olarak kullanın.

Sonraki Adımlar

Callback hata yönetiminizi bugün sağlamlaştırın — CaptchaAI API anahtarınızı alın ve bu üç dayanıklılık desenini uygulayın. Kurulumu tamamladıktan sonra callback URL ve webhook kurulum rehberini, pingback görev bildirimi desenlerini ve webhook güvenliği: callback doğrulama rehberini de inceleyin.

Bu makale için yorumlar devre dışı bırakılmıştır.