Format run webhook retries: dedupe on request_id, order by created_at
Sume Format run webhook retries repeat request_id, which equals run_id. Dedupe on it and order deliveries by created_at, which changes per built body.

In a Sume Format run webhook, request_id and run_id are equal and stable across retries, so use it as the dedupe key. It cannot order events, because it repeats; use created_at, which records when that delivery body was built.
Both rules are in the field table on Runs and results. The Formats cookbook receiver shows the insert-or-ignore pattern in Node and Python.
Why dedupe at all?
A delivery is retried up to ten times on any non-2xx, timeout or redirect, so your endpoint can legitimately see the same run more than once, for example when your 200 arrives after the 10-second budget. Without a dedupe step, a retry marks an item ready twice or bills a customer twice.
The cookbook's order of operations: verify the signature against the raw bytes, answer 2xx fast, dedupe on request_id, and only then act on outcome.
Which field does what?
The field roles from the docs:
| Field | Role |
|---|---|
event | Always format.run.terminal; route on it |
request_id, run_id | Equal; stable across retries; the dedupe key |
created_at | When this delivery body was built; order deliveries by it |
outcome | ok, degraded or error; branch here for usable output |
payload | The receipt, or null over 1 MiB, then fetch error.result_url |
What does a minimal dedupe look like?
A stdlib sketch of insert-or-ignore keyed on request_id, using SQLite so it runs as written. Replace it with your own database; the primary key does the work.
import json, sqlite3
db = sqlite3.connect(":memory:")
db.execute("create table seen (id text primary key, body text)")
def first_time(event: dict, raw: str) -> bool:
cur = db.execute("insert or ignore into seen values (?, ?)", (event["request_id"], raw))
db.commit()
return cur.rowcount == 1
raw = json.dumps({"event": "format.run.terminal", "request_id": "arun_demo"})
event = json.loads(raw)
print(first_time(event, raw))
print(first_time(event, raw))How should I handle degraded and oversized bodies?
degraded means the run completed and was billed, real media is in artifacts[], but output is null because the projection did not match your schema. Show the media and log output_error; do not treat it as a failure.
When payload is null, the receipt was over 1 MiB and error.code is payload_too_large; fetch error.result_url with your API key. A handler that assumes payload is an object will throw on your largest runs.
Do continuations share an id?
No. A continuation with previous_run_id is a new run with a new id, its own receipt, spend cap and single webhook, so it never collides with the original's request_id. Both runs share a read-only thread_id, which is not a dedupe key.
Canceled and skipped runs deliver nothing, so do not wait for an event that is not coming. Cancel answers you directly with the receipt, and a skipped run is already terminal on the create response.
Sources
Related posts
More in Formats
- Bulk Format runs: 100 items, 16 at once, what completed means
Sume bulk runs take 1 to 100 items at concurrency 1 to 16. A queue marked completed means every item is terminal, not that every item succeeded.
- Format output_schema for partial results: nullable and primary key
How to design a Sume Format output_schema so a run that makes the video but misses a caption still passes: nullable fields, no minItems, and primary_output_key.
- Format run expires_at: 90 minutes from created_at, or sooner
How long a Sume Format run can live, which statuses are terminal, and how to poll the status_url without waiting on an event stream that does not exist.
- Format run spend cap: generation_spend_cap_usd max 500, zero rejected
How generation_spend_cap_usd works on a Sume Format run: the 500 ceiling, why 0 is rejected, null meaning 500, and what the receipt does and does not include.
Written by Sume