Sweep Format runs for failed webhook deliveries, then redeliver
Sweep in Python: read each run's webhook_delivery, redeliver only failed or exhausted ones, and leave the rest alone. Uses formats:read, formats:write.

If your webhook receiver was down, Sume tried each terminal delivery ten times and then stopped, and the run stayed completed or failed. To catch up, read each run, look at webhook_delivery.status, and call POST /v1/format-runs/{run_id}/webhook/redeliver only for runs whose status is failed or exhausted. Redeliver re-sends the current receipt with a new timestamp and signature, and it does not use up one of the ten automatic attempts.
The script takes run ids from a file, one per line. You have them from your own records or from a bulk queue's items[].run_id.
The sweep
Set SUME_API_KEY, which needs formats:read for the reads and formats:write for redeliver. The reads run in a thread pool through asyncio.to_thread, so the reads overlap instead of running one after another.
import asyncio, json, os, urllib.error, urllib.request
BASE = os.environ.get("SUME_BASE", "https://api.sume.com/v1")
HEAD = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"], "Content-Type": "application/json"}
def call(method, path):
req = urllib.request.Request(BASE + path, method=method, headers=HEAD,
data=b"{}" if method == "POST" else None)
try:
with urllib.request.urlopen(req, timeout=30) as r:
return r.status, json.load(r)
except urllib.error.HTTPError as e:
return e.code, json.load(e)
def sweep(run_id):
code, body = call("GET", f"/format-runs/{run_id}")
if code != 200:
return run_id, f"read {code}"
state = (body["data"].get("webhook_delivery") or {}).get("status")
if state not in ("failed", "exhausted"):
return run_id, f"left alone ({state})"
code, body = call("POST", f"/format-runs/{run_id}/webhook/redeliver")
return run_id, "redelivered" if code < 300 else body["error"]["code"]
async def main():
ids = [line.strip() for line in open("run_ids.txt") if line.strip()]
for out in await asyncio.gather(*(asyncio.to_thread(sweep, i) for i in ids)):
print(*out)
asyncio.run(main())Why it only touches two states
not_armed, pending and retrying mean Sume is still working, and a manual redeliver on top of them only adds a duplicate. delivered means your endpoint answered with a 2xx. Only failed and exhausted mean Sume gave up, and last_status_code and last_error on the same block tell you why. Fix the receiver before you sweep: a redeliver to a broken endpoint fails the same way.
A run created without a webhook_url has no webhook_delivery block, so the script reports it as left alone. If you called redeliver on one anyway, the API would answer 409 webhook_not_configured. A run that is still in progress gives 409 run_not_terminal. Both come back as error.code and the script prints them.
| `webhook_delivery.status` | Meaning | Sweep action |
|---|---|---|
not_armed | URL stored, nothing scheduled, run in progress | Leave alone |
pending, retrying | Armed; next_attempt_at is set | Leave alone |
delivered | Your endpoint answered 2xx | Leave alone |
failed, exhausted | Sume stopped after a refusal, a 10 s timeout, a redirect, or a failed URL check | Redeliver |
Make the receiver safe to hit twice
A redelivered body is the current receipt, byte-identical to the data of GET /v1/format-runs/{run_id}, but with a fresh signature. Verify the sume-v1 signature against the raw body, and dedupe on the run id so a run you already stored is not stored again. A handler written this way is safe to sweep with whenever you like.
The sweep does not cancel anything and spends nothing: a webhook delivery never changes the run, and a redeliver is not a new run.
Where do the run ids come from? For a bulk queue, read items[].run_id from GET /v1/format-run-queues/{id}: queued items and children that never started have a null run id and nothing to redeliver. Skip those rows before writing run_ids.txt. A queue itself has no webhook, so a run webhook is something each item sets in its own body through communication.webhook_url.
Run the sweep after an outage or a deploy, not on a tight loop. The reads are cheap: they count against the read budget, which is forty times the write budget, while each redeliver is one write.
Sources
Related posts
More in Developers
- 10 hooks by 10 endings: a 100-variant grid in one Sume bulk queue
A 10 by 10 hook and ending grid is exactly 100 items, the bulk queue maximum. How to build the items array, pick concurrency up to 16, and read the result.
- A ten-photo edit regression suite to re-run when a model launches
Ten photos, five edits each, one scoring sheet: a cheap test to re-run whenever a new image edit model launches. 50 edits cost $1.875 at the low tier on Sume.
- Test a Sume webhook receiver with signed fixtures, no paid job needed
Generate sume-v1 signatures yourself and test six cases: good, rotated, reserialized, stale, empty-secret and unknown event. Python code that runs as is.
- Thai, Vietnamese, Indonesian speech to text: Sume STT language hints
Send th, vi or id as language_code to Sume STT, or omit it to auto-detect, then check language_probability. $0.01 per audio minute; test a sample first.
Written by Sume