Verify a Sume job.completed webhook in Python (stdlib)
A stdlib Python verifier for Sume job webhooks: HMAC SHA-256 over timestamp.raw_body, five-minute tolerance, rotation-safe, and it refuses an empty secret.

To verify a Sume job webhook in Python, compute HMAC SHA-256 of <timestamp>.<raw_body> with your signing secret, prefix it with sume-v1=, and compare it to every entry in the x-sume-webhook-signature header. Use the raw bytes of the body, not re-serialized JSON, and reject an empty secret or a timestamp outside your tolerance window.
What the docs specify
From the Webhooks docs.
| Item | Value |
|---|---|
| Signed text | <timestamp>.<raw_body>, HMAC SHA 256 |
| Headers | x-sume-webhook-timestamp and x-sume-webhook-signature: sume-v1=<hex> |
| Rotation | One sume-v1= entry per live secret, comma separated; accept if any matches |
| Replay window | Reject outside tolerance; five minutes is a reasonable default |
| Job events | job.completed, job.failed, job.canceled |
The verifier
Stdlib only. hmac.compare_digest avoids timing leaks, and every entry is compared so a rotation does not reveal which secret matched.
import hashlib, hmac, time
def verify(raw_body: bytes, timestamp: str, header: str, secret: str, tol=300):
if not secret:
raise ValueError("signing secret is empty")
try:
ts = int(timestamp)
except ValueError:
return False
if abs(int(time.time()) - ts) > tol:
return False
mac = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256)
expected = "sume-v1=" + mac.hexdigest()
ok = False
for entry in header.split(","):
if hmac.compare_digest(entry.strip(), expected):
ok = True
return okWhere the secret comes from
Read it from the Webhooks tab of the dashboard, or from GET /v1/webhooks/signing-secret with a key that has account:read. Store it in an environment variable such as SUME_COM_WEBHOOK_SIGNING_SECRET, never in source.
After it verifies
Fetch the image through the job result endpoint, since the webhook is a signal. Keep polling as a backup, as the docs advise, and dedupe on the job id because a delivery can repeat.
Sources
Related posts
More in Developers
- Image request timed out: do you pay for it on Sume?
Sume bills image jobs by outcome. A client timeout does not cancel the job, so a finished image can still bill. Use async mode and poll; do not resubmit.
- Instagram Login or Facebook Login for a Reels publishing app?
Both logins can publish Reels. They differ in host, token and scopes, and resumable upload plus some metrics are Facebook Login only. Pick before you build.
- Instagram API Reels total_interactions: how it is calculated
Instagram's insights reference defines total_interactions as likes, saves, comments and shares minus unlikes and deletions, and marks it in development.
- Instagram content_publishing_limit: read quota_usage before a bulk run
Read GET /<IG_USER_ID>/content_publishing_limit before queuing Reels. Meta's pages cite 100 posts per 24 hours and show quota_total 50, so do not hard-code it.
Written by Sume