Skip to main content
Pazy signs every delivery with your endpoint’s signing secret. Verify the signature on every request, before you parse or act on the body. Anyone who knows your URL can send it a request, and the signature is the only proof that Pazy sent it.

How the signature works

  • signing_secret is the secret we gave you for this endpoint.
  • timestamp is the value of the X-Pazy-Webhook-Timestamp header, exactly as sent.
  • raw_body is the request body bytes exactly as received, before any parsing.
  • The result is hex-encoded and sent in X-Pazy-Webhook-Signature as sha256=<64 hex characters>.

Verification steps

1

Read the raw body

Capture the body as a string before your framework parses it. Re-serialized JSON can differ in key order or whitespace, and then the signature won’t match.
2

Check the timestamp

Parse X-Pazy-Webhook-Timestamp as an integer. If it differs from your current time by more than 5 minutes in either direction, reject the request with 401. This stops an attacker replaying a captured request.
3

Recompute the signature

Compute HMAC-SHA256(secret, timestamp + "." + rawBody) and hex-encode it.
4

Compare in constant time

Strip the sha256= prefix from the header and compare the two values with a timing-safe function. Return 401 on a mismatch.
5

Parse and process

Only now parse the JSON and handle the event.
Use a constant-time comparison: crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python, hmac.Equal in Go. A plain == leaks timing information.

Code examples

Test your implementation

Use this fixed example to check your code. It computes the signature for a known secret, timestamp and body.
Expected signature:
That timestamp is in the past, so your full handler rejects it at the timestamp check. Test the HMAC step on its own, or temporarily widen the window.

Accept two secrets during a rotation

While a secret rotation is in progress, accept a request if it verifies against either secret:

Common mistakes