Clock changes vs clock drift: what breaks Sume webhook checks
Sume webhook timestamps are Unix seconds, so time zone changes cannot break the 5-minute replay check; a drifting server clock can. A check to tell them apart.

No: a daylight saving change, or a tz database update, cannot make a valid Sume webhook look stale. The x-sume-webhook-timestamp header is a plain Unix time in seconds (the docs show a value like 1780000000), which has no time zone. What can break the replay check is your server's clock being wrong by more than the tolerance, which is five minutes in the webhook docs and 300 seconds by default in the SDK's `verifyWebhook`.
The question comes up every autumn when clocks change, and this week Node.js 26.11.0 shipped with an updated time zone database (tz 2026e, per the release notes). Neither touches Unix seconds. A container or VM whose clock has drifted does, usually after a pause, a snapshot restore or a missing time sync.
Tell the two apart in your logs
A failed check can mean a wrong secret, a changed body or a stale timestamp, and they need different fixes. The usual mistake is a single return False that hides which one it was. Verify the signature first, then compute the skew and report how far outside the window the delivery was. If many deliveries are stale by nearly the same amount, your clock is off by that amount. If only some are, a queue or proxy in front of the receiver is holding requests.
| Fact | Value | Source |
|---|---|---|
| Timestamp header | x-sume-webhook-timestamp, Unix seconds | Sume webhook docs |
| Signed string | <timestamp>.<raw_body>, HMAC-SHA256 | Sume webhook docs |
| Replay window | 5 minutes suggested, SDK default 300 s | Sume webhook and SDK docs |
| SDK toleranceSeconds 0 | Skips the timestamp check | Sume SDK docs |
| Redeliver | Sends a fresh timestamp and signature | Sume webhook docs |
Steps
- Check the host clock first: compare
date -u +%son the receiver with a trusted source and enable time sync on the VM or container host. - Alert on the skew, not on single failures: one stale delivery is noise, a steady stream of them is a clock.
- Log the signature result and the skew in seconds as separate fields.
- Do not widen the tolerance to hide a bad clock. A window of hours turns replay protection off in practice.
- For a delivery that was rejected only for being stale, use
POST /v1/jobs/{job_id}/webhook/redeliverafter fixing the clock; it needs thejobs:writescope and sends a fresh timestamp.
Checker
This function verifies the signature before reading the timestamp and returns ok, bad_signature or stale_by_<n>s. It refuses to start with an empty secret. The test block signs three bodies at ages 0, 400 and minus 400 seconds.
import hashlib
import hmac
import os
import time
SECRET = os.environ.get("SUME_COM_WEBHOOK_SIGNING_SECRET", "")
if not SECRET:
raise SystemExit("SUME_COM_WEBHOOK_SIGNING_SECRET is empty")
def check(raw: bytes, ts: str, sig_header: str, tolerance: int = 300) -> str:
want = hmac.new(SECRET.encode(), ts.encode() + b"." + raw, hashlib.sha256).hexdigest()
parts = [p.strip().removeprefix("sume-v1=") for p in sig_header.split(",")]
if not any(hmac.compare_digest(p, want) for p in parts):
return "bad_signature"
skew = time.time() - int(ts)
return "ok" if abs(skew) <= tolerance else f"stale_by_{abs(skew) - tolerance:.0f}s"
if __name__ == "__main__":
body = b'{"event":"job.completed"}'
for age in (0, 400, -400):
ts = str(int(time.time()) - age)
sig = hmac.new(SECRET.encode(), ts.encode() + b"." + body, hashlib.sha256).hexdigest()
print(age, check(body, ts, f"sume-v1={sig}"))What Sume does not do
Sume does not adjust timestamps for your clock and does not offer a clock-skew allowance beyond the window you configure. Setting the SDK tolerance to 0 skips the timestamp check entirely, which the SDK docs list as an option. Keep job-level polling as a backup for events you reject.
Sources
Related posts
More in Developers
- Sume webhook status is OK or ERROR; job status is completed or failed
A Sume job webhook body says status OK or ERROR, while the job endpoints say completed, failed or canceled. Branch on the event name and map both vocabularies.
- Swap the Sume video model with an env var and a catalog check in Node
Read the model id from VIDEO_MODEL, confirm it appears in GET /v1/videos/models, and fall back to sume/auto when it does not. A Node 20 script of 21 lines.
- Swift: URLSession async/await for one 30-second Wan 3.0 job
A 28-line main.swift that submits wan-3.0 for 30 seconds, polls with Task.sleep and saves the MP4. Runs on macOS or Linux with swiftc.
- Switch video models by changing one string: what can still break
On Sume's /v1/videos you swap the model id and keep the body. Duration range, resolution and aspect ratio are the three fields that may need adjusting.
Written by Sume