Firma y verificación de webhooks
Calcula firmas HMAC reales con WebCrypto y entiende cada paso de la verificación.
Tu endpoint de webhooks es una URL pública. Cualquiera puede enviarle un POST que diga "status": "delivered". La firma es lo único que distingue un evento real de uno fabricado.
Los pasos de la verificación
- Lee el cuerpo crudo (bytes), no el objeto parseado.
- Lee el timestamp y la firma de los headers.
- Rechaza si el timestamp tiene más de 5 minutos.
- Calcula
HMAC(secret, timestamp + "." + rawBody). - Compara en tiempo constante con la firma recibida.
Firma real con WebCrypto
mx.webhook.sign(payload, secret) usa crypto.subtle del navegador: es HMAC-SHA256 real. Cambia un solo carácter del payload y observa cómo cambia toda la firma.
// HMAC-SHA256 real con WebCrypto dentro del worker
const secret = "whsec_demo_9f2c";
const ts = "1760536800";
const body = JSON.stringify({ id: "evt_91", type: "sms.delivered", to: "+56912345678" });
const firma = await mx.webhook.sign(ts + "." + body, secret);
console.log("firma:", firma);
// Simula la verificación en tu servidor
const recalculada = await mx.webhook.sign(ts + "." + body, secret);
console.log("válida:", firma === recalculada);
// Un atacante altera el cuerpo
const alterado = body.replace("delivered", "failed");
const falsa = await mx.webhook.sign(ts + "." + alterado, secret);
console.log("alterado válido:", firma === falsa);Comparación en tiempo constante
Comparar strings con === termina en el primer carácter distinto, lo que filtra información por tiempo de respuesta. En Node usa crypto.timingSafeEqual; en otros entornos, compara byte a byte acumulando con XOR.
function safeEqual(a, b) {
if (a.length !== b.length) return false;
let diff = 0;
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
return diff === 0;
}Además de la firma, incluye una marca de tiempo en el contenido firmado y rechaza las peticiones con más de cinco minutos de antigüedad. Así un atacante que capture un webhook válido no puede reenviarlo días después.