Webhooks Explicados: Payloads Assinados e Retentativas
Você acabou de integrar o webhook de um provedor de pagamentos. A documentação diz "faremos um POST no seu endpoint quando um pagamento for bem-sucedido". Simples, certo? Então você percebe eventos duplicados, notificações perdidas e um alerta de segurança sobre requisições forjadas. Webhooks são simples em conceito, mas complicados em produção. Este artigo explica como implementar webhooks corretamente usando payloads assinados e retentativas, para que você possa construir integrações confiáveis e seguras.
O Que São Webhooks?
Um webhook é uma API reversa: em vez de sua aplicação fazer polling em um serviço para obter atualizações, o serviço envia eventos para o seu endpoint via HTTP POST. É eficiente e quase em tempo real. Usos comuns incluem notificações de pagamento, gatilhos de CI/CD e integrações de chat.
Mas webhooks introduzem desafios: como você sabe que a requisição realmente veio do provedor? E se o seu servidor estiver fora do ar? E se o mesmo evento chegar duas vezes? Vamos resolver isso um por um.
Protegendo Webhooks com Payloads Assinados
Qualquer pessoa pode enviar uma requisição HTTP para o seu endpoint. Sem verificação, atacantes poderiam forjar eventos, levando a fraudes ou corrupção de dados. A solução padrão é assinar o payload com um segredo compartilhado.
Como Funcionam as Assinaturas HMAC
O provedor e você compartilham uma chave secreta. Para cada evento, o provedor calcula um HMAC (Hash-based Message Authentication Code) do corpo da requisição usando esse segredo e envia a assinatura em um cabeçalho (por exemplo, X-Signature). Você recalcula o HMAC do seu lado e compara. Se corresponderem, a requisição é autêntica e não foi adulterada.
Aqui está um exemplo mínimo em Python usando Flask:
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = b'your_shared_secret'
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Signature')
if not signature:
abort(400, 'Missing signature')
body = request.get_data()
expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(401, 'Invalid signature')
# Process the event
return '', 200
Sempre use hmac.compare_digest para comparação em tempo constante, a fim de evitar ataques de temporização. Além disso, verifique a assinatura antes de analisar o corpo JSON para evitar processamento desnecessário.
Boas Práticas para Assinaturas
- Use um segredo forte: gere uma chave aleatória de pelo menos 32 bytes e armazene-a com segurança (por exemplo, variável de ambiente).
- Inclua um timestamp: para evitar ataques de replay, os provedores frequentemente incluem um timestamp no payload assinado. Rejeite requisições com mais de alguns minutos.
- Rotacione segredos periodicamente: suporte múltiplos segredos ativos durante a rotação para evitar indisponibilidade.
- Documente o algoritmo de assinatura: especifique a função de hash (SHA-256 é o padrão) e o nome do cabeçalho.
Lidando com Falhas Usando Retentativas
Webhooks são do tipo fire-and-forget da perspectiva do provedor, mas o seu endpoint pode estar temporariamente indisponível. Para garantir a entrega, os provedores implementam políticas de retentativa. Como receptor, você deve estar preparado para retentativas e duplicatas.
Estratégias de Retentativa Usadas pelos Provedores
A maioria dos provedores tenta novamente com backoff exponencial: por exemplo, imediatamente, depois após 1 minuto, 5 minutos, 30 minutos, etc., até um número máximo de tentativas (frequentemente 5–10). Alguns também permitem configurar um cronograma de retentativas. O comportamento exato varia, então leia a documentação do provedor.
Projetando Seu Endpoint para Retentativas
Seu endpoint deve:
- Responder rapidamente: confirme o recebimento com um status 2xx em poucos segundos. Faça processamento pesado de forma assíncrona (por exemplo, enfileire o evento).
- Ser idempotente: o mesmo evento pode ser entregue múltiplas vezes. Use uma chave de idempotência (frequentemente fornecida no payload do evento ou no cabeçalho) para deduplicar. Armazene IDs de eventos processados por um período razoável.
- Retornar códigos de status apropriados: 2xx para sucesso, 4xx para erros do cliente (o provedor pode não tentar novamente), 5xx para erros do servidor (o provedor tentará novamente).
Exemplo de verificação de idempotência em Python:
processed_events = set() # In production, use a database or cache
@app.route('/webhook', methods=['POST'])
def webhook():
# ... signature verification ...
event = request.json
event_id = event['id']
if event_id in processed_events:
return '', 200 # Already processed
# Process event
processed_events.add(event_id)
return '', 200
Testando Sua Implementação de Webhook
Antes de entrar em produção, teste seu endpoint minuciosamente:
- Simule requisições assinadas: escreva testes unitários que gerem assinaturas válidas e inválidas.
- Teste cenários de retentativa: simule falhas e garanta que seu endpoint lide com duplicatas.
- Use ferramentas do provedor: muitos provedores oferecem uma CLI ou dashboard para enviar eventos de teste.
- Monitore e registre logs: registre eventos recebidos (sem dados sensíveis) e configure alertas para falhas de assinatura ou altas taxas de erro.
Comparação: Polling vs Webhooks
| Aspecto | Polling | Webhooks |
|---|---|---|
| Latência | Depende do intervalo (por exemplo, minutos) | Quase em tempo real |
| Carga no servidor | Alta (requisições frequentes) | Baixa (apenas em eventos) |
| Complexidade | Simples de implementar | Requer verificação de assinatura e tratamento de retentativas |
| Confiabilidade | Você controla as retentativas | O provedor controla as retentativas; você deve lidar com duplicatas |
FAQ
Como faço para verificar uma assinatura de webhook?
Calcule o HMAC do corpo bruto da requisição usando o segredo compartilhado e compare com o cabeçalho de assinatura usando uma função de comparação em tempo constante. Sempre verifique antes de processar o payload.
O que meu endpoint deve retornar para confirmar um webhook?
Retorne um código de status 2xx (por exemplo, 200 OK) assim que receber e enfileirar o evento para processamento. Evite processamento longo no handler da requisição para prevenir timeouts.
Como posso lidar com entregas duplicadas de webhook?
Use uma chave de idempotência do payload do evento (ou um ID de evento único) para rastrear eventos processados. Armazene esses IDs em um banco de dados ou cache e pule o processamento se o ID já tiver sido visto antes.
Pronto para testar seus payloads de webhook? Use nosso JSON Formatter para formatar e validar o corpo JSON de webhooks recebidos, facilitando a depuração.