Sume webhooks plus a sweeper: recover jobs whose callback never came
Webhook delivery can fail after 10 attempts while the Sume job still finishes. Run a sweeper that polls jobs stuck non-terminal in your own table.

A Sume job webhook can fail to reach you and the job will still finish. Delivery is retried up to 10 attempts total with a 10 second timeout each, and ten refused attempts leave a failed delivery but a job in its real terminal state. The docs say to keep status_url polling available for exactly this, and a sweeper is that polling, done once in a while rather than in a tight loop.
The pattern
Store every job id with a status column on submit. The webhook handler marks a row terminal. A scheduled sweeper selects rows still non-terminal after your usual job time, reads their status once, and marks them. Redeliver is a second option: POST /v1/jobs/{job_id}/webhook/redeliver re-sends the real terminal event with a fresh signature and does not use one of the automatic attempts.
import asyncio, os
import httpx
async def sweep(open_job_ids: list[str]) -> dict[str, str]:
headers = {"x-api-key": os.environ["SUME_API_KEY"]}
found: dict[str, str] = {}
async with httpx.AsyncClient(base_url="https://api.sume.com", headers=headers) as c:
for job_id in open_job_ids:
r = await c.get(f"/v1/jobs/{job_id}/status")
r.raise_for_status()
body = r.json()
if body.get("terminal"):
found[job_id] = body.get("sume_status", "unknown")
return found
async def main() -> None:
print(await sweep(["job_123"]))
asyncio.run(main())Make handling idempotent
Both paths can report the same job, so key your handler on job_id and ignore a second report. Use the same handler function for the webhook and the sweeper.
| Path | Latency | Covers |
|---|---|---|
| Webhook | Seconds after the terminal event | Normal case |
| Sweeper | Your schedule | Deliveries that exhausted retries |
| Redeliver call | On demand | One known job, after you fix the endpoint |
What not to do
Do not resubmit the paid request because a callback is missing; that creates a second paid job. Read the status instead.
Sources
Related posts
More in Developers
- Sume webhook timestamp window: reject stale deliveries (Python)
The Sume signature covers a timestamp, and the SDK default rejects anything over 300 seconds old. Verify at the edge, then queue, so a late worker never fails.
- Sume run webhook behind a redirect: a 3xx counts as a failed attempt
Sume does not follow redirects on run webhooks, so a 301 from an http or www hostname is a failed delivery. The URL is also re-checked at delivery time.
- Supabase Realtime for Sume render progress, with RLS per subscriber
Add the job table to the supabase_realtime publication and subscribe with a row filter. Realtime checks RLS for every subscriber, so keep the table lean.
- SvelteKit +server.js endpoint to verify a Sume webhook signature
A SvelteKit +server.js POST handler gets a Fetch Request, so request.text() gives the raw body Sume signs. Verify the HMAC, then handle job.completed.
Written by Sume