Localizado por IA

Firma de Solicitudes

Cada solicitud de webhook de Emailit incluye un encabezado X-Emailit-Signature que contiene una firma HMAC-SHA256 y un encabezado X-Emailit-Timestamp con la marca de tiempo Unix de cuando se firmó la solicitud. Debes verificar esta firma para asegurar que la solicitud es auténtica y no ha sido alterada.

Cómo funciona

  1. Cuando creas un webhook, Emailit genera un secreto de firma para ese endpoint
  2. Para cada entrega, Emailit concatena la marca de tiempo y el cuerpo de la solicitud sin procesar como {timestamp}.{rawBody}
  3. Se calcula un hash HMAC-SHA256 sobre esa cadena usando tu secreto de firma
  4. La firma se envía en el encabezado X-Emailit-Signature y la marca de tiempo en X-Emailit-Timestamp
  5. Tu servidor recalcula la firma y la compara con el valor del encabezado

Verificación de la firma

Para verificar una firma de webhook:

  1. Extrae los encabezados X-Emailit-Signature y X-Emailit-Timestamp de la solicitud
  2. Concatena la marca de tiempo y el cuerpo de la solicitud sin procesar como {timestamp}.{rawBody}
  3. Calcula un hash HMAC-SHA256 de esa cadena usando tu secreto de firma del webhook
  4. Compara el hash calculado con la firma usando una comparación segura contra ataques de temporización
  5. Opcionalmente, rechaza solicitudes donde la marca de tiempo sea anterior a unos minutos para protegerte contra ataques de repetición

Verificar firma

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')
  );
}

// En tu manejador de webhook:
const rawBody = req.body; // cuerpo de solicitud sin procesar como string
const signature = req.headers['x-emailit-signature'];
const timestamp = req.headers['x-emailit-timestamp'];
const secret = process.env.WEBHOOK_SIGNING_SECRET;

// Protección contra ataques de repetición (tolerancia de 5 minutos)
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
if (age > 300) {
  return res.status(401).send('Solicitud demasiado antigua');
}

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

Notas importantes

  • La firma se calcula sobre {timestamp}.{rawBody} — siempre usa el cuerpo de la solicitud sin procesar como string, no una versión re-serializada (ej. JSON.stringify(req.body)). Las diferencias en espacios en blanco o el orden de las claves romperán la firma.
  • Siempre usa una función de comparación segura contra ataques de temporización (ej. crypto.timingSafeEqual, hmac.compare_digest, hash_equals) para prevenir ataques de temporización.
  • Considera rechazar solicitudes donde el X-Emailit-Timestamp sea anterior a unos minutos para protegerte contra ataques de repetición.
  • Mantén tu secreto de firma seguro — guárdalo en variables de entorno, no en el código fuente.
  • Puedes encontrar tu secreto de firma del webhook en el panel de Emailit bajo la configuración de webhooks, o a través de la API de Webhooks.