Sume webhooks: return 2xx only after you store the event
Store the event durably, then return any 2xx. Sume retries non-2xx and network errors. Use job_id as your idempotency key to absorb duplicates.

After you store the event durably, return any 2xx. Sume retries network errors and non-2xx responses until all attempts are used, and you should use job_id as your idempotency key (read 2026-10-06 in the webhook docs).
What does that look like?
Write the event to your own storage keyed by job_id, then respond. A second delivery of the same job then becomes a no-op.
seen: set[str] = set()
def handle(event: dict) -> int:
job_id = event["job_id"]
if job_id in seen:
return 200
seen.add(job_id)
return 200
print(handle({"job_id": "job_1"}), handle({"job_id": "job_1"}))What should I do in practice?
A slow endpoint spends the retry budget.
- Keep the receiver under the 10 s timeout.
- Do heavy work after you ack.
- Use a database, not a set, in production.
Sources
Related posts
More in Developers
- Sume webhook rotation: upgrade the verifier before you click Rotate
During a rotation window Sume sends two signatures in one header. A receiver that compares the whole header for equality fails every delivery. Fix it first.
- Sume webhook signature will not verify: compare the fingerprint
Each Sume delivery has x-sume-webhook-secret-fingerprint. Compare it with the dashboard fingerprint to find a wrong secret without ever sending the secret.
- Sume webhook secret fingerprint header: check your secret safely
Every Sume run webhook carries x-sume-webhook-secret-fingerprint. Compare it to the receipt and dashboard fingerprint to catch a wrong secret before verifying.
- Sume webhook secret rotation: why the header has two sume-v1 entries
During a signing-secret rotation Sume sends one sume-v1 entry per live secret, newest first, comma separated. Accept the delivery if any entry matches.
Written by Sume