Webhooks Explicados: Payloads Assinados e Estratégias de Retry
Você construiu uma API e agora precisa notificar outros serviços quando eventos acontecem. Polling é ineficiente, então você implementa webhooks. Mas logo você enfrenta dois problemas comuns: como garantir que o payload não foi adulterado e o que fazer quando o endpoint receptor está fora do ar. Este artigo explica como usar payloads assinados e estratégias de retry para tornar seus webhooks seguros e confiáveis.
Por que Webhooks Precisam de Assinaturas e Retries
Webhooks são callbacks HTTP disparados por eventos. São simples de implementar, mas introduzem desafios de segurança e confiabilidade. Sem assinaturas, um atacante poderia forjar requisições para o seu endpoint, levando à corrupção de dados ou ações não autorizadas. Sem retries, falhas transitórias (como oscilações de rede ou erros temporários de servidor) causam eventos perdidos.
Payloads assinados resolvem a autenticidade: o remetente assina o payload com um segredo, e o receptor o verifica. Retries resolvem a confiabilidade: o remetente tenta novamente entregas falhas com backoff.
Como Funcionam os Payloads Assinados
Um payload assinado inclui uma assinatura criptográfica gerada a partir do corpo da requisição e de um segredo compartilhado. O método mais comum é o HMAC (Hash-based Message Authentication Code) com SHA-256.
Gerando a Assinatura
O remetente calcula o HMAC do corpo bruto da requisição usando a chave secreta. A assinatura é enviada em um header, frequentemente junto com um timestamp para prevenir ataques de replay.
// Exemplo em Node.js: gerando assinatura HMAC
const crypto = require('crypto');
function signPayload(payload, secret) {
const timestamp = Math.floor(Date.now() / 1000);
const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;
const signature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
return { timestamp, signature };
}
Inclua o timestamp e a assinatura nos headers, por exemplo, X-Webhook-Timestamp e X-Webhook-Signature.
Verificando a Assinatura
O receptor deve reconstruir o payload assinado usando o timestamp recebido e o corpo bruto, então calcular o HMAC e compará-lo de forma segura.
// Exemplo em Node.js: verificando assinatura
function verifySignature(rawBody, timestamp, receivedSignature, secret) {
const signedPayload = `${timestamp}.${rawBody}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Use comparação timing-safe
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(receivedSignature, 'hex')
);
}
Sempre use uma comparação timing-safe para prevenir ataques de timing. Além disso, verifique se o timestamp é recente (por exemplo, dentro de 5 minutos) para mitigar ataques de replay.
Projetando uma Estratégia de Retry
Retries são essenciais para uma entrega confiável de webhooks. Mas retries ingênuos podem sobrecarregar o receptor ou causar processamento duplicado. Uma boa estratégia de retry inclui:
- Backoff exponencial: Espere mais tempo entre cada retry (por exemplo, 1s, 2s, 4s, 8s...).
- Máximo de retries: Limite o número de tentativas (por exemplo, 5) para evitar loops infinitos.
- Fila de mensagens mortas (dead-letter queue): Após o máximo de retries, armazene o evento para inspeção manual.
- Idempotência: Inclua um ID de evento único para que o receptor possa deduplicar.
Aqui está um loop de retry simples em pseudocódigo:
function deliverWithRetry(url, payload, maxRetries = 5) {
let attempt = 0;
while (attempt < maxRetries) {
try {
const response = await fetch(url, { method: 'POST', body: payload });
if (response.ok) return;
} catch (error) {
// Log error
}
attempt++;
const delay = Math.pow(2, attempt) * 1000; // backoff exponencial
await new Promise(resolve => setTimeout(resolve, delay));
}
// Mover para dead-letter queue
}
Considere adicionar jitter ao backoff para evitar problemas de thundering herd.
Comparação: Polling vs Webhooks com Retries
| Aspecto | Polling | Webhooks com Retries |
|---|---|---|
| Latência | Alta (depende do intervalo) | Baixa (quase em tempo real) |
| Carga no servidor | Constante (mesmo sem eventos) | Orientada a eventos |
| Confiabilidade | Alta (receptor controla) | Depende da estratégia de retry |
| Segurança | Autenticação padrão de API | Requer payloads assinados |
Passo a Passo: Implementando Webhooks com Assinaturas e Retries
- Gere uma chave secreta para cada endpoint de webhook e compartilhe-a de forma segura.
- Na ocorrência do evento, construa o payload (JSON) e calcule a assinatura usando HMAC-SHA256 com o segredo e um timestamp.
- Envie a requisição POST para o endpoint com os headers:
Content-Type: application/json,X-Webhook-TimestampeX-Webhook-Signature. - No lado do receptor, verifique a assinatura usando o corpo bruto e o timestamp. Rejeite se inválida ou se o timestamp for muito antigo.
- Se a entrega falhar (resposta não-2xx ou erro de rede), tente novamente com backoff exponencial até um número máximo de tentativas.
- Após o máximo de retries, registre a falha e opcionalmente alerte; armazene o evento em uma dead-letter queue para replay manual.
- Garanta idempotência no receptor usando um ID de evento para ignorar duplicatas.
Armadilhas Comuns e Boas Práticas
- Não usar o corpo bruto: Muitos frameworks fazem parse do JSON e alteram o corpo. Use o corpo bruto para verificação de assinatura.
- Ignorar timestamps: Sem validação de timestamp, ataques de replay são possíveis.
- Sem idempotência: Retries podem causar processamento duplicado. Use chaves de idempotência.
- Retries infinitos: Sempre defina um limite máximo de retries e uma dead-letter queue.
- Não registrar logs: Registre tentativas de entrega e falhas para depuração.
FAQ
O que é um payload de webhook assinado?
Um payload de webhook assinado inclui uma assinatura criptográfica (por exemplo, HMAC) que permite ao receptor verificar que a requisição se originou do remetente esperado e não foi adulterada.
Quantas vezes devo tentar novamente um webhook que falhou?
Uma prática comum é de 3 a 5 retries com backoff exponencial. O número exato depende do seu SLA e da criticidade do evento. Sempre use uma dead-letter queue para eventos que excedam o limite de retries.
Posso usar webhooks sem assinaturas?
Não é recomendado. Sem assinaturas, seu endpoint fica vulnerável a requisições forjadas. Sempre use assinaturas (como HMAC) e valide timestamps para prevenir ataques de replay.
Ao depurar entregas de webhooks, inspecionar os logs do servidor é crucial. Se você usa Nginx, o Nginx Log Analyzer pode ajudar a fazer parse e analisar logs de acesso para rastrear requisições de webhook e identificar falhas.