Verify a Sume webhook in Node: raw body, timing-safe compare

A short Node verifier for Sume webhooks: sign timestamp.rawBody with HMAC SHA-256, compare sume-v1 entries with timingSafeEqual, and refuse an empty secret.

4 min readSume
All posts

In Node, verify against the raw body string, not JSON.parse output. Sume signs <timestamp>.<raw_body> with HMAC SHA-256 and sends the hex digest as sume-v1=<digest> (read 2026-10-06 in the webhook docs).

What is the smallest correct verifier?

The function loops over every sume-v1= entry, because the header carries one entry per live secret during a rotation.

import crypto from "node:crypto";

export function verify(rawBody, timestamp, header, secret, tolerance = 300) {
  if (!secret) throw new Error("empty webhook secret");
  const ts = Number(timestamp);
  if (!Number.isFinite(ts)) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - ts) > tolerance) return false;
  const digest = crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  const expected = Buffer.from(`sume-v1=${digest}`);
  let ok = false;
  for (const entry of header.split(",")) {
    const got = Buffer.from(entry.trim());
    if (got.length === expected.length && crypto.timingSafeEqual(got, expected)) ok = true;
  }
  return ok;
}

What should I do in practice?

Run it against a Send test delivery first.

  • Capture the raw body before any JSON middleware runs.
  • Return 2xx only after you store the event.
  • Use job_id as your idempotency key.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume