Webhooks firmados con HMAC-SHA256: cómo verificar FUVIHUB sin confiar en la red
Guía breve para consumir el header X-Veil-Signature: cuerpo raw, secret compartido, comparación constant-time.
FUVIHUB firma cada webhook saliente. El header `X-Veil-Signature` (alias legacy preservado, ver docs/brand-canon.md) contiene `sha256=<hex>` donde `<hex>` es el HMAC-SHA256 del cuerpo raw del request con tu webhook secret como clave.
La receta, en tres pasos que tu servidor debe hacer antes de procesar el payload: leer el cuerpo como bytes crudos (nunca como JSON ya parseado), calcular HMAC-SHA256 con el secret, y comparar en constant-time contra el header recibido.
import { createHmac, timingSafeEqual } from 'node:crypto';function verifyFuvihubSignature(raw: Buffer, secret: string, header: string) { const [, sig] = header.split('='); const expected = createHmac('sha256', secret).update(raw).digest(); const received = Buffer.from(sig ?? '', 'hex'); if (received.length !== expected.length) return false; return timingSafeEqual(expected, received); } ```
Errores comunes que vemos: parsear el body antes de firmar (JSON re-serializado no coincide), usar comparación `===` en vez de constant-time, y no rotar el secret después de un compromiso.
FUVIHUB expone rotación sin downtime: al emitir un nuevo secret, el anterior sigue válido durante la ventana de gracia configurada en `PLATFORM_WEBHOOK_GRACE_MS` (24 h por defecto). Tu consumidor debe aceptar ambos durante esa ventana.