signing: true, every request Omni Z-API sends to your endpoint carries an HMAC-SHA256 signature. This page describes exactly what is signed, so you can reproduce the computation on your server.
It applies to both webhook types — channel and template.
Headers you receive
t— the moment the request was signed, in seconds since the epoch (UTC).v1— the HMAC-SHA256 in lowercase hex. Thev1=prefix is the scheme version and is not part of what gets signed.
What gets signed
The signature is computed over a canonical string of five fields separated by\n (byte 0x0A, no \r, no trailing newline):
If the body arrives compressed
Requests of 1024 bytes or more are sent withcontent-encoding: gzip. The signature is computed over the uncompressed bytes.
If your HTTP framework decompresses the body for you, use the body it hands you. If you read the raw body, decompress it before computing the HMAC — otherwise verification passes on small payloads and fails on large ones, which looks like flakiness.
Complete example
Every value below is consistent with the others: with thissecret and this body, the v1 in the header is reproducible.
Secret returned by the API (example, not a real secret):
t comes from x-webhook-signature, while topic, partition, and offset come from x-idempotency-key:
Verifying on your server
Read the raw body. Frameworks that
JSON.parse and then JSON.stringify before you sign change the bytes (key order, whitespace, escapes) and the signature will not match. In Express, use express.raw({ type: 'application/json' }) on the webhook route.Replay protection
Comparet against your server’s current time and reject requests outside an acceptable window — 5 minutes is a reasonable value. That alone is not enough: also use x-idempotency-key to discard redeliveries of the same event, which arrive with the same value.
Rotating the secret
Thesecret is returned only in the create response or in an update response with signing: true. GET does not return it.
After rotating, verify with the secret from the most recent response you received.
When the signature does not match
- Are you signing all five fields, and not the body alone?
- Is the separator a bare
\n, with no\rand no trailing newline? - Is the body the raw one, with no reserialization by your framework?
- If
content-encoding: gzipwas present, did you decompress first? - Did
topic,partition, andoffsetcome from thex-idempotency-keyof this same request? - Is the secret the one from the most recent response with
signing: true?