Verifying webhook signatures
Verify the HMAC-SHA256 signature on Nerva webhooks to confirm authenticity and prevent tampering.
When you configure a secret on a webhook, Nerva signs every request with HMAC-SHA256. Verifying the signature on your server is the single most important security step in your webhook handler — without it, anyone who knows your endpoint URL can forge events.
How it works
For every delivery, Nerva:
- Takes the raw JSON body of the request (exactly the bytes sent over the wire — no re-serialization).
- Computes
HMAC-SHA256(secret, raw_body). - Sends the hex digest in the
X-Nerva-Signatureheader.
Your server:
- Reads the
X-Nerva-Signatureheader. - Computes the same HMAC over the raw body you received.
- Compares the two using a constant-time comparison (not
===).
If they match, the request came from Nerva and the body wasn't tampered with.
You must compute the HMAC over the raw request body bytes, not over a re-serialized JavaScript / Python object. JSON serialization isn't deterministic — key ordering and whitespace differ — and any mismatch will cause verification to fail. Always buffer the raw body before parsing.
Node.js / Express
Next.js Route Handler
Python / FastAPI
Ruby / Sinatra
PHP
Go
Common verification failures
| Symptom | Likely cause |
|---|---|
| Always fails | Body was parsed/re-serialized before HMAC was computed. Use the raw body. |
| Sometimes fails | Reverse proxy / load balancer is rewriting the body or stripping the header. |
| Test endpoint passes, real events fail | Unicode handling differs. Webhooks are UTF-8; ensure your raw-body reader doesn't transcode. |
| Header missing entirely | No secret is configured on the integration. Set one in Dashboard → Integrations → your webhook → Secret. |
Replay protection (coming soon)
Nerva will add a X-Nerva-Timestamp header and sign timestamp.body instead
of just body. This lets you reject replayed requests with a stale timestamp
window. The current single-body HMAC will continue to work; the new header
will be additive.
See also
- Webhooks overview — setup, scope, retries
- Event reference — every event with payload shape