DevOps y Escalado

Creación de resolución de CAPTCHA basada en eventos con AWS SNS y CaptchaAI

La consulta para obtener resultados de CAPTCHA vincula los subprocesos y crea un estrecho acoplamiento entre su raspador y el proceso de resolución. AWS SNS (Servicio de notificación simple) desacopla estas preocupaciones: CaptchaAI envía resultados a su devolución de llamada, que publica en SNS, y cualquier número de consumidores intermedios reaccionan de forma independiente.

Descripción general de la arquitectura

[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)

SNS proporciona distribución: un resultado CAPTCHA activa múltiples consumidores sin que el controlador de devolución de llamada los sepa.

Paso 1: crear el tema de SNS

CLI de AWS

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}")

Paso 2: construir el receptor de devolución de llamada

Esta función Lambda recibe los resultados de la devolución de llamada CaptchaAI y los publica en SNS.

Python (controlador Lambda)

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 (controlador Lambda)

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

Paso 3: envíe CAPTCHA con la URL de devolución de llamada

Apunte pingback de CaptchaAI a su punto final de API Gateway:

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')}")

Paso 4: suscribir a los consumidores

Cola SQS (almacenamiento de resultados)

# 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 (registrador de auditoría)

# 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
)

Correo electrónico (alertas de fallos)

# Subscribe email for error notifications with filter
sns.subscribe(
    TopicArn=topic_arn,
    Protocol="email",
    Endpoint="ops@example.com"
)

Paso 5: Consumir los resultados de SQS

Su raspador lee soluciones de SQS en lugar de sondear CaptchaAI:

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

Filtrado de mensajes SNS

Enrute diferentes resultados a diferentes consumidores:

# 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"]
        })
    }
)

Solución de problemas

Problema causa Solución
La devolución de llamada devuelve 403 Bloqueo de autenticación de API Gateway CaptchaAI Deshabilite la autenticación en la ruta de devolución de llamada; utilice la validación basada en tokens en su lugar
Los mensajes SQS no llegan Falta el permiso SNS -> SQS Agregue el permiso sns:Publish a la política de cola SQS
Resultados duplicados procesados SNS envía al menos una vez Implementar idempotencia: verifique task_id antes de procesar
El arranque en frío Lambda retrasa la devolución de llamada Simultaneidad aprovisionada no establecida Habilite la simultaneidad aprovisionada para la devolución de llamada Lambda

Preguntas frecuentes

¿Por qué utilizar SNS en lugar de procesar los resultados directamente en la devolución de llamada Lambda?

SNS desacopla el controlador de devolución de llamada de la lógica descendente. Puede agregar nuevos consumidores (registro, alertas, análisis) sin modificar la devolución de llamada Lambda. La devolución de llamada sigue siendo simple y rápida.

¿Cuál es la latencia adicional de la capa SNS?

SNS agrega entre 10 y 50 ms por mensaje. Dado que CAPTCHA se resuelve en entre 5 y 30 segundos, esta sobrecarga es insignificante.

¿Puedo utilizar SNS FIFO para el procesamiento de pedidos?

Sí. Utilice un tema SNS FIFO con la cola SQS FIFO si necesita resultados ordenados. Establezca MessageGroupId en el ID de tarea para realizar pedidos por tarea.

Artículos relacionados

  • Construir pipelines de CAPTCHA de clientes con CaptchaAI
  • Seguridad de webhook: validación de callbacks

Crea una solución CAPTCHA basada en eventos: obtén tu API key de CaptchaAI y conéctala a tu canal de eventos de AWS.

Guías relacionadas:

  • AWS Lambda + CaptchaAI serverless
  • Seguridad de webhook: validación de callbacks
Los comentarios están deshabilitados para este artículo.