Push or poll for a finished render: listen, webhook or jobs_wait
MCP 2026-07-28 adds subscriptions/listen. For a render that takes minutes, compare a listen stream, a signed webhook and jobs_wait, with a Python verifier.

Use a signed webhook when your server owns the workflow, jobs_wait slices when an agent is waiting in a conversation, and treat the new MCP listen stream as a notification channel, not as the record of a job. MCP 2026-07-28 replaces the HTTP GET endpoint and resources/subscribe with subscriptions/listen: one long-lived POST response stream for opted-in listChanged and resource-subscription notifications, per the changelog read 2026-10-03.
Three ways to learn a render finished
They differ in who holds a connection and what survives a restart. The Sume figures come from its webhooks and jobs pages.
| Option | Who holds the connection | Limits to plan around |
|---|---|---|
| MCP listen stream | Client, one long-lived stream | Notifications only reach a connected client |
| Sume webhook | Sume calls your HTTPS endpoint | Terminal events only; up to 10 attempts, 30 s apart, 10 s timeout |
| Sume jobs_wait (MCP) | Client, one call at a time | At most 55 s per call; 1 to 20 job ids |
Why the listen stream is not the record
A subscription tells a connected client that something changed. It cannot tell a client that was offline. For a paid render that may finish in minutes, the durable record is the job itself: Sume's docs say delivery is an optimization and never the only recovery path, and they recommend keeping status polling in place for events that never arrive.
Sume sends terminal events only (job.completed, job.failed, job.canceled), with no progress deliveries. If you want progress, poll the status.
Verify the webhook
Sume signs <timestamp>.<raw_body> with HMAC SHA 256 and sends x-sume-webhook-timestamp and x-sume-webhook-signature: sume-v1=<hex>. During a secret rotation the header carries one entry per live secret, so accept any match. Reject timestamps outside your tolerance window; the docs suggest five minutes. The verifier below refuses an empty secret and compares in constant time.
import hashlib, hmac, time
def verify(raw_body: bytes, timestamp: str, header: str, secret: str, tolerance: int = 300) -> bool:
if not secret:
return False
try:
ts = int(timestamp)
except ValueError:
return False
if abs(int(time.time()) - ts) > tolerance:
return False
signed = f"{ts}.".encode() + raw_body
expected = "sume-v1=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
ok = False
for entry in header.split(","):
if hmac.compare_digest(entry.strip().encode(), expected.encode()):
ok = True
return okA practical split
Use the webhook for the system of record: store the event, return a 2xx, and key your handling on job_id. Use jobs_wait for the agent, and when it returns wait_slice_expired, call it again with the same ids rather than resubmitting. If your client supports the listen stream, use it only to refresh a UI; do not let a missed notification decide whether a render is lost.
Sources
Related posts
More in Developers
- Python 3.10 is end of life: a stdlib Sume webhook verifier
Python 3.10 has reached end of life. A standard-library verifier for Sume's signed webhooks that refuses an empty secret and accepts rotated signatures.
- Recraft V4.1 Flash: median 1.3 s, p95 1.8 s. Set timeouts from p95
Recraft quotes a median of about 1.3 seconds and a p95 of 1.8 seconds for V4.1 Flash. How to turn latency claims into timeouts and polling for image APIs.
- Reproduce the same AI voiceover later: model id, voice, settings
To redo a narration line months later you need the model id, voice, language, format and settings. Sume's completed TTS job records them. A short routine.
- Shopify rejects file names ending in thumb, icon or large
Shopify file uploads reject names ending in pico, icon, thumb, testing, small, compact, medium, large or grande. A Python rename step for batch outputs.
Written by Sume