Page on-call when a Sume webhook is exhausted: PagerDuty dedup_key
Send a PagerDuty Events API v2 trigger when Sume gives up on a webhook, using the job id as dedup_key so retries and redelivers never page twice.

Yes: you can page on-call when Sume stops retrying a webhook by sending a PagerDuty Events API v2 trigger event with the Sume job id as the dedup_key. PagerDuty's event endpoint is https://events.pagerduty.com/v2/enqueue, it answers 202 on success, and it returns the dedup key it used (read 2026-10-10).
Using the job id as the key gives you one alert per job no matter how many times you send it. That matters because Sume treats job_id as the idempotency key on its side too.
Why the job id is the right dedup key
Sume's webhooks page says a delivery gets up to 10 attempts, each timing out after 10 seconds, and that receivers should treat job_id as the idempotency key. After the last attempt, the delivery status is exhausted, one of the six states listed on the errors and credits page.
PagerDuty's page describes dedup_key as optional for trigger events and required for acknowledge and resolve (read 2026-10-10). If you set it yourself, a later resolve with the same value can close the alert you opened, so reuse the same job id when you redeliver and the delivery finally succeeds.
| PagerDuty field | Value to send | Why |
|---|---|---|
routing_key | Your Events API V2 integration key | Required by PagerDuty; keep it in an environment variable |
event_action | trigger when exhausted, resolve after a successful redeliver | Allowed values are trigger, acknowledge and resolve |
dedup_key | The Sume job_id | One open alert per job |
payload.summary | Short text naming the job and the cause | What the responder reads first |
payload.source | Your receiver or service name | Identifies the sender |
A sender that runs as written
This standard-library Python script triggers an alert for the job id passed on the command line. Set PAGERDUTY_ROUTING_KEY first. It sends the fields above, and PagerDuty's reply echoes the dedup_key it used.
import json, os, sys, urllib.request
def trigger(job_id, summary):
body = {
"routing_key": os.environ["PAGERDUTY_ROUTING_KEY"],
"event_action": "trigger",
"dedup_key": job_id,
"payload": {"summary": summary, "source": "sume-webhook-receiver", "severity": "critical"},
}
req = urllib.request.Request(
"https://events.pagerduty.com/v2/enqueue",
data=json.dumps(body).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=10) as res:
return json.load(res)
if __name__ == "__main__":
print(trigger(sys.argv[1], "Sume webhook delivery exhausted for " + sys.argv[1]))Where the trigger comes from
Sume does not push an event when it gives up, so something on your side has to notice. Two sources work. Poll the job object, which the docs say shows the webhook delivery status and attempt count when available, and trigger when it reads exhausted or failed. Or run a scheduled check for jobs that finished in Sume but never reached your receiver, which is the recovery path the webhooks page describes for missing events.
The receipt carries last_status_code and last_error, our transport error rather than your body, so copy them into payload.summary and the responder sees whether the cause was a refusal, a 10-second timeout or a redirect.
Close the loop with redeliver
Once the receiver is fixed, call POST /v1/jobs/{job_id}/webhook/redeliver with a jobs:write key. Sume re-sends the real terminal event with a fresh timestamp and signature, and this works after the ten automatic attempts are used. When your receiver stores the event, send a resolve event with the same dedup_key.
Keep the pager for the exhausted case only. Individual retrying states heal on their own within the retry schedule, and paging on them would wake someone for deliveries Sume is already handling.
Sources
Related posts
More in Developers
- Pick the cheapest Sume image row for a ratio and 3 refs (Python)
A 24-line Python script reads GET /v1/images/models and the endpoints route, filters by aspect ratio and reference count, and sorts by billed price.
- Pick the highest resolution a Sume video model lists (Python)
Sume's catalog row is now the one resolution list for both video endpoints. A short Python helper reads supported_resolutions and steps down instead of failing.
- Port a fal queue submit and poll loop to Sume /v1/videos in Python
A fal queue client posts to queue.fal.run, polls a status URL and fetches a result. The Sume version is 21 lines of Python on /v1/videos. Field map, traps.
- Port an ElevenLabs text-to-speech call to Sume TTS 1.0, field by field
ElevenLabs puts the voice in the URL; Sume TTS 1.0 takes it in the body and rejects model_id. A field map and a tested mapper function.
Written by Sume