verifyWebhook toleranceSeconds: 0 turns the replay window off
In @sume-com/sdk, toleranceSeconds: 0 skips the timestamp check, so a day-old signed delivery verifies. Keep the 300-second default.

The verifyWebhook helper in @sume-com/sdk returns true only when two things hold: the signature matches and the timestamp is inside a replay window. The window defaults to 300 seconds, the same figure the webhook guide recommends. The option that sets it, toleranceSeconds, has one value that is easy to misread.
Passing 0 does not mean a zero-second window that rejects everything. The check is written as toleranceSeconds > 0 && ..., so 0 skips the timestamp comparison altogether. The signature is still checked, but a captured delivery now verifies forever.
What changes with the option
| Call | Result |
|---|---|
| Default tolerance (300 seconds) | false |
| toleranceSeconds: 0 | true |
| toleranceSeconds: 0 with the wrong secret | false |
Reproduce it
This script signs a body with a timestamp from 24 hours ago, the way a delivery would be signed, and calls the verifier twice. Run it as an ES module with Node 22 after npm install @sume-com/sdk. The top-level await works because the file is a .mjs module.
import { createHmac } from "node:crypto";
import { verifyWebhook } from "@sume-com/sdk";
const secret = "whsec_test";
const body = '{"event":"job.completed","job_id":"job_1"}';
const ts = Math.floor(Date.now() / 1000) - 86_400; // a day old
const sig =
"sume-v1=" + createHmac("sha256", secret).update(`${ts}.${body}`).digest("hex");
const headers = {
"x-sume-webhook-timestamp": String(ts),
"x-sume-webhook-signature": sig,
};
console.log("default:", await verifyWebhook({ body, headers, secret }));
console.log(
"toleranceSeconds 0:",
await verifyWebhook({ body, headers, secret, toleranceSeconds: 0 }),
);Why the window matters
- The signature covers the timestamp and the raw body together, so an attacker cannot change either. They can still resend an exact copy. The window limits how long a stolen copy is useful.
- With the check off, only your own dedupe stands in the way. Sume tells receivers to use
job_idas the idempotency key, and a replayed old event that your table has forgotten would be processed again. - Tests are the usual reason people reach for
0. Pass anowfunction that returns the delivery's timestamp instead, which keeps the window on and the fixture valid. - The helper never throws on a bad delivery. A missing header, a garbage timestamp or an empty secret all return
false, so oneifin the handler is enough, and the handler should answer 401 for it. - The same verifier covers job webhooks and run webhooks, so the window you choose applies to both kinds of delivery.
- If your clock drifts, fix the clock or widen the window to a few minutes. Do not remove it.
The recommended window and the signing scheme are in the webhooks guide. The package itself is on npm.
Sources
Related posts
More in Developers
- AI video API fallback: retry on another model when a job fails
Chain seedance-2.5, seedance-2 and kling-3 on Sume: poll status_url, read the job error category, and resubmit the brief to the next model.
- Sume video callback not verifying? Compare the secret fingerprint
Every Sume job webhook carries x-sume-webhook-secret-fingerprint. Compare it with the dashboard, accept two signatures in rotation, refuse an empty secret.
- Caption inputs on Sume: script_text, words, cues or segments?
Four caption inputs and only one may be sent. When to use script_text with transcription, word timings, or phrase cues for a silent clip. Plus the errors.
- video_filter_crop_out_of_bounds: FFmpeg crop pixels to fractions
Sume video-filter crop takes fractions of the frame, not pixels. Convert FFmpeg crop=w:h:x:y with a runnable Python check of the bounds.
Written by Sume