verifyWebhook toleranceSeconds: 300 by default, and 0 turns replay off
In the Sume SDK, verifyWebhook rejects deliveries older than 300 seconds by default. toleranceSeconds 0 skips that check. When each setting is right.

verifyWebhook in @sume-com/sdk rejects a delivery whose x-sume-webhook-timestamp is more than 300 seconds from your clock. That window is the toleranceSeconds default. If you pass 0, the helper skips the timestamp check entirely, and a captured delivery with a valid signature verifies forever. Keep the default in production and use 0 only in a unit test with a fixed fixture.
What the setting does
The signed text is <timestamp>.<raw_body>, so the timestamp is covered by the HMAC. An attacker cannot change it without breaking the signature, but they can resend an old delivery unchanged. The replay window is what turns that resend into a rejection. The SDK enforces the window before it computes the HMAC, compares in constant time, and returns false instead of throwing.
| Option or rule | Value | Effect |
|---|---|---|
| toleranceSeconds | 300 (default) | Reject when the timestamp is more than 300 s from now |
| toleranceSeconds | 0 | Skip the timestamp check; replay is possible |
| body | raw string, ArrayBuffer or typed array | Parsed and re-serialized JSON will not verify |
| Return value | true or false | Malformed header, bad timestamp and bad signature all return false |
| Rotation | Several sume-v1 entries accepted | Any matching entry passes |
Why not just widen the window
A wide window hides the real problem. If valid deliveries fail the check, the usual cause is a skewed server clock, or a proxy that holds requests before forwarding them. Fix the clock first. The docs call five minutes a reasonable default for the replay window, and the SDK default matches it.
Two more details matter. First, the replay check does not replace deduplication. A delivery inside the window can still arrive twice, for example when an earlier attempt timed out, so store request_id or job_id and ignore repeats. Second, an empty secret must be a startup error. A verifier that quietly accepts an empty string as a secret turns signature checking into a formality.
A route that fails closed
The handler below reads the raw body first, refuses to start without a secret, keeps the default window, and answers 401 on any failure.
import { verifyWebhook } from "@sume-com/sdk";
const secret = process.env.SUME_COM_WEBHOOK_SIGNING_SECRET ?? "";
if (secret.length === 0) {
throw new Error("SUME_COM_WEBHOOK_SIGNING_SECRET is empty");
}
export async function POST(request: Request) {
const body = await request.text(); // raw, before JSON.parse
const ok = await verifyWebhook({
body,
headers: request.headers,
secret,
toleranceSeconds: 300, // the default, written out
});
if (!ok) return new Response("bad signature", { status: 401 });
const event = JSON.parse(body);
console.log("verified", event.event, event.job_id);
return new Response(null, { status: 204 });
}When 0 is acceptable
Use 0 in a test that replays a stored fixture whose timestamp is long past. Do not read it from an environment variable that production could set by accident. A better habit is to inject a fixed clock in tests so the production code path stays the same.
A test plan for the window
Four fixtures cover the window. A fresh delivery verifies. A delivery signed correctly but stamped 301 seconds ago returns false. A delivery with a missing timestamp header returns false, not an exception. And a delivery with a valid timestamp but a body edited by one byte returns false. If you test against a fixed clock, also check that a delivery stamped 299 seconds ago still passes, so you know the boundary is where you think it is.
Run the same four cases through any hand-rolled verifier in another language. The raw scheme is HMAC SHA-256 over the timestamp, a dot and the raw body, compared against each sume-v1= entry, which is easy to port. The part people forget is the window, because the signature alone will pass for an old but untouched delivery.
Job webhooks and run webhooks use the same signature scheme and the same signing secret, so this one verifier and one tolerance cover both. Route on the event field after verification. Do not expect a run_id in a job body or a job_id in a run body.
Sources
Related posts
More in Developers
- verifyWebhook toleranceSeconds 0 turns off the Sume replay check
In @sume-com/sdk, toleranceSeconds defaults to 300 and 0 skips the timestamp check. A runnable test shows an hour-old signed delivery passing only at 0.
- Sume video models by aspect ratio: who takes 9:16, 21:9 and 1:1
Seedance takes 21:9 through 9:16, Kling only 16:9, 9:16 and 1:1, Gemini Omni only 16:9 and 9:16. Full matrix of Sume video models as of 2026-10-08.
- AI video API with audio reference input: which Sume models accept it
Seedance (all four), Wan 3.0, MiniMax H3 and H3 Max accept audio references on Sume. Kling, Gemini Omni, Grok Imagine and the motion models do not.
- Video frames caps at 24 stills: a 15 s hook needs fps 1.6, not 2
Video frames returns at most 24 stills and fps up to 2. At 2 fps only 12 s are covered, so a 15 s hook needs fps 1.6; the Python below checks any length.
Written by Sume