Webhooks Explicados: Payloads Assinados e Estratégias de Retry
Você criou uma API que envia webhooks para os endpoints dos seus usuários. Mas então surgem os problemas clássicos: como garantir que o payload não foi adulterado? O que acontece quando o endpoint está fora do ar? Como evitar processamento duplicado? Neste artigo, vamos enfrentar esses desafios de frente com payloads assinados e estratégias robustas de retry.
Por que Webhooks Precisam de Assinaturas
Webhooks são callbacks HTTP que notificam outros sistemas sobre eventos. Como são enviados pela internet pública, estão vulneráveis a interceptação e falsificação. Sem uma assinatura, um ator malicioso poderia enviar webhooks falsos para seus usuários, acionando ações indesejadas. Assinar payloads com um segredo compartilhado garante autenticidade e integridade.
Como Funcionam os Payloads Assinados
A abordagem mais comum é HMAC (Hash-based Message Authentication Code) com SHA-256. O remetente e o destinatário compartilham uma chave secreta. O remetente calcula uma assinatura sobre o payload (e às vezes um timestamp) e a inclui em um cabeçalho. O destinatário recalcula a assinatura e a compara com a recebida.
Passo a Passo: Assinando um Payload
- Crie o payload: Serialize seus dados de evento para JSON. Garanta formatação consistente (por exemplo, sem espaços extras) porque a assinatura é calculada sobre os bytes exatos.
- Adicione um timestamp: Inclua um timestamp Unix no payload ou no cabeçalho para prevenir ataques de replay. O destinatário pode rejeitar requisições mais antigas que alguns minutos.
- Calcule o HMAC: Use sua chave secreta para gerar o HMAC-SHA256 da string do payload (ou timestamp + payload).
- Anexe a assinatura: Envie-a em um cabeçalho como
X-Signature.
Aqui está um exemplo em Python:
import hmac
import hashlib
import json
import time
secret = b'your_shared_secret'
payload = {"event": "user.created", "data": {"id": 123}}
body = json.dumps(payload, separators=(',', ':'))
timestamp = str(int(time.time()))
signed_content = timestamp + '.' + body
signature = hmac.new(secret, signed_content.encode(), hashlib.sha256).hexdigest()
# Send headers: X-Signature: signature, X-Timestamp: timestamp
Verificando a Assinatura
No lado receptor, você deve:
- Extrair o timestamp e a assinatura dos cabeçalhos.
- Rejeitar se o timestamp for muito antigo (por exemplo, >5 minutos) para prevenir ataques de replay.
- Recalcular a assinatura usando o mesmo método.
- Comparar usando uma função de tempo constante para evitar ataques de timing.
Exemplo de verificação em Node.js:
const crypto = require('crypto');
function verifySignature(req, secret) {
const signature = req.headers['x-signature'];
const timestamp = req.headers['x-timestamp'];
if (!signature || !timestamp) return false;
if (Date.now() / 1000 - timestamp > 300) return false; // 5 min
const body = JSON.stringify(req.body);
const signed = timestamp + '.' + body;
const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
Projetando Estratégias de Retry
Mesmo com assinaturas, webhooks podem falhar devido a problemas de rede, erros de servidor ou timeouts. Uma estratégia robusta de retry garante a entrega sem sobrecarregar o receptor.
Considerações-Chave
- Idempotência: Os receptores devem lidar com eventos duplicados de forma elegante. Use uma chave de idempotência (por exemplo, ID do evento) para deduplicar.
- Backoff exponencial: Retente após atrasos crescentes (por exemplo, 1s, 5s, 30s, 5m, 30m) para dar tempo ao receptor de se recuperar.
- Máximo de retries: Defina um limite (por exemplo, 5 tentativas) e então marque o webhook como falho, talvez alertando o usuário.
- Timeouts: Defina um timeout razoável (por exemplo, 10 segundos) para cada tentativa.
Implementando Retries
A maioria dos remetentes de webhooks usa um sistema de filas (por exemplo, Redis, RabbitMQ ou um banco de dados) para agendar retries. Aqui está um fluxo simplificado:
- Ao disparar o evento, enfileire um job de webhook com o payload e a URL de destino.
- O worker pega o job e tenta a entrega.
- Se o status da resposta for 2xx, marque como sucesso.
- Se não, agende um retry com backoff exponencial, até o máximo de tentativas.
- Após o máximo de tentativas, marque como falho e notifique.
Exemplo de cronograma de retry:
| Tentativa | Atraso |
|---|---|
| 1 | Imediato |
| 2 | 1 minuto |
| 3 | 5 minutos |
| 4 | 30 minutos |
| 5 | 2 horas |
Melhores Práticas para Segurança de Webhooks
- Use HTTPS: Sempre entregue webhooks via TLS para prevenir escutas.
- Rotacione segredos: Rotacione periodicamente os segredos compartilhados e suporte múltiplos segredos ativos durante a rotação.
- Valide URLs: Garanta que a URL do webhook seja HTTPS e não aponte para IPs internos (para prevenir SSRF).
- Registre e monitore: Mantenha logs de tentativas de entrega e falhas para depuração e auditoria.
FAQ
O que é um payload de webhook assinado?
Um payload assinado inclui uma assinatura criptográfica (geralmente HMAC) que permite ao receptor verificar a autenticidade do remetente e que o payload não foi adulterado.
Quantas vezes devo retentar um webhook com falha?
Não há uma resposta única, mas uma abordagem comum é 3–5 retries com backoff exponencial. Considere a criticidade do evento e a capacidade do receptor.
Posso usar JWT para assinaturas de webhook?
Sim, JWT pode ser usado, mas é mais complexo. HMAC é mais simples e suficiente para a maioria dos casos de uso. JWT é útil se você precisar incluir claims ou expiração dentro da própria assinatura.
Pronto para testar seus payloads de webhook? Use nosso JSON Formatter para validar e formatar seus payloads JSON antes de enviar.