Manter um worker ligado 24 horas por dia só para resolver CAPTCHA raramente compensa: ele fica ocioso na maior parte do tempo e você paga pelo servidor do mesmo jeito. O AWS Lambda elimina esse desperdício executando a chamada à API da CaptchaAI apenas quando uma tarefa chega — via API Gateway, fila SQS ou Step Functions — e cobra por milissegundo de execução, não por instância ligada.
Este guia mostra o handler completo em Python, como guardar a chave no Secrets Manager, como descrever a infraestrutura em um template SAM e como processar lotes de CAPTCHA vindos de uma fila SQS. Faz mais sentido para tráfego irregular e scraping autorizado do que para volume alto e constante, onde um worker dedicado pode sair mais barato.
Handler do Lambda: da requisição ao token
O handler abaixo recebe o method e os params do CAPTCHA (por exemplo, googlekey e pageurl para reCAPTCHA), envia a tarefa ao endpoint in.php da CaptchaAI e faz o polling em res.php até obter o token ou estourar o tempo limite. Como usa apenas urllib — biblioteca padrão do Python — não é preciso empacotar dependências externas nem criar uma camada Lambda:
# lambda_function.py
import json
import os
import time
import urllib.request
import urllib.parse
def lambda_handler(event, context):
"""AWS Lambda handler for CaptchaAI solving."""
api_key = os.environ["CAPTCHAAI_KEY"]
# Parse input
body = json.loads(event.get("body", "{}")) if isinstance(event.get("body"), str) else event
method = body.get("method", "userrecaptcha")
params = body.get("params", {})
try:
token = solve_captcha(api_key, method, params)
return {
"statusCode": 200,
"body": json.dumps({"token": token}),
}
except Exception as e:
return {
"statusCode": 500,
"body": json.dumps({"error": str(e)}),
}
def solve_captcha(api_key, method, params, timeout=90):
"""Solve CAPTCHA using CaptchaAI API."""
# Submit task
submit_data = urllib.parse.urlencode({
"key": api_key,
"method": method,
"json": 1,
**params,
}).encode()
req = urllib.request.Request(
"https://ocr.captchaai.com/in.php",
data=submit_data,
)
with urllib.request.urlopen(req, timeout=30) as resp:
result = json.loads(resp.read())
if result.get("status") != 1:
raise RuntimeError(f"Submit error: {result.get('request')}")
task_id = result["request"]
# Poll for result
start = time.time()
while time.time() - start < timeout:
time.sleep(5)
poll_url = (
f"https://ocr.captchaai.com/res.php"
f"?key={api_key}&action=get&id={task_id}&json=1"
)
with urllib.request.urlopen(poll_url, timeout=15) as resp:
data = json.loads(resp.read())
if data["request"] != "CAPCHA_NOT_READY":
if data.get("status") == 1:
return data["request"]
raise RuntimeError(f"Solve error: {data['request']}")
raise TimeoutError("Solve timeout")
Com o handler pronto, o próximo passo é parar de expor a chave de API em texto puro e mover a leitura para o Secrets Manager.
Chave de API protegida com o Secrets Manager
Colocar a chave da CaptchaAI direto no código ou em uma variável de ambiente sem criptografia é o erro mais comum quando um protótipo vira produção. O Secrets Manager guarda o valor criptografado e o Lambda busca a chave em tempo de execução, sem que ela apareça em logs ou no console do CloudFormation:
import json
import boto3
def get_api_key():
"""Retrieve CaptchaAI key from AWS Secrets Manager."""
client = boto3.client("secretsmanager")
response = client.get_secret_value(SecretId="captchaai/api-key")
secret = json.loads(response["SecretString"])
return secret["api_key"]
Armazene o segredo:
aws secretsmanager create-secret \
--name captchaai/api-key \
--secret-string '{"api_key":"YOUR_API_KEY"}'
Conceda ao papel de execução apenas secretsmanager:GetSecretValue restrito a esse ARN — políticas com * dão acesso a todos os segredos da conta.
Infraestrutura como código com o SAM
O template abaixo declara a função, a rota do API Gateway e a permissão de leitura do segredo em um único arquivo, sem clicar em nada no console. Timeout: 120 dá margem para a maioria dos CAPTCHAs; MemorySize: 256 é suficiente porque o handler só faz chamadas HTTP, sem processamento de imagem local:
# template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Timeout: 120
MemorySize: 256
Runtime: python3.11
Resources:
CaptchaSolverFunction:
Type: AWS::Serverless::Function
Properties:
Handler: lambda_function.lambda_handler
Environment:
Variables:
CAPTCHAAI_KEY: !Sub "{{resolve:secretsmanager:captchaai/api-key:SecretString:api_key}}"
Events:
SolveApi:
Type: Api
Properties:
Path: /solve
Method: post
Policies:
- AWSSecretsManagerGetSecretValuePolicy:
SecretArn: !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:captchaai/api-key-*"
Outputs:
SolveApiUrl:
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/solve"
Versione esse arquivo junto do código: mudanças de memória, timeout ou IAM ficam no histórico do Git, não escondidas em um clique no console.
Build, deploy e primeiro teste
Com o SAM CLI instalado, sam build empacota o código e sam deploy --guided cria — ou atualiza — a stack no CloudFormation, perguntando região, nome da stack e confirmação de mudanças de IAM na primeira execução. Depois do deploy, teste o endpoint com uma chamada real:
# Build and deploy
sam build
sam deploy --guided
# Test
curl -X POST https://YOUR_API_ID.execute-api.us-east-1.amazonaws.com/Prod/solve \
-H "Content-Type: application/json" \
-d '{
"method": "userrecaptcha",
"params": {
"googlekey": "SITE_KEY",
"pageurl": "https://example.com"
}
}'
Se a resposta vier com token, o pipeline funciona ponta a ponta: API Gateway, Lambda e API da CaptchaAI.
Processando picos de volume com SQS
Chamar o Lambda direto pelo API Gateway funciona bem para tarefas isoladas, mas em scraping autorizado com centenas de CAPTCHAs por minuto, uma fila SQS entre a origem e o Lambda absorve os picos sem estourar a concorrência da conta. O handler abaixo processa um lote de mensagens, resolve cada CAPTCHA e devolve o resultado por tarefa — incluindo as que falharam, para você decidir se reenvia ou move para uma fila de mensagens não entregues:
import json
import os
import time
import urllib.request
import urllib.parse
def sqs_handler(event, context):
"""Process CAPTCHA tasks from SQS queue."""
api_key = os.environ["CAPTCHAAI_KEY"]
results = []
for record in event["Records"]:
task = json.loads(record["body"])
try:
token = solve_captcha(
api_key,
task["method"],
task["params"],
)
results.append({
"task_id": task.get("id"),
"status": "success",
"token": token[:50],
})
except Exception as e:
results.append({
"task_id": task.get("id"),
"status": "error",
"error": str(e),
})
return {"results": results}
Rodar os workers em sa-east-1 (São Paulo) reduz o RTT para tráfego originado no Brasil, o que ajuda quando o tempo de resolução já está perto do timeout. Se os logs guardarem dados de usuários, considere a LGPD.
O que considerar antes de ir para produção
Antes de apontar tráfego de produção para essa função, revise os números que definem o comportamento de uma arquitetura serverless — mudam pouco entre projetos, mas custam caro quando ignorados:
| Fator | Valor |
|---|---|
| Tempo limite máximo | 15 minutos (defina 2 minutos para a maioria dos CAPTCHAs) |
| Memória | 256 MB são suficientes (sem processamento pesado) |
| Simultaneidade | Padrão de 1000 execuções simultâneas (solicite aumento se precisar) |
| Cold start | ~500 ms para Python (insignificante perto do tempo de resolução) |
| Custo | ~US$ 0,0001 por resolução (somente o cálculo do Lambda) |
| Dependências | Use urllib (nativo) para evitar camadas Lambda |
Nenhum desses limites é específico da CaptchaAI — valem para qualquer função Lambda com chamada HTTP de saída. A variável que muda por tipo de CAPTCHA é o tempo de resolução, que define o timeout mínimo seguro.
Erros comuns e como resolver
A maior parte dos problemas cai em uma destas quatro causas:
| Problema | Causa | Correção |
|---|---|---|
| A função expira | Tempo limite do Lambda menor que o tempo de resolução | Defina o tempo limite para 120 s ou mais |
| Permissão negada no segredo | Política do IAM ausente | Adicione uma política de leitura do Secrets Manager |
| Cold start adiciona latência | Invocações pouco frequentes | Use concorrência provisionada |
Erro de importação de requests |
Biblioteca não empacotada no Lambda | Use urllib.request (nativo) ou adicione uma camada |
Perguntas frequentes
Vale a pena usar Lambda em vez de um worker fixo para resolver CAPTCHA?
Para volume irregular, sim: você paga por invocação (cerca de US$ 0,0001, 256 MB, 60 s), não por hora ociosa de servidor. Para volume alto e constante, compare com o custo de um worker dedicado — a diferença some a partir de um certo patamar de chamadas por segundo.
Como evito que o timeout do Lambda derrube uma resolução em andamento?
Configure o timeout para pelo menos 120 segundos — a maioria dos tipos resolve em 10 a 60 segundos. Para tipos mais lentos, como reCAPTCHA Enterprise, use 180 segundos e confirme a duração real no CloudWatch antes de reduzir a margem.
Preciso de uma VPC para o Lambda chamar a API da CaptchaAI?
Não. Chamadas HTTPS de saída para ocr.captchaai.com funcionam a partir de um Lambda padrão, fora de VPC. Configure VPC só se a função também precisar de recursos internos, como um banco privado — isso adiciona latência de cold start que vale medir antes.
Como lidar com picos de volume sem estourar a concorrência do Lambda?
Coloque uma fila SQS entre a origem das tarefas e a função: o SQS controla a taxa de invocação e absorve picos, em vez de disparar milhares de execuções simultâneas de uma vez. Ajuste o tamanho do lote do trigger conforme o throughput que sua conta suporta.
Faz sentido rodar as funções na região sa-east-1 (São Paulo)?
Se a maior parte do tráfego de origem está no Brasil, sim: reduz o RTT até a função. A chamada à API da CaptchaAI sai pela internet pública — o ganho fica no trecho até o Lambda, não na chamada externa.
Guias relacionados
Para expandir além do Lambda:
Vá sem servidor — obtenha sua chave da CaptchaAI hoje.