Dedupe Sume webhooks with Postgres ON CONFLICT DO NOTHING
Insert job_id or request_id with ON CONFLICT DO NOTHING RETURNING. A returned row means first delivery; no row means a duplicate. Includes the SQL.

Insert the delivery id into a table with a unique key using INSERT ... ON CONFLICT DO NOTHING RETURNING. If a row comes back, this is the first delivery and you enqueue the work. If no row comes back, it is a duplicate and you return 200. Sume tells receivers to use job_id as the idempotency key for job webhooks, and request_id for Format run webhooks.
Why this form
The PostgreSQL 18 INSERT page says ON CONFLICT DO NOTHING simply avoids inserting a row, and that only rows actually inserted or updated are returned. So RETURNING doubles as the first-time check, in one statement, with no select-then-insert race between two concurrent deliveries.
The id you key on matters. Sume's run webhooks page says the envelope request_id equals the run id and is stable across retries, while the receipt nested at payload.request_id is a different correlation id. Key on the envelope.
| Webhook family | Unique key | Stable across retries and redeliver |
|---|---|---|
| Job (job.completed, job.failed, job.canceled) | job_id | Yes, redeliver keeps the same job |
| Format run (format.run.terminal) | envelope request_id, equal to run_id | Yes |
The SQL
Create the table once, then run the insert in the same transaction that enqueues your work, so a crash cannot leave an id recorded with no job queued.
create table if not exists webhook_seen (
delivery_key text primary key,
event text not null,
body jsonb not null,
received_at timestamptz not null default now()
);
-- $1 = job_id or request_id, $2 = event name, $3 = raw JSON body
insert into webhook_seen (delivery_key, event, body)
values ($1, $2, $3::jsonb)
on conflict (delivery_key) do nothing
returning delivery_key;
-- one row -> first delivery: enqueue the work
-- no rows -> duplicate: return 200 and stopTwo cases to keep in mind
This does not replace polling. A delivery that never arrives leaves no row, so keep a periodic check of status_url for jobs you expected to finish.
- A redelivery after a terminal failure reuses the same key, so it will be skipped. If you want to reprocess on purpose, delete the row or add a version column.
- Verify the signature before the insert. An unsigned request should never write a row.
- Return 200 for the duplicate. Sume retries on any non-2xx, so an error for a duplicate only creates more traffic.
Alternatives and limits
If you do not use Postgres, the same pattern exists elsewhere: a unique key plus a conditional insert. The point is that the database enforces uniqueness, not your application code, so two deliveries that overlap in time cannot both win. A cache lookup before the insert is a fine optimisation but not a substitute.
Keep the stored body only as long as you need it. The row is useful for debugging a delivery and for replaying your own worker, but the job or run on Sume remains the authoritative record, and you can always read it again from result_url. Add an index on received_at if you plan to prune old rows on a schedule.
Sources
Related posts
More in Developers
- Python preflight for a YouTube Short: length and shape check
A short Python script that reads Sume's video inspect probe and flags a clip over 180 seconds or not square or vertical, including 90 degree rotation.
- Preflight a Sume image request against capability descriptors
Catch unsupported_parameter before you send: fetch the model descriptors, then check each field of your request against enum, range and boolean types.
- Pub/Sub push subscription for Sume events: ack codes and dedupe
Pub/Sub push redelivers on any code outside 102, 200, 201, 202 and 204. Fan Sume completions through Pub/Sub safely with run_id and job_id dedupe.
- Python 3.15 TaskGroup.cancel: stop at the first Sume job done
Python 3.15 adds TaskGroup.cancel. Watch several Sume jobs and stop the other watchers when the first one completes, without cancelling the paid jobs.
Written by Sume