Thin vs full webhook payloads: how Sume's job events work
Stripe made thin events generally available for API v1. Sume's job webhooks carry the result in the payload, are keyed by job_id, and can be redelivered.

Stripe's changelog for 2026-09-30 says thin events for API v1 resources are generally available. Sume's job webhooks are not thin: a terminal event carries the job's result in payload, keyed by job_id. Your handler can act on the delivery, and a lost delivery can be redelivered or recovered by polling.
What Stripe announced
The changelog line is the only Stripe fact used here. In general, a thin event names the resource that changed and leaves you to fetch the current object. Check Stripe's documentation for the details of its model.
What a Sume event holds
Sume sends terminal job events only: job.completed, job.failed and job.canceled. There are no progress or partial deliveries. A completed event carries the result, such as the artifact list with id, url, type and content_type. Failed and canceled events use status: "ERROR" with an error object.
Action, Format and Agent Completion runs have their own events: action.run.terminal, format.run.terminal and agent.run.terminal. They share the signature scheme, so one verifier covers both.
| Item | Value |
|---|---|
| Events | job.completed, job.failed, job.canceled |
| Idempotency key on your side | job_id |
| Attempts | Up to 10 |
| Spacing | Fixed, 30 s by default |
| Timeout | 10 s per attempt |
| Replay | POST /v1/jobs/{job_id}/webhook/redeliver |
Verify before you trust it
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: sume-v1=<hex>. During a secret rotation the header holds one entry per live secret, so accept any match. Reject old timestamps; five minutes is a reasonable window. This version refuses an empty secret.
import hashlib, hmac, time
def verify(raw: bytes, ts: str, header: str, secret: str, tol: int = 300) -> bool:
if not secret:
return False
try:
t = int(ts)
except ValueError:
return False
if abs(time.time() - t) > tol:
return False
digest = hmac.new(secret.encode(), f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
want = "sume-v1=" + digest
ok = False
for entry in header.split(","):
if hmac.compare_digest(entry.strip(), want):
ok = True
return okRedeliver and poll
Return any 2xx after you durably store the event. Ten refused attempts leave a failed delivery, while the job still reaches its real terminal state. POST /v1/jobs/{job_id}/webhook/redeliver re-posts that job's real terminal event with a fresh timestamp and signature, and it does not use up one of the automatic ten. Keep status_url polling as a fallback for events that never arrive.
Sources
Related posts
More in Developers
- Three Sume throttle signals: rate_limited, queue_full, 503
OpenAI now splits 429 (traffic rising too fast) from 503 (overload). Sume has three: 429 rate_limited, 429 queue_full and 503 provider_capacity_exceeded.
- TikTok ad price must match the landing page: burn one price with Sume
TikTok's example: an ad saying 'up to 50% off' while the site says 30% is not allowed. Drive the video and the page from one price variable. Code included.
- TikTok Shop trend research via Sume trending search, $0.10 a call
Before a holiday product video, see what is trending for your product on TikTok. Sume's trending-videos search returns public video metadata at $0.10 a call.
- TikTok total_chunk_count rounds down: the final chunk can reach 128 MB
TikTok's Content Posting API sets total_chunk_count to video_size divided by chunk_size, rounded down, so the last chunk is bigger. Worked sizes and a checker.
Written by Sume