Updated March 2026
HMAC webhook signing lets you verify that a webhook payload is real and hasn't been tampered with.
HMAC stands for Hash-based Message Authentication Code. It combines a hash function with a secret key to produce a signature. Only someone with the secret can compute the correct signature.
How it works in Sutrena: when a webhook fires, Sutrena computes an HMAC-SHA256 hash of the request body using your webhook secret. This hash goes in the X-Sutrena-Signature-256 header. On your server, you compute the same hash with your copy of the secret and compare. Match? The payload is authentic. No match? Someone is sending you forged data.
Two implementation details matter. First, use a constant-time comparison function. A naive === can leak timing information that lets an attacker guess the signature gradually. Node.js has crypto.timingSafeEqual for this. Second, compute the HMAC from the raw request body, not parsed-and-re-serialized JSON. JSON serialization doesn't guarantee key order or whitespace, so re-serializing can produce different bytes and a different hash.
You provide your own webhook secret when creating a webhook. Use a cryptographically random string of at least 32 characters. Sutrena doesn't generate secrets for you -- you control it from creation.
This is the same pattern GitHub, Stripe, and Shopify use. If you've verified webhooks from any of those, the Sutrena process is identical.
Can you skip verification? Yes. The webhook still delivers. But for production systems handling real data, HMAC verification is worth the few lines of code.
// Node.js webhook signature verification
const crypto = require("crypto");
function verifyWebhook(body, signature, secret) {
const computed = crypto
.createHmac("sha256", secret)
.update(body, "utf8")
.digest("hex");
const sig = signature.replace("sha256=", "");
return crypto.timingSafeEqual(
Buffer.from(computed, "hex"),
Buffer.from(sig, "hex")
);
}
// In your Express handler:
app.post("/webhook", (req, res) => {
const signature = req.headers["x-nishkala-signature-256"];
const isValid = verifyWebhook(req.rawBody, signature, process.env.WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).send("Invalid signature");
}
const data = JSON.parse(req.rawBody);
// Process the submission...
res.status(200).send("OK");
});