Live-commerce clip webhook: verify the Sume signature in Python
Verify a Sume webhook in Python: HMAC SHA256 over timestamp.raw_body, any sume-v1 entry, a 300-second window, and reject an empty secret.

To verify a Sume webhook that reports a finished live-commerce clip, compute HMAC SHA256 over <timestamp>.<raw_body> with your signing secret, compare it to each sume-v1= entry in x-sume-webhook-signature, reject a timestamp more than five minutes old, and refuse to run at all if the secret is empty. A trim, caption or render submitted with webhook_url is delivered as job.completed, job.failed or job.canceled.
Source: Webhooks: signed job callbacks, which gives the TypeScript version; this is the same logic in Python.
What Sume signs
Sume signs the raw JSON body, not the parsed object, so read the request bytes before any JSON middleware reformats them. The headers are x-sume-webhook-timestamp and x-sume-webhook-signature: sume-v1=<hex>. During a secret rotation the signature header has one entry per live secret, newest first, separated by commas; accept the delivery if any entry matches. Each delivery also carries x-sume-webhook-secret-fingerprint, which you can compare to the fingerprint next to the secret in the dashboard when verification fails.
Why reject an empty secret
An empty secret is a valid HMAC key, so a verifier with an unset environment variable computes a signature anyone can forge. Failing closed when the secret is blank turns a silent hole into a loud error at startup. The same function should also compare in constant time and ignore entries that do not start with sume-v1=.
The function below takes the raw bytes, the two header values and the secret, and returns a boolean. It includes a self-test with a signature it generates itself, so you can run the file as it is.
import hashlib, hmac, time
def verify(raw: bytes, ts: str, header: str, secret: str, tol: int = 300) -> bool:
if not secret:
raise ValueError("signing secret is empty")
if not ts.isdigit() or abs(int(time.time()) - int(ts)) > tol:
return False
msg = ts.encode() + b"." + raw
want = "sume-v1=" + hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
ok = False
for entry in header.split(","):
entry = entry.strip()
if entry.startswith("sume-v1=") and hmac.compare_digest(entry, want):
ok = True
return ok
if __name__ == "__main__":
body = b'{"event":"job.completed","job_id":"job_demo"}'
ts = str(int(time.time()))
sig = "sume-v1=" + hmac.new(b"s3cret", ts.encode() + b"." + body,
hashlib.sha256).hexdigest()
print(verify(body, ts, "sume-v1=old," + sig, "s3cret"))
try:
verify(body, ts, sig, "")
except ValueError as e:
print("rejected:", e)
After it verifies
Store the event durably, then return any 2xx. Sume retries network errors and non-2xx responses, up to 10 attempts in total, so use job_id as your idempotency key and handle a repeated delivery as a no-op. If you do heavy work, such as copying the finished clip, do it after you acknowledge, not before.
| Input | Where it comes from | Rule |
|---|---|---|
| Raw body | Request bytes | Do not re-serialize JSON |
| Timestamp | x-sume-webhook-timestamp | Reject past 300 seconds |
| Signature | x-sume-webhook-signature | Accept any sume-v1 entry |
| Secret | Dashboard or GET /v1/webhooks/signing-secret | Reject when empty |
| Idempotency | job_id | Treat repeats as no-ops |
Where this fits in a live-commerce pipeline
A common shape is: cut the product moment with trim, burn the price with caption cues, and have each job post to one endpoint. The verifier is the only code that touches the secret, so test it once and share it. The stored post on live-commerce host videos by API shows the Format version with a webhook.
Sources
Related posts
More in Use cases
- Live-commerce run spend cap: $120 typical, $400 default, $500 maximum
A Sume live-commerce run is capped near $120, against a $400 default and $500 maximum for other Formats. What the cap does and how to set it on an API call.
- Live sale replay: three highlight trims, keyframe then exact
Cut three highlights from a live-sale replay under 30 minutes with Sume video-trim: keyframe for a fast first look, exact for the ad, $0.02 per cut.
- Log segment count and billable minutes per holiday ad
Read segment_count, billable_minutes, and warnings from each Sume timeline render result and log one cost line per holiday ad. A Python script, no network.
- Only 12 left badge over a product clip: compose overlay, $0.02
A low-stock badge on a product clip is $0.02 per variant with Sume compose overlay. Set position, width_ratio and margin_ratio; re-render as stock drops.
Written by Sume