Webhooks Explicados: Payloads Assinados e Retries
Você acabou de integrar um gateway de pagamento. A documentação diz: "Enviaremos uma requisição POST para o seu endpoint quando um pagamento for bem-sucedido." Fácil, certo? Você cria uma rota simples que aceita JSON, atualiza seu banco de dados e retorna 200. Então acontece: o pagamento de um cliente é bem-sucedido, mas seu servidor fica brevemente indisponível. O webhook nunca chega. Ou pior, chega duas vezes, e você credita a conta em dobro. Webhooks parecem simples até falharem.
Este artigo explica como webhooks realmente funcionam, por que payloads assinados são essenciais para a segurança e como construir uma lógica de retry que mantém sua integração confiável—mesmo quando as coisas dão errado.
O Que É um Webhook?
Um webhook é um callback HTTP: em vez de sua aplicação fazer polling em uma API para detectar mudanças, o provedor envia um POST HTTP para uma URL que você especifica quando um evento ocorre. É push em vez de pull.
Exemplos comuns incluem confirmações de pagamento (Stripe, PayPal), notificações de build de CI/CD (GitHub, GitLab) e plataformas de mensagens (Slack, Discord). O padrão é simples: você registra uma URL, o provedor envia payloads JSON para essa URL e seu servidor os processa.
Mas essa simplicidade esconde dois desafios críticos: autenticidade (como você sabe que a requisição realmente veio do provedor?) e confiabilidade (o que acontece se seu endpoint estiver fora do ar ou lento?).
Por Que Payloads Assinados Importam
Seu endpoint de webhook é uma URL pública. Qualquer pessoa pode enviar uma requisição POST para ele. Sem verificação, um atacante poderia forjar um evento de pagamento bem-sucedido, disparar um envio ou corromper seus dados.
Payloads assinados resolvem isso incluindo uma assinatura criptográfica nos headers da requisição. O provedor calcula um HMAC (hash-based message authentication code) usando um segredo compartilhado e o corpo da requisição, e então o envia junto com o payload. Seu servidor recalcula o HMAC e compara. Se coincidirem, a requisição é autêntica.
Como a Assinatura HMAC Funciona
- O provedor e você compartilham uma chave secreta (fornecida durante a configuração).
- Para cada webhook, o provedor concatena o timestamp e o corpo bruto da requisição, e então calcula um hash HMAC-SHA256 usando o segredo.
- A assinatura é enviada em um header, geralmente algo como
X-SignatureouStripe-Signature. - Seu servidor recalcula o HMAC usando o mesmo segredo e o compara com a assinatura recebida.
- Se coincidirem, você processa o evento; caso contrário, rejeita com 401.
Sempre use o corpo bruto da requisição para o cálculo do HMAC. Se seu framework fizer o parse do JSON antes da verificação, o corpo pode ser alterado (espaços em branco, ordem das chaves), fazendo a verificação falhar.
Exemplo: Verificando uma Assinatura de Webhook em Node.js
const crypto = require('crypto');
const express = require('express');
const app = express();
// Use raw body for signature verification
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-signature'];
const secret = process.env.WEBHOOK_SECRET;
const payload = req.body; // Buffer
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
// Process the event
const event = JSON.parse(payload.toString());
// ... handle event
res.status(200).send('OK');
});
Observe o uso de timingSafeEqual para prevenir ataques de temporização. Nunca use uma comparação simples com === para assinaturas.
Projetando Lógica de Retry
Mesmo com assinaturas, webhooks podem falhar. Seu servidor pode estar fora do ar, a rede pode derrubar a requisição, ou seu processamento pode atingir timeout. Provedores geralmente tentam novamente entregas falhas, mas você também deve projetar seu sistema para lidar com retries de forma graciosa.
O Que Conta Como Falha?
A maioria dos provedores considera qualquer resposta diferente de 2xx como falha. Isso inclui redirecionamentos 3xx, erros de cliente 4xx e erros de servidor 5xx. Alguns também impõem um timeout (por exemplo, 5 segundos). Se você não responder rapidamente, a entrega é marcada como falha e tentada novamente.
Backoff Exponencial com Jitter
Provedores geralmente tentam novamente com backoff exponencial: primeira tentativa após 1 minuto, depois 5 minutos, 30 minutos, etc. Adicionar jitter (variação aleatória) previne thundering herds se muitos webhooks falharem simultaneamente.
Se você está construindo um emissor de webhooks, implemente o backoff assim:
- Retry 1: após 10 segundos
- Retry 2: após 1 minuto
- Retry 3: após 5 minutos
- Retry 4: após 30 minutos
- Retry 5: após 2 horas
Após um número máximo de tentativas, mova o evento para uma dead-letter queue para inspeção manual.
Idempotência: Lidando com Entregas Duplicadas
Retries significam que seu endpoint pode receber o mesmo evento múltiplas vezes. Seu processamento deve ser idempotente: processar o mesmo evento duas vezes deve ter o mesmo efeito que processá-lo uma vez.
Estratégias comuns:
- Deduplicação por ID de evento: Armazene IDs de eventos processados em um banco de dados ou cache. Antes de processar, verifique se o ID já foi visto.
- Upsert em vez de insert: Use operações de banco de dados que naturalmente lidam com duplicatas (por exemplo,
INSERT ... ON CONFLICT DO NOTHING). - Chaves de idempotência: Para operações como cobrar um cartão, passe uma chave única para que o provedor de pagamento ignore requisições duplicadas.
Melhores Práticas para Consumidores de Webhooks
| Prática | Por Que Importa |
|---|---|
| Verificar assinaturas | Previne requisições forjadas |
| Responder rapidamente (em até 2s) | Evita timeouts e retries desnecessários |
| Processar de forma assíncrona | Desacopla o recebimento do processamento pesado |
| Usar handlers idempotentes | Seguro contra entregas duplicadas |
| Registrar todos os eventos | Auxilia na depuração e auditoria |
| Monitorar falhas | Detecta problemas antes que escalem |
Considere também validar o timestamp na assinatura para prevenir ataques de replay. Muitos provedores incluem um timestamp no payload assinado; rejeite eventos mais antigos que alguns minutos.
Melhores Práticas para Produtores de Webhooks
Se você está construindo um serviço que envia webhooks, siga estas diretrizes:
- Assine cada payload com HMAC-SHA256.
- Inclua um ID de evento único e timestamp.
- Tente novamente com backoff exponencial e jitter.
- Forneça uma forma para consumidores visualizarem logs de entrega e reexecutarem eventos manualmente.
- Documente claramente seu processo de verificação de assinatura.
FAQ
O que é um payload de webhook assinado?
Um payload assinado inclui uma assinatura criptográfica (geralmente HMAC) calculada a partir do corpo da requisição e um segredo compartilhado. O receptor verifica a assinatura para garantir que a requisição é autêntica e não modificada.
Quantas vezes um webhook deve ser tentado novamente?
Não há um número universal, mas a maioria dos provedores tenta novamente de 3 a 5 vezes ao longo de várias horas. Depois disso, o evento geralmente é marcado como falho e pode ficar disponível para replay manual. Seu sistema deve lidar com duplicatas de forma idempotente independentemente do número de retries.
Posso usar webhooks sem HTTPS?
Tecnicamente sim, mas você deve sempre usar HTTPS. Webhooks frequentemente contêm dados sensíveis, e sem TLS, um atacante poderia interceptar ou adulterar o payload. HTTPS também garante que você está enviando para o servidor pretendido.
Depurando Webhooks com Logs do Nginx
Quando webhooks falham, o primeiro lugar para olhar são os logs do seu servidor web. Se você usa Nginx, os logs de acesso e erro registram cada requisição, incluindo códigos de status e tempos de resposta. Você pode identificar rapidamente padrões: 401s repetidos (falha na verificação de assinatura), 500s (erros da aplicação) ou timeouts.
Para uma análise mais rápida, experimente o Nginx Log Analyzer para fazer o parse e resumir o tráfego de webhooks, filtrar por código de status e identificar endpoints problemáticos.