Webhooks: Payloads Assinados e Retentativas

Backend2026-10-29TryQuickToolBox

Você acabou de integrar um provedor de pagamentos terceirizado. A documentação diz: "Enviaremos um webhook quando um pagamento for bem-sucedido." Simples, certo? Mas então você percebe: como saber se a requisição realmente veio deles? E se seu servidor estiver fora do ar quando eles enviarem? E se eles enviarem o mesmo evento duas vezes?

Webhooks são poderosos, mas exigem manuseio cuidadoso. Este guia cobre o essencial: assinar payloads para verificar autenticidade, implementar retentativas para lidar com falhas e garantir idempotência para evitar processamento duplicado.

O que são Webhooks?

Um webhook é um callback HTTP: em vez de seu aplicativo consultar uma API em busca de mudanças, o provedor envia um HTTP POST para uma URL que você especifica quando um evento ocorre. Isso torna as integrações eficientes e quase em tempo real.

Exemplos comuns incluem notificações de pagamento (Stripe, PayPal), gatilhos de CI/CD (GitHub, GitLab) e plataformas de mensagens (Slack, Discord).

Por que proteger Webhooks com Assinaturas?

Seu endpoint de webhook é acessível publicamente. Qualquer pessoa pode enviar uma requisição POST para ele. Sem verificação, um atacante poderia forjar eventos—por exemplo, marcando um pedido não pago como pago.

Assinar payloads com um segredo compartilhado permite verificar que a requisição se originou do remetente esperado e que o payload não foi adulterado.

Como funcionam as Assinaturas HMAC

O provedor e você compartilham uma chave secreta. Para cada webhook, o provedor calcula um HMAC (Hash-based Message Authentication Code) do payload usando esse segredo e inclui a assinatura em um cabeçalho (ex.: X-Signature). Você recalcula o HMAC do seu lado e compara.

HMAC garante integridade e autenticidade porque apenas alguém com o segredo pode produzir uma assinatura válida.

Implementando Payloads Assinados

Vamos percorrer uma implementação típica em Node.js (Express) e Python (Flask). Os princípios se aplicam a qualquer linguagem.

Passo 1: Obtenha e Armazene o Segredo

Quando você registra o webhook, o provedor fornece um segredo. Armazene-o com segurança—variáveis de ambiente ou um gerenciador de segredos—nunca no código.

Passo 2: Capture o Corpo Bruto

A verificação da assinatura requer os bytes exatos do corpo bruto. Se seu framework analisar o JSON antes de você verificar, a re-serialização pode alterar os bytes e quebrar a verificação.

No Express, use express.raw({ type: 'application/json' }) para a rota do webhook. No Flask, acesse request.get_data() antes de analisar.

Passo 3: Calcule e Compare Assinaturas

Use uma função de comparação de tempo constante para prevenir ataques de temporização.

// Node.js (Express)
const crypto = require('crypto');

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-signature'];
  const secret = process.env.WEBHOOK_SECRET;
  const expected = crypto.createHmac('sha256', secret).update(req.body).digest('hex');
  
  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process event
  const event = JSON.parse(req.body);
  // ... handle event
  res.status(200).send('OK');
});

Em Python, use hmac.compare_digest:

# Python (Flask)
import hmac, hashlib, os
from flask import request, abort

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Signature')
    secret = os.environ['WEBHOOK_SECRET'].encode()
    expected = hmac.new(secret, request.get_data(), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(signature, expected):
        abort(401)
    event = request.get_json()
    # ... handle event
    return 'OK', 200

Passo 4: Lide com Timestamps (Opcional mas Recomendado)

Alguns provedores incluem um timestamp na assinatura para prevenir ataques de replay. Por exemplo, o esquema do Stripe assina timestamp.payload. Você deve rejeitar requisições com timestamps muito no passado (ex.: mais de 5 minutos).

Implementando Retentativas

Redes falham. Seu servidor pode estar fora do ar, ou a requisição pode expirar. Sistemas de webhook confiáveis tentam novamente entregas falhas.

No Lado do Provedor

Se você está construindo o remetente do webhook, implemente retentativas com backoff exponencial. Um padrão comum: tentar novamente após 1 minuto, depois 5 minutos, depois 30 minutos, etc., até um número máximo de tentativas (ex.: 5). Inclua um ID de evento único no payload para que o receptor possa deduplicar.

No Lado do Receptor

Como receptor, você deve:

Idempotência: Lidando com Eventos Duplicados

Retentativas podem levar a eventos duplicados. Use o ID do evento (incluído pela maioria dos provedores) para rastrear eventos processados. Antes de processar, verifique se o ID já foi visto; se sim, pule.

Armazene IDs processados em um banco de dados com TTL, ou use uma chave de idempotência em sua lógica de negócios.

Testando Webhooks Localmente

Durante o desenvolvimento, seu servidor local não é acessível publicamente. Ferramentas como ngrok ou localtunnel criam uma URL pública que faz túnel para sua máquina. Alternativamente, use um serviço de teste de webhook que captura requisições e permite reproduzi-las.

Armadilhas Comuns e Melhores Práticas

Comparação: Webhooks Assinados vs. Não Assinados

AspectoAssinadoNão Assinado
SegurançaAlta: verifica origem e integridadeBaixa: qualquer um pode forjar
Esforço de implementaçãoModerado: calcular e comparar HMACMínimo
Recomendado paraTodos os sistemas em produçãoApenas redes internas e confiáveis

FAQ

Como verifico uma assinatura de webhook?

Calcule um HMAC do corpo bruto da requisição usando o segredo compartilhado, depois compare-o com o cabeçalho de assinatura usando uma função de tempo constante. Sempre use o corpo bruto, não o JSON analisado.

O que meu endpoint de webhook deve retornar?

Retorne um código de status 2xx rapidamente para confirmar o recebimento. Se o processamento falhar, retorne um 5xx para acionar uma retentativa, mas garanta que seu endpoint seja idempotente para lidar com duplicatas.

Quantas vezes um webhook será retentado?

Depende do provedor. A maioria tenta novamente várias vezes com backoff exponencial ao longo de horas ou dias. Consulte a documentação do provedor para detalhes.

Ao depurar entregas de webhook, inspecionar logs do servidor geralmente é necessário. Se você estiver usando Nginx, o Analisador de Logs Nginx pode ajudar a analisar logs de acesso para identificar requisições falhas ou padrões incomuns.