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.

5 min readSume
All posts

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.

Poll vs webhook for H3 Max jobs (Sume docs read 2026-10-05)
QuestionPoll status_urlWebhook
Needs a public HTTPS endpointNoYes
Tells you progressqueued or processingNo, terminal events only
RetriesYour loopUp to 10 attempts, 30 s apart by default, 10 s timeout each
Recovery if missedPoll againPoll, or redeliver the job's webhook
Best forOne clip, a scriptBatches, 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_url for 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

All Developers posts

Written by Sume