Callback or polling for a ported video worker: pick by job count
Replacing a Sora polling worker on Sume: when callback_url beats polling, when polling is enough, and one function that does both.

Use polling when your worker handles a handful of video jobs and already loops; use callback_url when jobs number in the hundreds or when you do not want a process alive just to wait. Sume lets you do both on the same job: send callback_url on POST /v1/videos, and keep a poll as the backup, because a failed delivery never changes the job's real state (Sume webhooks, read 2026-10-06).
The OpenAI Sora API ended on 2026-09-24 (Magic Hour tracker, read 2026-10-06). A worker that polled it needs a new target either way, so this is the moment to choose how it hears about results.
The two ways to hear back
Polling is GET /v1/videos/{id} until the status is completed, failed or cancelled. The docs suggest about a 30-second interval, since video takes from 30 seconds to several minutes. A webhook is a signed POST that Sume sends once the job reaches a terminal state; it sends terminal events only, never progress (Sume video docs, read 2026-10-06).
| Question | Polling | callback_url |
|---|---|---|
| Who initiates | Your worker, every poll | Sume, once per terminal state |
| Progress events | Status only | None, terminal events only |
| Needs a public endpoint | No | Yes, HTTPS, not localhost or private network |
| If your endpoint is down | Not applicable | Up to 10 attempts, 30 s apart, 10 s timeout each |
| Recovery path | Keep polling | Poll the status URL; redeliver per job |
A rule of thumb
- Under about twenty jobs at a time and a worker already running: poll. It is simpler and needs no endpoint.
- Hundreds of jobs, or a serverless host that cannot sleep: callback, with the handler returning 2xx fast and doing the copy in the background.
- Anything customer-facing: callback plus a slow poll, so a missed delivery is found within minutes.
One function that does both
The function submits with a callback and a stable Idempotency-Key, then polls with a long interval until the job is terminal. If the callback arrives first, your handler can set a flag that this loop checks; here it just polls so the sketch runs alone.
import os
import time
import requests
BASE = "https://api.sume.com/v1/videos"
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
def render(prompt: str, key: str, callback: str, deadline_s: int = 900) -> dict:
body = {"model": "gemini-omni-flash-1.1", "prompt": prompt,
"aspect_ratio": "9:16", "resolution": "720p", "duration": 5,
"callback_url": callback}
r = requests.post(BASE, headers={**H, "Idempotency-Key": key},
json=body, timeout=30)
r.raise_for_status()
url = r.json()["polling_url"]
end = time.time() + deadline_s
while time.time() < end:
time.sleep(30)
s = requests.get(url, headers=H, timeout=30).json()
if s["status"] in ("completed", "failed", "cancelled"):
return s
raise TimeoutError(url)Two details matter. The Idempotency-Key makes a retried submit return the original job rather than create a second paid one. And the deadline is yours: do not lengthen an HTTP timeout to wait for video, because the sync mode waits at most 30 seconds and the job continues regardless (Jobs and results, read 2026-10-06).
On the receiving end, verify the x-sume-webhook-signature over <timestamp>.<raw_body> and use job_id as your idempotency key; the delivery can repeat. Earlier posts cover the 10-second handler limit and the polling loop port in more detail.
Running both without double work
When a job has a callback and a slow poll, two paths can reach the same result. The rule is simple: make finishing a job idempotent on the job id. The first path to see a terminal state does the work, such as copying the file and updating the row, and the second sees the row already done and returns.
A single status column with a compare-and-set update is enough. Set it from running to done only if it is still running, and do the copy only if the update changed a row. That one statement also handles a repeated webhook delivery, which the docs allow for, since a delivery can be retried up to ten times.
- Verify the signature before reading the body as trusted, and reject an empty secret at startup.
- Return a 2xx quickly and do the download in a background task, since the delivery times out after 10 seconds.
- Keep a slow poll for any job older than a few minutes, to catch a missed delivery.
- Use the
job_idas the key for your own deduplication.
Sources
Related posts
More in Developers
- Split a long video into Shorts episodes: Python trim ranges
Generate back-to-back video trim ranges for a long vertical video with a tail rule, so no episode is a sliver. Respects the 1800 s source and 900 s output caps.
- Start a song at the chorus in a Short: split, then soundtrack
Timeline's soundtrack has no in-point. Cut the chorus with Timeline audio split, then pass the new audio_url as the soundtrack. Steps and cost: $0.01 + $0.10.
- Stitch four voice takes into one 3-minute Short with audio.parts
Timeline audio.parts joins up to 20 gapless narration slices inside one render, no re-synthesis. Build a 180-second Short from four takes, with the math.
- Sume 401 halfway through a batch: stop every worker, do not retry
A 401 on a Sume submit means a missing, malformed or revoked key. The SDK does not retry it, and neither should you. Python sample that keeps job ids.
Written by Sume