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.

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_idas your idempotency key.
Sources
Related posts
More in Developers
- Verify a Sume TTS transcript_receipt SHA-256 yourself in Python
Recompute submitted_transcript_sha256 from your script with NFC and LF canonicalization and compare it to the transcript_receipt on a finished Sume TTS job.
- verifyWebhook toleranceSeconds 0 turns off the replay check
In @sume-com/sdk, toleranceSeconds defaults to 300 and 0 skips the timestamp check. Keep the default and use the now seam to test old deliveries.
- 4K vertical Short in Timeline: the 2160 cap and 1214x2160
Timeline output width and height top out at 2160 and must be even, so 2160x3840 is refused. What the largest 9:16 frame is and whether a Short needs it.
- video_analyze or video_segment not in Sume tools_list? Dev host only
Sume's docs list video_analyze and video_segment as dev-only, shown only when a flag is on. On mcp.sume.com use video_inspect for what is in a clip.
Written by Sume