H3 Max job: poll or webhook? A signed Python receiver
Poll GET /v1/jobs/:id/status for one clip, use a signed webhook for batches. Python receiver that refuses an empty secret and checks the sume-v1 signature.

Poll when you submit one or two H3 Max clips from a script; use a signed webhook when you queue many. Both read the same job: a minimax-h3-max clip of 5 to 15 seconds is submitted once and ends as completed, failed or canceled.
Sume's docs recommend that most integrations store the job id and poll with backoff, and to use webhooks when you have a public HTTPS endpoint. Webhook delivery is an optimization: keep the status poll as a backup, because a delivery can fail after all attempts while the job still finished.
Which one to pick
At $1.00 for a 10-second 768p clip and $2.00 at 1080p, a batch of 100 clips is a $100 to $200 job, and you do not want 100 polling loops. A webhook gives one request per terminal event. The docs list job.completed, job.failed and job.canceled as the only events: there are no progress deliveries.
| Question | Poll status_url | Webhook |
|---|---|---|
| Needs a public HTTPS endpoint | No | Yes |
| Tells you progress | queued or processing | No, terminal events only |
| Retries | Your loop | Up to 10 attempts, 30 s apart by default, 10 s timeout each |
| Recovery if missed | Poll again | Poll, or redeliver the job's webhook |
| Best for | One clip, a script | Batches, servers |
Submit with a webhook
Send mode: "webhook" and a public HTTPS webhook_url on the submit. The API rejects localhost, private-network and non-HTTPS URLs. If you send webhook_url without a mode, you get webhook mode. The response is 202 with the job envelope and poll URLs, so you still have the job id.
A receiver that refuses an empty secret
Sume signs the raw body with HMAC SHA-256 over <timestamp>.<raw_body>. The headers are x-sume-webhook-timestamp and x-sume-webhook-signature, in the form sume-v1=<hex>. During a secret rotation the header can carry two comma-separated entries, and you accept the delivery if any of them matches. This function does that and treats an empty secret as a failure.
import hashlib, hmac, os, time
def verify(raw_body: bytes, ts: str, sig_header: str, secret: str,
tolerance: int = 300) -> bool:
if not secret:
raise RuntimeError("signing secret is empty")
try:
t = int(ts)
except ValueError:
return False
if abs(int(time.time()) - t) > tolerance:
return False
mac = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256)
want = "sume-v1=" + mac.hexdigest()
ok = False
for entry in sig_header.split(","):
if hmac.compare_digest(entry.strip(), want):
ok = True
return ok
SECRET = os.environ.get("SUME_COM_WEBHOOK_SIGNING_SECRET", "")
if not SECRET:
raise SystemExit("set SUME_COM_WEBHOOK_SIGNING_SECRET")What to do on receipt
Return any 2xx only after you store the event durably. Use job_id as your own idempotency key, since Sume can deliver the same event more than once. Verify before you download anything from the payload, and fetch the clip from the media.sume.com URL in the artifacts.
The signing secret is read from the dashboard Webhooks tab or GET /v1/webhooks/signing-secret with an API key that has account:read, and the docs name the variable SUME_COM_WEBHOOK_SIGNING_SECRET. Compare the x-sume-webhook-secret-fingerprint header with the dashboard if a signature does not verify.
- Reject timestamps older than five minutes.
- Keep the raw bytes: re-serialized JSON will not verify.
- Poll
status_urlfor any job that never sent an event.
Running both together
The safest pattern is a webhook as the fast path and a slow poll as the safety net. Submit with mode: "webhook", store the job id and the time you submitted, and run a sweep every few minutes that polls GET /v1/jobs/:id/status for any job that has no terminal event yet. When the sweep finds a terminal job, fetch the result the same way the webhook handler would, keyed on the job id so you never process a clip twice.
Sume's docs say that after ten refused automatic attempts you have a failed delivery but a job that still reached its terminal state, and that Redeliver re-sends the real terminal event with a fresh timestamp and signature. That means a webhook outage on your side is recoverable without paying for the clip again. A 15-second clip at 1080p is $3.00 on H3 Max, so recovering it matters more than resubmitting.
Testing the receiver first
Use the dashboard's Send test, or POST /v1/webhooks/test-deliveries, to fire a signed webhook.test payload at your URL. It is a dummy body with no job id and does not replay a real job. If it verifies, you know the secret, the raw-body handling and the timestamp tolerance are right before you spend on a real clip.
Sources
Related posts
More in Developers
- Lip sync for singing: fal can turn off guidance, Sume has no switch
fal's H3 Max Lip Sync is transcription-guided by default and can be switched off for singing or processed audio. Sume's documented body has no such field.
- HEAD-check reference image URLs before an image edit call (Python)
OpenAI caps edit images at 50 MB and Ideogram at 25 MB. A Python pre-flight that checks size, type and https before you send references to Sume.
- Hold AI Shorts for human approval: poll the run, then publish
A Python poll loop for a Sume Format run that stops at a human yes or no before any upload, so a reviewer can reject sameness first.
- How long can your webhook be down? Sume job vs run retry windows
Sume job webhooks retry 10 times, 30 s apart: about 5 minutes. Run webhooks back off to roughly 3 hours. The arithmetic, and what Redeliver covers.
Written by Sume