Verify the Sume signature in the HTTP handler, not in the queue worker
A queue delay over 300 seconds makes a valid Sume signature look stale. Verify at receipt, enqueue the verified event, and use redeliver if a late check failed.

Verify the sume-v1 signature in the HTTP handler that receives the webhook, before the event goes on a queue. The signature check includes a replay window (300 seconds by default), and a worker that checks it after a long queue delay will reject a delivery that was valid when it arrived.
How the window bites
Sume signs <timestamp>.<raw_body> and sends the timestamp in x-sume-webhook-timestamp. A verifier that enforces the window compares that timestamp with the clock at the moment of the check. If a job sits in your queue for six minutes, a worker that verifies at pickup sees a timestamp six minutes old and returns false, even though the bytes and the secret are correct.
The failure looks like an attack, so people respond by widening the tolerance or turning it off. Neither is needed. Move the check to the point of receipt.
| Design | Check time | Result with a 360 s delay |
|---|---|---|
| verify in handler, then enqueue | at receipt | passes, window not exceeded |
| enqueue raw body, verify in worker | at pickup | fails, 360 s is over 300 s |
| verify in worker with tolerance 0 | at pickup | passes, but replay protection is off |
Handler shape
The handler below verifies with the SDK, then puts only the verified, parsed event on the queue. The worker never sees headers and never repeats the check. The queue message should carry job_id, which is also the idempotency key for the work.
import { verifyWebhook } from "@sume-com/sdk";
const secret = process.env.SUME_COM_WEBHOOK_SIGNING_SECRET;
if (!secret) throw new Error("SUME_COM_WEBHOOK_SIGNING_SECRET is not set");
export async function POST(request: Request) {
const body = await request.text();
const ok = await verifyWebhook({ body, headers: request.headers, secret });
if (!ok) return new Response("bad signature", { status: 401 });
const event = JSON.parse(body);
await queue.send({ jobId: event.job_id, event: event.event });
return new Response(null, { status: 204 });
}Two common variants
Some teams verify at an API gateway or edge function and forward a trusted internal header to the app. That is fine as long as the gateway verifies the raw bytes and the app is not reachable from the internet by another route. Others verify in the handler and also store the raw body, so a later job can be debugged against the exact bytes Sume signed.
Either way, the one place that checks the timestamp should run within seconds of the delivery, not minutes.
If a late check already failed
A delivery that you dropped for a stale timestamp is not lost. POST /v1/jobs/{job_id}/webhook/redeliver (API key with jobs:write) re-sends the real terminal event with a fresh timestamp and a fresh signature, and it does not use one of the 10 automatic attempts. The job also stays readable by polling its status_url.
Keep the handler quick, since Sume allows 10 seconds per attempt. Verification and one queue write fit in that time; the video download does not. See ack fast then enqueue for the queue side.
- Keep
toleranceSecondsat its default of 300. - Do not set it to 0 to silence a stale-timestamp error; that turns the replay check off.
- Check your own clock with NTP before blaming the window.
Sources
Related posts
More in Developers
- Verify x-sume-webhook-signature in Node: sume-v1 HMAC, raw body
A node:crypto verifier for Sume's sume-v1 signature that refuses an empty secret, checks the 5-minute window, and accepts either entry during a rotation.
- 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 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.
Written by Sume