Arsitektur berbasis event dengan AWS SNS membuat scraper Anda berhenti menunggu: alih-alih polling ke CaptchaAI, hasil solve didorong ke Anda begitu selesai. Callback CaptchaAI dipublikasikan ke satu topik SNS, lalu di-fan-out ke berapa pun konsumen — SQS, Lambda, hingga alert email — yang masing-masing bereaksi sendiri tanpa saling tahu. Pola ini melepas ikatan erat antara worker scraping dan alur penyelesaian, sehingga Anda bisa menambah konsumen baru tanpa menyentuh kode pengambil hasil.
Ikhtisar arsitektur
[Scraper] → Submit CAPTCHA → [CaptchaAI API]
↓
Solve completes
↓
Callback → [API Gateway + Lambda]
↓
Publish → [SNS Topic]
↓
┌───────────────┼───────────────┐
↓ ↓ ↓
[SQS Queue] [Lambda Logger] [Email Alert]
(result store) (audit trail) (on failure)
Inti pola ini adalah fan-out SNS: satu hasil CAPTCHA memicu banyak konsumen sekaligus, sementara handler callback sama sekali tidak tahu siapa saja yang berlangganan.
Menambah konsumen cukup dengan membuat subscription baru pada topik yang sama — kode callback tidak berubah.
Secara ringkas, alurnya berjalan seperti ini:
- Scraper mengirim CAPTCHA ke CaptchaAI beserta URL
pingback. - CaptchaAI menyelesaikan CAPTCHA lalu memanggil URL callback Anda.
- Lambda callback mempublikasikan hasil ke topik SNS.
- SNS mem-fan-out pesan ke setiap konsumen yang berlangganan.
Kapan pola event-driven ini tepat
Pola ini bukan default untuk setiap integrasi. Untuk satu skrip yang menyelesaikan beberapa CAPTCHA per menit, polling biasa sudah cukup. SNS mulai terbayar ketika:
- Anda menjalankan banyak worker paralel dan tidak ingin tiap worker menahan thread hanya untuk menunggu hasil.
- Satu hasil solve perlu memicu beberapa aksi sekaligus — simpan ke database, catat audit, dan kirim peringatan bila gagal.
- Tim Anda ingin menambah atau mengganti konsumen (misalnya menambah analitik) tanpa menyentuh jalur callback.
| Pendekatan | Cara kerja | Cocok untuk |
|---|---|---|
| Polling | Scraper menanyakan res.php berulang kali |
Volume kecil, satu konsumen |
| Event-driven (SNS) | Hasil didorong lalu di-fan-out ke banyak konsumen | Banyak worker paralel, banyak konsumen |
Konteks ini umum pada tim freelance scraping dan agensi price-monitoring yang banyak beroperasi di Indonesia: volume tidak menentu, tetapi biaya harus tetap terkendali. Karena callback melintasi internet publik, deploy komponen AWS di region terdekat — ap-southeast-1 (Singapura) atau ap-southeast-3 (Jakarta) — menekan latensi antara CaptchaAI dan API Gateway Anda.
Langkah 1: buat topik SNS
Topik SNS adalah titik pusat tempat semua hasil solve dipublikasikan. Buat sekali, lalu setiap konsumen cukup berlangganan ke ARN-nya.
AWS CLI
aws sns create-topic --name captcha-results --output text
# Returns: arn:aws:sns:us-east-1:123456789:captcha-results
Python (boto3)
import boto3
sns = boto3.client("sns", region_name="us-east-1")
response = sns.create_topic(Name="captcha-results")
topic_arn = response["TopicArn"]
print(f"Topic ARN: {topic_arn}")
Langkah 2: bangun penerima callback
Fungsi Lambda ini adalah jembatan antara CaptchaAI dan SNS: ia menerima hasil callback, memastikan id dan code ada, lalu mempublikasikan pesan terstruktur ke topik.
Jaga fungsi ini tetap ringan. Semua logika berat sebaiknya dijalankan konsumen hilir, bukan di jalur callback yang harus merespons cepat.
Python (Lambda handler)
import json
import os
import boto3
sns = boto3.client("sns")
TOPIC_ARN = os.environ["SNS_TOPIC_ARN"]
def lambda_handler(event, context):
"""Receive CaptchaAI callback and publish to SNS."""
# Parse query parameters from API Gateway
params = event.get("queryStringParameters", {}) or {}
task_id = params.get("id", "")
solution = params.get("code", "")
if not task_id or not solution:
return {"statusCode": 400, "body": "Missing id or code"}
# Publish to SNS
message = {
"task_id": task_id,
"solution": solution,
"status": "solved"
}
sns.publish(
TopicArn=TOPIC_ARN,
Message=json.dumps(message),
Subject="captcha-solved",
MessageAttributes={
"task_id": {
"DataType": "String",
"StringValue": task_id
}
}
)
return {"statusCode": 200, "body": "OK"}
JavaScript (Lambda handler)
const { SNSClient, PublishCommand } = require("@aws-sdk/client-sns");
const sns = new SNSClient({ region: "us-east-1" });
const TOPIC_ARN = process.env.SNS_TOPIC_ARN;
exports.handler = async (event) => {
const params = event.queryStringParameters || {};
const taskId = params.id;
const solution = params.code;
if (!taskId || !solution) {
return { statusCode: 400, body: "Missing id or code" };
}
const message = {
task_id: taskId,
solution: solution,
status: "solved",
};
await sns.send(
new PublishCommand({
TopicArn: TOPIC_ARN,
Message: JSON.stringify(message),
Subject: "captcha-solved",
MessageAttributes: {
task_id: { DataType: "String", StringValue: taskId },
},
})
);
return { statusCode: 200, body: "OK" };
};
Langkah 3: kirim CAPTCHA dengan URL callback
Arahkan parameter pingback CaptchaAI ke endpoint API Gateway Anda.
Setelah solve selesai, CaptchaAI memanggil URL itu dengan id (task ID) dan code (token) sebagai query parameter — persis yang dibaca handler pada Langkah 2.
Python
import os
import requests
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
CALLBACK_URL = os.environ["CALLBACK_GATEWAY_URL"] # API Gateway URL
def submit_captcha(sitekey, pageurl):
"""Submit CAPTCHA with SNS-backed callback."""
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:
return data["request"] # task_id
raise RuntimeError(f"Submit failed: {data.get('request')}")
Langkah 4: langganan konsumen ke topik
Dengan topik yang sudah aktif, daftarkan tiap konsumen sesuai perannya. Tiga pola yang paling umum: menyimpan hasil, mencatat audit, dan mengirim peringatan kegagalan.
Antrean SQS (penyimpanan hasil)
# Subscribe an SQS queue to receive all results
sqs_arn = "arn:aws:sqs:us-east-1:123456789:captcha-results-queue"
sns.subscribe(
TopicArn=topic_arn,
Protocol="sqs",
Endpoint=sqs_arn
)
Lambda (pencatat audit)
# Subscribe a Lambda for audit logging
lambda_arn = "arn:aws:lambda:us-east-1:123456789:function:captcha-audit-logger"
sns.subscribe(
TopicArn=topic_arn,
Protocol="lambda",
Endpoint=lambda_arn
)
Email (peringatan kegagalan)
# Subscribe email for error notifications with filter
sns.subscribe(
TopicArn=topic_arn,
Protocol="email",
Endpoint="ops@example.com"
)
Langkah 5: konsumsi hasil dari SQS
Kini scraper Anda membaca solusi dari SQS, bukan lagi polling ke CaptchaAI. SQS long polling (hingga 20 detik per panggilan) membuat konsumen hemat request sekaligus tetap responsif.
Satu hal yang mudah terlewat: SNS membungkus pesan asli dalam amplop JSON, jadi hasilnya perlu di-unwrap dua kali — sekali dari body SQS, sekali dari field Message milik SNS.
Python
import json
import boto3
sqs = boto3.client("sqs", region_name="us-east-1")
QUEUE_URL = os.environ["SQS_QUEUE_URL"]
def get_solved_captcha(timeout=30):
"""Wait for a CAPTCHA solution from the SQS queue."""
response = sqs.receive_message(
QueueUrl=QUEUE_URL,
MaxNumberOfMessages=1,
WaitTimeSeconds=min(timeout, 20) # Long polling (max 20s)
)
messages = response.get("Messages", [])
if not messages:
return None
msg = messages[0]
# SNS wraps the message — unwrap it
sns_envelope = json.loads(msg["Body"])
result = json.loads(sns_envelope["Message"])
# Delete message after processing
sqs.delete_message(
QueueUrl=QUEUE_URL,
ReceiptHandle=msg["ReceiptHandle"]
)
return result
JavaScript
const {
SQSClient,
ReceiveMessageCommand,
DeleteMessageCommand,
} = require("@aws-sdk/client-sqs");
const sqs = new SQSClient({ region: "us-east-1" });
const QUEUE_URL = process.env.SQS_QUEUE_URL;
async function getSolvedCaptcha(timeout = 30) {
const response = await sqs.send(
new ReceiveMessageCommand({
QueueUrl: QUEUE_URL,
MaxNumberOfMessages: 1,
WaitTimeSeconds: Math.min(timeout, 20),
})
);
const messages = response.Messages || [];
if (messages.length === 0) return null;
const msg = messages[0];
const snsEnvelope = JSON.parse(msg.Body);
const result = JSON.parse(snsEnvelope.Message);
await sqs.send(
new DeleteMessageCommand({
QueueUrl: QUEUE_URL,
ReceiptHandle: msg.ReceiptHandle,
})
);
return result;
}
Filter pesan SNS per konsumen
FilterPolicy SNS mengarahkan hasil tertentu ke konsumen tertentu berdasarkan atribut pesan. Contoh berikut hanya meneruskan solve yang gagal ke antrean tim ops, sementara antrean utama tetap menerima semua hasil:
# Only send failures to the ops queue
sns.subscribe(
TopicArn=topic_arn,
Protocol="sqs",
Endpoint=failure_queue_arn,
Attributes={
"FilterPolicy": json.dumps({
"status": ["failed", "error"]
})
}
)
Biaya thread dan skala
Perlu diingat, SNS tidak mengubah cara CaptchaAI menagih. CaptchaAI menagih per thread konkuren — satu thread adalah satu CAPTCHA yang sedang diproses — dengan solve tak terbatas per thread selama masa tagihan. Paket dimulai dari BASIC ($15/bulan, 5 thread) hingga tier lebih besar seperti ADVANCE ($90/bulan, 50 thread); pola event-driven tidak menambah biaya solve, ia hanya mengubah cara Anda menerima hasil.
Catatan: harga di atas adalah biaya paket CaptchaAI dalam USD. Biaya AWS untuk SNS, SQS, dan Lambda dihitung terpisah oleh AWS sesuai pemakaian.
Yang bertambah adalah biaya AWS: SNS, SQS, Lambda, dan API Gateway ditagih terpisah sesuai volume pesan dan invokasi. Untuk beban kecil hingga menengah biaya ini biasanya jauh di bawah tagihan solver, tetapi tetap pantau invokasi Lambda callback saat volume melonjak.
Satu catatan kepatuhan: proses hanya data yang memang boleh Anda olah. UU Pelindungan Data Pribadi (UU 27/2022) menjadikan prinsip ini relevan untuk alur scraping — hindari menyertakan data pribadi dalam pesan SNS yang tersebar ke banyak konsumen.
Pemecahan masalah callback SNS
| Masalah | Penyebab | Solusi |
|---|---|---|
| Callback mengembalikan 403 | Autentikasi API Gateway memblokir CaptchaAI | Nonaktifkan autentikasi pada rute callback; gunakan validasi berbasis token |
| Pesan SQS tidak sampai | Izin SNS → SQS tidak ada | Tambahkan izin sns:Publish ke kebijakan antrean SQS |
| Hasil duplikat diproses | SNS mengirimkan at-least-once | Terapkan idempotency – periksa task_id sebelum diproses |
| Lambda cold start menunda callback | Provisioned concurrency tidak disetel | Aktifkan provisioned concurrency untuk callback Lambda |
Pertanyaan umum
Apakah lapisan SNS mengubah tagihan CaptchaAI?
Tidak. CaptchaAI menagih per thread konkuren dengan solve tak terbatas per thread, terlepas dari cara Anda menerima hasil. SNS, SQS, dan Lambda adalah biaya AWS yang terpisah dari paket solver Anda.
Bagaimana cara mencegah satu hasil solve diproses dua kali?
SNS mengirim pesan minimal sekali (at-least-once), jadi duplikat bisa terjadi. Terapkan idempotency di konsumen: simpan task_id yang sudah diproses dan lewati bila muncul lagi. Untuk urutan yang ketat, pakai topik SNS FIFO dengan antrean SQS FIFO dan set MessageGroupId ke task ID.
Region AWS mana yang sebaiknya dipakai?
Deploy topik SNS, antrean SQS, dan Lambda di region yang dekat dengan target dan tim Anda — ap-southeast-1 (Singapura) atau ap-southeast-3 (Jakarta) untuk pengguna di Indonesia. Yang terpenting semua komponen berada di region yang sama agar tidak ada latensi lintas-region.
Apa yang terjadi jika callback CaptchaAI gagal terkirim?
Bila endpoint callback sedang down, hasil tidak masuk ke SNS. Lindungi jalur ini dengan retry dan dead-letter queue pada Lambda callback, serta sediakan polling res.php sebagai cadangan untuk task penting agar tidak ada hasil yang hilang.
Bisakah saya memproses hasil langsung di Lambda callback tanpa SNS?
Bisa, dan untuk kebutuhan sederhana itu memang lebih ringkas. SNS baru berguna saat satu hasil harus memicu beberapa konsumen atau saat Anda ingin menambah konsumen tanpa mengubah kode callback. Jika hanya ada satu tujuan, callback langsung sudah memadai.
Artikel terkait
- Membangun pipeline CAPTCHA sisi klien dengan CaptchaAI
- Mengamankan webhook: memvalidasi callback CaptchaAI
- Integrasi serverless AWS Lambda dengan CaptchaAI
Langkah selanjutnya
Siap membangun pipeline penyelesaian CAPTCHA berbasis event? Ambil API key CaptchaAI Anda dan sambungkan callback-nya ke topik SNS di alur AWS Anda.