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
- Pydantic AI slot leak vs Sume queue_full: tell them apart
Pydantic AI v2.53.0 fixed a streamed-request concurrency slot leak. A client limiter is not Sume's workspace queue_full 429, and each needs its own handling.
- Pydantic AI Workspace sandboxes: fetch Sume artifacts
Pydantic AI's Workspace abstraction runs tools locally or in sandboxes. Inside a sandbox, fetch Sume artifact URLs, and give Sume inputs as public HTTPS URLs.
- pytest and respx: fake a Sume job that is queued, then completed
Test your Sume poll loop with respx: a queued, a processing and a completed response in order, with sleep injected so the test finishes in milliseconds.
- 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.
Written by Sume