Localizado por IA

Assinatura de Requisição

Toda requisição de webhook do Emailit inclui um cabeçalho X-Emailit-Signature contendo uma assinatura HMAC-SHA256 e um cabeçalho X-Emailit-Timestamp com o timestamp Unix de quando a requisição foi assinada. Você deve verificar esta assinatura para garantir que a requisição é autêntica e não foi alterada.

Como funciona

  1. Quando você cria um webhook, o Emailit gera um segredo de assinatura para esse endpoint
  2. Para cada entrega, o Emailit concatena o timestamp e o corpo bruto da requisição como {timestamp}.{corpoRaw}
  3. Um hash HMAC-SHA256 é calculado sobre essa string usando seu segredo de assinatura
  4. A assinatura é enviada no cabeçalho X-Emailit-Signature e o timestamp no X-Emailit-Timestamp
  5. Seu servidor recalcula a assinatura e a compara com o valor do cabeçalho

Verificando a assinatura

Para verificar uma assinatura de webhook:

  1. Extraia os cabeçalhos X-Emailit-Signature e X-Emailit-Timestamp da requisição
  2. Concatene o timestamp e o corpo bruto da requisição como {timestamp}.{corpoRaw}
  3. Calcule um hash HMAC-SHA256 dessa string usando seu segredo de assinatura do webhook
  4. Compare o hash calculado com a assinatura usando uma comparação segura contra timing
  5. Opcionalmente, rejeite requisições onde o timestamp seja mais antigo que alguns minutos para proteger contra ataques de replay

Verificar assinatura

import crypto from 'crypto';

function verifyWebhookSignature(rawBody, signature, timestamp, secret) {
  const signedPayload = `${timestamp}.${rawBody}`;
  const computed = crypto
    .createHmac('sha256', secret)
    .update(signedPayload, 'utf8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(computed, 'hex'),
    Buffer.from(signature, 'hex')
  );
}

// No seu manipulador de webhook:
const rawBody = req.body; // corpo bruto da requisição como string
const signature = req.headers['x-emailit-signature'];
const timestamp = req.headers['x-emailit-timestamp'];
const secret = process.env.WEBHOOK_SIGNING_SECRET;

// Proteção contra ataques de replay (tolerância de 5 minutos)
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
if (age > 300) {
  return res.status(401).send('Requisição muito antiga');
}

if (!verifyWebhookSignature(rawBody, signature, timestamp, secret)) {
  return res.status(401).send('Assinatura inválida');
}

Observações importantes

  • A assinatura é calculada sobre {timestamp}.{corpoRaw} — sempre use a string do corpo bruto da requisição, não uma versão re-serializada (ex: JSON.stringify(req.body)). Diferenças de espaçamento ou ordenação de chaves irão quebrar a assinatura.
  • Sempre use uma função de comparação segura contra timing (ex: crypto.timingSafeEqual, hmac.compare_digest, hash_equals) para prevenir ataques de timing.
  • Considere rejeitar requisições onde o X-Emailit-Timestamp seja mais antigo que alguns minutos para proteger contra ataques de replay.
  • Mantenha seu segredo de assinatura seguro — armazene-o em variáveis de ambiente, não no código fonte.
  • Você pode encontrar seu segredo de assinatura do webhook no painel do Emailit nas configurações do webhook, ou através da API de Webhooks.