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.

5 min readSume
All posts

Sume signs each job webhook with HMAC SHA-256 over <timestamp>.<raw_body> and sends it as x-sume-webhook-signature: sume-v1=<hex>, with the timestamp in x-sume-webhook-timestamp. Verify the raw bytes, reject timestamps more than five minutes old, accept any matching entry during a secret rotation, and reject an empty secret. Use job_id as your idempotency key, because retries and redelivery can repeat an event.

What the docs specify

The Webhooks page lists three events for generation jobs (job.completed, job.failed, job.canceled), up to 10 delivery attempts 30 seconds apart with a 10 second timeout, and a signing secret from the dashboard Webhooks tab or GET /v1/webhooks/signing-secret. Store it as SUME_COM_WEBHOOK_SIGNING_SECRET.

Submit an Omni clip with mode: "webhook" and a public HTTPS webhook_url, per the Video Router request shape.

Webhook delivery facts from the Sume docs (read 2026-10-04)
ItemValue
Signed stringtimestamp, a dot, then the raw body
Header formatsume-v1=hex, comma-separated during rotation
Replay tolerance5 minutes suggested
Attempts10, 30 seconds apart, 10 s timeout
Idempotency keyjob_id

The verifier

This function returns false for an empty secret, a bad timestamp, a stale timestamp or no matching entry. It compares every entry in constant time. The last lines sign a sample body so you can run the file as is.

import hashlib, hmac, time

def verify(raw: bytes, ts: str, header: str, secret: str, tol: int = 300) -> bool:
    if not secret:
        return False
    try:
        t = int(ts)
    except ValueError:
        return False
    if abs(int(time.time()) - t) > tol:
        return False
    mac = hmac.new(secret.encode(), f"{t}.".encode() + raw, hashlib.sha256)
    expected = "sume-v1=" + mac.hexdigest()
    ok = False
    for entry in header.split(","):
        if hmac.compare_digest(entry.strip(), expected):
            ok = True
    return ok

secret, body, now = "demo-secret", b'{"event":"job.completed"}', str(int(time.time()))
sig = "sume-v1=" + hmac.new(secret.encode(), now.encode() + b"." + body, hashlib.sha256).hexdigest()
print(verify(body, now, sig, secret), verify(body, now, sig, ""))

Operational notes

Read the body as bytes before any JSON parsing; re-serialized JSON will not match. Store the event durably, return 2xx, then process. Keep polling status_url as a fallback, since ten refused attempts leave the job complete but the delivery failed; POST /v1/jobs/{job_id}/webhook/redeliver re-sends the real terminal event with a fresh signature.

In your handler, read the artifact URL from the payload.artifacts array, and de-duplicate on job_id.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume