Skip to content

How do I verify the webhook signature?

Requires Elite

Every delivery carries the header X-Webhook-Signature: sha256=<hexdigest>. The value is an HMAC-SHA256 over the raw request body, keyed with your webhook’s signing secret. To verify it, compute the same HMAC on your side and compare it in constant time. If the values don’t match: discard the request — it did not come from Notory (unmodified).

Important: the HMAC is computed over the unmodified body bytes. So verify before JSON parsing, i.e., on the raw body — re-serialized JSON almost never has the same bytes.

  1. Store the secret. Save the secret shown at creation time in your receiver system’s secret store (environment variable, Vault — not in code).

  2. Add the verification logic. In your endpoint, implement: read the raw body → compute HMAC-SHA256 with the secret → compare it in constant time against the X-Webhook-Signature header (after the sha256= prefix) → reject with 401 on a mismatch.

  3. Verify with “Send test.” Trigger a test delivery in Notory (ping event). Only once your endpoint accepts the signature and returns 2xx should you start accepting production events.

  • Signature generation in Notory. Before sending, Notory serializes the envelope {"event": …, "data": …} to JSON and computes HMAC_SHA256(secret, body) as a hex digest. The header is exactly X-Webhook-Signature: sha256=<hexdigest>; in addition, X-Webhook-Event names the event.
  • What the check guarantees: authenticity (only someone who knows the secret can sign validly) and integrity (any change to the body invalidates the signature). It does not replace TLS — use HTTPS endpoints so confidentiality is also covered.
  • No timestamp in the envelope. The payload contains no signed timestamp or delivery ID — if you need strict replay protection, you’ll have to implement it yourself at the application level (e.g., idempotency based on the id of the object in data).
  • Compare in constant time. Use hmac.compare_digest / crypto.timingSafeEqual instead of == to rule out timing attacks on the comparison.
  • Secret rotation. There is no endpoint to reissue the secret: create the webhook again, switch the receiver to the new secret, delete the old webhook.