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.
- Track AI video spend per job: Synthesia Billing API vs Sume job cost
Synthesia added a Billing API and auto top-up. On Sume, every finished job reports usage.cost, so you can keep a per-job ledger without a billing endpoint.
- Transcribe two minutes of a long video: audio detach range, then STT
Streaming transcribers charge by the hour; you may only need one segment. Detach a range as 16 kHz mono wav, then run one STT job. Caps and codes included.
- Trigger.dev Node 21 warning: which Node runs the Sume SDK
Trigger.dev v4.6.1 added Node.js 21 deprecation warnings. The Sume TypeScript SDK needs Node 18 or later, so tasks on Node 22 or newer are fine.
Written by Sume