Webhooks
Verifying webhook signatures
Every request Nomod sends to your endpoint is signed. Verifying the signature protects your endpoint from fake or altered payloads.
Each webhook request includes three headers used for verification:
svix-id— unique identifier for the webhook messagesvix-timestamp— Unix timestamp (seconds since epoch) of when the message was sentsvix-signature— Base64-encoded HMAC-SHA256 signature of the payload
Your webhook's signing secret is available in Settings > Tools & customisations > Apps & APIs > Webhooks. Select your webhook and copy the value from the Signing secret field.
Verifying a signature using Svix libraries
The Svix library handles header parsing, timestamp validation, and signature comparison for you. It is Svix's recommended verification method.
- Install the Svix library
npm install svix # or yarn add svix
- Pass the raw request body and headers to the verify function. Svix handles the verification logic for you.
import { Webhook } from "svix";
const secret = "whsec_<your_signing_secret>";
// These headers are sent by Nomod with every webhook
const headers = {
"svix-id": "<svix-id from request>",
"svix-timestamp": "<svix-timestamp from request>",
"svix-signature": "<svix-signature from request>",
};
const payload = "<raw request body>";
const wh = new Webhook(secret);
// Throws on error, returns the verified content on success
const msg = wh.verify(payload, headers);
If verification succeeds, the function returns the verified message payload. If it fails, it throws an exception. Respond with HTTP 400 in that case.
Verifying a signature manually
If you cannot use the Svix library, you can verify the signature manually using HMAC-SHA256.
-
Extract the three svix headers from the incoming request:
svix-id,svix-timestamp, andsvix-signature. If any of these headers are missing, return a 400 response and reject the request. -
Construct the signed string by concatenating
svix-id,svix-timestamp, and the raw request body, separated by dots:signedContent = svix-id + "." + svix-timestamp + "." + rawBody -
Compute HMAC-SHA256 using the base64 portion of your signing secret (the part after the
whsec_prefix) as the key:const crypto = require('crypto'); const signedContent = `${svix_id}.${svix_timestamp}.${rawBody}`; const secret = "whsec_<your_signing_secret>"; const secretBytes = Buffer.from(secret.split('_')[1], "base64"); const signature = crypto .createHmac('sha256', secretBytes) .update(signedContent) .digest('base64'); -
Compare your computed signature against the value in
svix-signature. The header may contain multiple space-delimited signatures, each prefixed withv1,. Strip the prefix from each and compare against your computed signature using constant-time comparison. Return a 400 response if no signature matches.svix-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= -
Validate the timestamp. Compare
svix-timestampagainst your server's current time and reject any request where the difference exceeds five minutes. This prevents replay attacks.