Verifying Webhook Signatures

Every webhook delivery is signed with HMAC-SHA256. Verifying the signature ensures the request genuinely came from Hermon and has not been tampered with.

How Signatures Work

Hermon generates a signature for each delivery using your webhook secret and the raw request body:

signature = "sha256=" + HMAC_SHA256(secret, body_string)

secret — Your webhook secret shown when you created the endpoint. Format: whsec_<64 hex chars>

body_string — The raw JSON request body as a string. Do not parse it first. Any whitespace difference will cause the signature to fail.

Always use a constant-time comparison function (timingSafeEqual, hmac.compare_digest, hash_equals) to prevent timing attacks. A regular string equality check is not sufficient.

Verification Examples

Select your language to see a complete, production-ready verification example:

const crypto = require("crypto");

function verifyWebhook(secret, rawBody, signatureHeader) {
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}

// Express example — use express.raw() to get the raw body buffer
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.headers["x-hermon-signature"];
  if (!verifyWebhook("whsec_your_secret", req.body, sig)) {
    return res.status(401).send("Invalid signature");
  }
  const event = JSON.parse(req.body);
  // process event...
  res.status(200).send("OK");
});

The Signature Header

The signature is delivered in the X-Hermon-Signature request header in the format:

X-Hermon-Signature: sha256=abc123def456...

Receiving the Raw Body

The signature is computed over the raw request body — you must access the body before it is parsed. Many frameworks offer a way to do this:

Express (Node.js) — Use express.raw({ type: 'application/json' }) middleware on your webhook route.

Next.js — Export a custom config with api: { bodyParser: false } and read req.body as a buffer.

Flask (Python) — Use request.data or request.get_data() before accessing request.json.

Finding Your Secret

Your webhook secret is shown once when you create an endpoint via the dashboard or API. It is stored hashed and cannot be retrieved afterwards. If you lose it, use the Regenerate Secret endpoint to generate a new one — this will invalidate the old secret immediately.

Store your webhook secret in an environment variable. Never commit it to source control.