Webhooks Explicados: Payloads Assinados e Retentativas

Backend2026-10-18TryQuickToolBox

Você construiu uma API e agora precisa notificar outros serviços quando eventos ocorrem. Você configurou um endpoint de webhook, mas logo descobre que qualquer um pode enviar payloads falsos e, quando seu servidor está fora do ar, os eventos são perdidos para sempre. Esta é uma armadilha comum: webhooks são simples em conceito, mas exigem um design cuidadoso para segurança e confiabilidade.

Neste guia, vamos abordar os fundamentos dos webhooks: como assinar payloads para evitar adulteração, como verificar assinaturas no lado receptor e como implementar lógica de retentativa com backoff exponencial para lidar com falhas. Também abordaremos idempotência para evitar processamento duplicado. Ao final, você terá um sistema de webhook robusto e confiável.

O que são Webhooks?

Webhooks são callbacks HTTP definidos pelo usuário. Quando um evento ocorre em um sistema de origem (por exemplo, um pagamento é processado), ele envia uma requisição HTTP POST para uma URL que você especifica, com um payload contendo detalhes do evento. Isso permite comunicação em tempo real entre serviços sem polling.

No entanto, webhooks introduzem dois desafios:

Protegendo Webhooks com Payloads Assinados

Para evitar requisições forjadas, o remetente deve assinar o payload usando um segredo compartilhado. O receptor pode então verificar a assinatura para autenticar o remetente. O método mais comum é HMAC (Hash-based Message Authentication Code) com SHA-256.

Como funciona a assinatura HMAC

  1. O remetente e o receptor compartilham uma chave secreta (idealmente uma string longa e aleatória).
  2. Para cada webhook, o remetente calcula um HMAC do corpo da requisição usando o segredo e o inclui em um cabeçalho (por exemplo, X-Signature).
  3. O receptor calcula o mesmo HMAC no corpo recebido e compara com o valor do cabeçalho. Se coincidirem, a requisição é autêntica.

É crucial assinar o corpo bruto da requisição, não uma versão parseada, para evitar discrepâncias devido a diferenças de serialização JSON.

Exemplo: Assinando um Payload em Node.js

const crypto = require('crypto');

function signPayload(payload, secret) {
  return crypto.createHmac('sha256', secret).update(payload).digest('hex');
}

// No lado do remetente:
const payload = JSON.stringify({ event: 'payment.success', amount: 100 });
const signature = signPayload(payload, 'your-secret-key');
// Inclua a assinatura no cabeçalho: X-Signature: signature

Verificando a Assinatura no Receptor

No lado receptor, você deve verificar a assinatura antes de processar o payload. Use uma comparação de tempo constante para evitar ataques de temporização.

const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

// No seu endpoint de webhook:
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-signature'];
  const payload = req.rawBody; // corpo bruto como string
  if (!verifySignature(payload, signature, 'your-secret-key')) {
    return res.status(401).send('Invalid signature');
  }
  // Processe o evento
  res.status(200).send('OK');
});

Nota: Sempre use HTTPS para evitar ataques man-in-the-middle. Considere também incluir um timestamp no payload assinado para prevenir ataques de replay.

Implementando Retentativas com Backoff Exponencial

Mesmo com um receptor confiável, problemas de rede ou indisponibilidades temporárias podem fazer a entrega do webhook falhar. Um remetente robusto deve tentar novamente entregas falhas. No entanto, retentativas ingênuas podem sobrecarregar o receptor ou causar processamento duplicado. A solução: backoff exponencial com jitter e um limite máximo de retentativas.

Estratégia de Retentativa

Exemplo: Lógica de Retentativa em Node.js

async function deliverWebhook(url, payload, secret, maxRetries = 5) {
  const signature = signPayload(payload, secret);
  let attempt = 0;
  while (attempt < maxRetries) {
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-Signature': signature },
        body: payload,
      });
      if (response.ok) return;
      throw new Error(`HTTP ${response.status}`);
    } catch (error) {
      attempt++;
      if (attempt >= maxRetries) throw error;
      const delay = Math.min(1000 * 2 ** attempt + Math.random() * 1000, 30000);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Esta função tenta novamente até 5 vezes com backoff exponencial e jitter, limitando o atraso a 30 segundos. Ajuste os parâmetros conforme suas necessidades.

Garantindo Idempotência

Retentativas podem causar entregas duplicadas, então seu manipulador de webhook deve ser idempotente. Inclua um ID de evento único no payload e faça o receptor rastrear IDs processados para evitar processamento duplo.

// No receptor:
const processedEvents = new Set();

app.post('/webhook', (req, res) => {
  const { id } = req.body;
  if (processedEvents.has(id)) {
    return res.status(200).send('Already processed');
  }
  // Processe o evento
  processedEvents.add(id);
  res.status(200).send('OK');
});

Em produção, use um armazenamento persistente (por exemplo, Redis ou um banco de dados) para rastrear IDs processados entre reinicializações.

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

Aspecto Não Assinado Assinado (HMAC)
Segurança Qualquer um pode forjar requisições Apenas o detentor do segredo pode criar assinatura válida
Integridade Payload pode ser adulterado Adulteração invalida a assinatura
Complexidade Baixa Moderada (gerenciamento de chaves, verificação)
Caso de uso Redes internas e confiáveis Qualquer webhook público

FAQ

O que é uma assinatura de webhook?

Uma assinatura de webhook é um hash criptográfico (geralmente HMAC) do payload, criado com um segredo compartilhado. Permite que o receptor verifique se a requisição veio do remetente esperado e se o payload não foi alterado.

Quantas vezes devo tentar novamente um webhook com falha?

Não há um número fixo, mas a prática comum é de 3 a 5 retentativas com backoff exponencial. Depois disso, marque a entrega como falha e alerte sua equipe. Considere a criticidade do evento: notificações de pagamento podem exigir mais retentativas do que atualizações não críticas.

Posso usar webhooks sem HTTPS?

Você nunca deve usar webhooks sobre HTTP simples em produção. HTTPS criptografa o payload em trânsito, prevenindo escutas e ataques man-in-the-middle. Mesmo com assinaturas, HTTP expõe dados sensíveis.

Pronto para testar seus payloads de webhook? Use nosso Formatador JSON para formatar e validar payloads JSON antes de enviá-los ou depurá-los.