Webhooks Explicados: Payloads Assinados e Retentativas

Backend2026-11-04TryQuickToolBox

Você acabou de integrar um provedor de pagamento terceirizado, e ele promete notificar seu aplicativo quando um pagamento for bem-sucedido via webhook. Mas como você sabe que a requisição realmente veio deles e não de um atacante? E o que acontece se seu servidor estiver brevemente fora do ar quando o webhook disparar? Sem as devidas proteções, você corre o risco de processar eventos falsos ou perder atualizações críticas. Este artigo explica como proteger webhooks com payloads assinados e implementar mecanismos de retentativa robustos.

O que é um Webhook?

Um webhook é um callback HTTP definido pelo usuário. Quando um evento ocorre em um sistema de origem (por exemplo, um pagamento é concluído), ele envia uma requisição HTTP POST para uma URL que você fornece. Diferentemente do polling, onde você pergunta repetidamente por atualizações, os webhooks enviam dados para você quase em tempo real. Eles são amplamente utilizados para notificações de eventos, como gatilhos de CI/CD, mensagens de chat ou mudanças de status de pagamento.

Por que proteger Webhooks com Assinaturas?

Os endpoints de webhook são acessíveis publicamente, então qualquer pessoa pode enviar uma requisição POST para eles. Sem verificação, um atacante poderia forjar eventos, levando a ações não autorizadas, como marcar um pedido como pago. Assinar payloads com um segredo compartilhado permite verificar a autenticidade e a integridade.

Como funcionam os Payloads Assinados

O remetente e o destinatário compartilham uma chave secreta. Para cada webhook, o remetente calcula uma assinatura criptográfica (geralmente um HMAC) sobre o corpo da requisição e a inclui em um cabeçalho. O destinatário recalcula a assinatura usando o mesmo segredo e compara. Se corresponderem, a requisição é genuína.

Escolhendo o Algoritmo Correto

HMAC-SHA256 é o padrão de fato. É rápido, seguro e disponível em todas as principais linguagens. Evite MD5 ou SHA1, que são considerados fracos.

Passo a Passo: Verificando um Payload Assinado

  1. Extraia a assinatura do cabeçalho (por exemplo, X-Signature).
  2. Obtenha o corpo bruto da requisição como string ou array de bytes. Não o analise primeiro; qualquer transformação alterará a assinatura.
  3. Calcule o HMAC usando seu segredo e o corpo bruto.
  4. Compare a assinatura calculada com a recebida usando uma comparação de tempo constante para prevenir ataques de temporização.

Aqui está um exemplo em Node.js usando Express:

const crypto = require('crypto');
const express = require('express');
const app = express();

// Use raw body parser for webhook route
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-signature'];
  const secret = process.env.WEBHOOK_SECRET;

  const hmac = crypto.createHmac('sha256', secret);
  const digest = hmac.update(req.body).digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest))) {
    return res.status(401).send('Invalid signature');
  }

  // Process the event
  const event = JSON.parse(req.body);
  // ... handle event

  res.status(200).send('OK');
});

Armadilha Comum: Usar um analisador de corpo JSON antes da verificação. O express.json() do Express analisa o corpo, então os bytes brutos são perdidos. Sempre use express.raw() para rotas de webhook ou capture o corpo bruto separadamente.

Projetando uma Estratégia de Retentativa

Mesmo com assinaturas, webhooks podem falhar: seu servidor pode estar fora do ar ou a requisição pode expirar. Um remetente confiável tenta novamente entregas falhas. Como destinatário, você também deve lidar com retentativas graciosamente.

Retentativas no Lado do Remetente

Se você está construindo o remetente, implemente backoff exponencial com jitter. Por exemplo, tente novamente após 1 minuto, depois 5 minutos, depois 30 minutos, até um número máximo de tentativas (por exemplo, 5). Inclua um ID de evento único no payload para que o destinatário possa deduplicar.

Idempotência no Lado do Destinatário

Como as retentativas podem causar entregas duplicadas, seu endpoint deve ser idempotente. Use o ID do evento para rastrear eventos processados. Armazene-o em um banco de dados com uma restrição de unicidade ou use um cache como Redis com TTL. Se o ID do evento já foi visto, retorne um 200 OK imediatamente sem reprocessar.

Exemplo usando um índice único de banco de dados:

async function handleWebhook(event) {
  try {
    await db.query('INSERT INTO processed_events (event_id) VALUES ($1)', [event.id]);
  } catch (err) {
    if (err.code === '23505') { // unique violation
      return; // already processed
    }
    throw err;
  }
  // process event
}

Melhores Práticas para Endpoints de Webhook

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

Aspecto Webhooks Assinados Webhooks Não Assinados
Segurança Alta – verifica remetente e integridade Baixa – qualquer um pode forjar requisições
Complexidade de Implementação Moderada – requer HMAC e gerenciamento de segredos Baixa – apenas aceite POST
Casos de Uso Pagamentos, dados sensíveis Notificações não críticas

FAQ

Como verifico uma assinatura de webhook em Python?

Use o módulo hmac com SHA256. Compare o digest calculado com o cabeçalho usando hmac.compare_digest para comparação de tempo constante.

O que devo fazer se meu endpoint de webhook receber um evento duplicado?

Verifique o ID do evento contra um armazenamento de IDs processados. Se for duplicado, retorne um 200 OK e pule o processamento. Isso garante idempotência.

Quantas retentativas um remetente de webhook deve realizar?

Não há um número fixo, mas 3–5 retentativas com backoff exponencial (por exemplo, 1min, 5min, 30min) é comum. Depois disso, marque a entrega como falha e alerte.

Pronto para testar seu endpoint de webhook? Use nosso Formatador JSON para formatar e validar payloads durante o desenvolvimento.