signing: true, cada petición que Omni Z-API envía a tu endpoint lleva una firma HMAC-SHA256. Esta página describe exactamente qué se firma, para que puedas reproducir el cálculo en tu servidor.
Vale para los dos tipos de webhook: de canal y de plantilla.
Headers que recibes
t— el instante de la firma, en segundos desde la época (UTC).v1— el HMAC-SHA256 en hexadecimal minúsculo. El prefijov1=es la versión del esquema y no forma parte de lo que se firma.
Qué se firma
La firma se calcula sobre una cadena canónica de cinco campos separados por\n (byte 0x0A, sin \r, sin salto de línea final):
Si el cuerpo llega comprimido
Las peticiones de 1024 bytes o más se envían concontent-encoding: gzip. La firma se calcula sobre los bytes descomprimidos.
Si tu framework HTTP ya descomprime el cuerpo automáticamente, usa el cuerpo que te entrega. Si lees el cuerpo crudo, descomprímelo antes de calcular el HMAC: de lo contrario la validación pasa en los payloads pequeños y falla en los grandes, lo que parece intermitencia.
Ejemplo completo
Todos los valores de abajo son consistentes entre sí: con estesecret y este cuerpo, el v1 del header es reproducible.
Secret devuelto por la API (ejemplo, no es un secret real):
t viene del x-webhook-signature, y topic, partition y offset vienen del x-idempotency-key:
Validando en tu servidor
Lee el cuerpo crudo. Los frameworks que hacen
JSON.parse y luego JSON.stringify antes de que firmes cambian los bytes (orden de claves, espacios, escapes) y la firma no cuadra. En Express, usa express.raw({ type: 'application/json' }) en la ruta del webhook.Protección contra replay
Comparat con la hora actual de tu servidor y rechaza las peticiones fuera de una ventana aceptable: 5 minutos es un valor razonable. Eso solo no basta: usa también el x-idempotency-key para descartar reentregas del mismo evento, que llegan con el mismo valor.
Rotación del secret
Elsecret se devuelve únicamente en la respuesta de creación o de actualización con signing: true. El GET no lo devuelve.
Después de rotar, valida con el secret de la última respuesta recibida.
Si la firma no cuadra
- ¿Estás firmando los cinco campos, y no solo el cuerpo?
- ¿El separador es
\npuro, sin\r, y no hay salto de línea al final? - ¿El cuerpo es el crudo, sin reserializar por tu framework?
- Si vino
content-encoding: gzip, ¿lo descomprimiste antes? - ¿
topic,partitionyoffsetsalieron delx-idempotency-keyde esta misma petición? - ¿El secret es el de la última respuesta con
signing: true?