Verify a Sume video callback_url webhook in Python, rotation-aware
A Python verifier for a Sume video job callback: HMAC over timestamp.body, five-minute window, rotation entries, and an empty secret refused.

Short answer
Sume signs a video job callback with HMAC SHA-256 over <timestamp>.<raw_body> and sends x-sume-webhook-timestamp and x-sume-webhook-signature: sume-v1=<hex>. Verify the raw bytes, reject timestamps more than five minutes away, and accept the delivery if any sume-v1= entry in the header matches, because the header carries one entry per live secret during rotation. Per the video docs, callback_url must be HTTPS and Sume posts when the job reaches a terminal state.
The verifier
It refuses an empty secret, parses the timestamp, checks the window, computes the expected value and compares it against every entry in constant time. The demo signs its own sample body to show the round trip; replace it with the raw request body and headers from your framework.
import asyncio, hashlib, hmac, os, time
def verify(raw, ts, header, secret, tol=300):
if not secret:
raise ValueError("empty signing secret")
try:
sent = int(ts)
except ValueError:
return False
if abs(time.time() - sent) > tol:
return False
mac = hmac.new(secret.encode(), f"{sent}.".encode() + raw,
hashlib.sha256).hexdigest()
ok = False
for entry in header.split(","):
ok = hmac.compare_digest(entry.strip(), "sume-v1=" + mac) or ok
return ok
async def main():
secret = os.environ.get("SUME_COM_WEBHOOK_SIGNING_SECRET", "")
raw = b'{"job_id":"job_demo"}'
ts = str(int(time.time()))
mac = hmac.new(secret.encode(), f"{ts}.".encode() + raw,
hashlib.sha256).hexdigest()
print(verify(raw, ts, "sume-v1=" + mac, secret))
asyncio.run(main())Facts to get right
| Item | Value |
|---|---|
| Signed string | timestamp, a dot, then the raw JSON body |
| Algorithm | HMAC SHA-256, hex, prefixed sume-v1= |
| Replay window | Reject outside about five minutes |
| Rotation | Header holds one entry per live secret, comma-separated, newest first |
| Secret name | SUME_COM_WEBHOOK_SIGNING_SECRET, read from the dashboard Webhooks tab |
| Response | Return a 2xx after storing the event durably |
Common mistakes
The first is verifying the parsed and re-serialized JSON instead of the raw bytes; key order or spacing changes the digest. The second is comparing with ==, which leaks timing. The third is checking only the first header entry, which fails during a rotation when the newest entry is the one you do not yet hold. The fourth is accepting an empty secret, which turns the check into a signature anyone can forge: the function above raises instead.
Idempotent handling
Use the job_id as your key and ignore a second delivery of the same terminal state. Store the event before returning 2xx; Sume retries non-2xx responses and network errors. After the event, fetch the video with the content endpoint or the unsigned_urls on the job, using your API key header.
Testing it
Test three cases before you go live: a correct signature passes, a body changed by one byte fails, and a timestamp ten minutes old fails. Add a fourth for rotation by building a header with two entries, a wrong one first and the right one second, and assert it passes. A fifth test should assert that an empty secret raises, since silently accepting everything is the worst failure this code can have.
Caveats
The envelope is Sume's standard job webhook, not OpenRouter's video.generation.* shape, so do not reuse an OpenRouter parser. Delivery is on terminal states only, so a webhook is not a progress feed.
Related posts
More in Developers
- Verify a Sume webhook signature in Python for a finished TTS job
A short Python verifier for Sume job webhooks: HMAC SHA-256 over timestamp.body, the sume-v1 header, a five-minute replay window, and no empty secret.
- Verify a Sume job webhook in Python for finished Omni clips
Check x-sume-webhook-signature on job.completed callbacks: HMAC SHA-256 over timestamp.raw_body, 5-minute tolerance, rotation-safe, empty secret rejected.
- Verify a Sume video download against checksum_sha256 in Python
Stream a finished Sume artifact to disk, hash it with hashlib, and compare to checksum_sha256 from the job result. Skips cleanly when the field is null.
- Sume webhook signature header: why the sume-v1= prefix is checked
verifyWebhook only compares entries that start with sume-v1= and drops others, so a future scheme in the same header cannot break a receiver. A test proves it.
Written by Sume