job_id, request_id, Idempotency-Key: which one goes in which column

Your Idempotency-Key is the unique key before submit, job_id or run_id is the poll key, and request_id dedupes webhooks and goes into support tickets.

5 min readSume
All posts

Keep three different ids in three different columns. The Idempotency-Key is yours. You derive it before the submit and it makes the paid call safe to repeat. The job_id, or run_id for a run, is Sume's. It arrives in the submit response and is the key you poll with. The request_id is the id on webhook payloads and on error envelopes, and it is the one you dedupe deliveries on and quote to support.

A common failure comes from using the wrong id as a unique key. If you use the Sume id as the unique key of your intent table, you cannot write the row until the response arrives, and a crash between the call and the response leaves a paid job with no record. Using your own key, written first, closes that gap.

Why they get mixed up

The three look similar because a job webhook shows request_id and job_id with the same job_... value, and teams then assume they are one thing. They are not interchangeable. A run webhook carries a run_id and a request_id, and an error response carries a request_id for the failed call, which does not point to any job at all, because a rejected submit creates none.

Support is the second place where the mix-up costs time. When you contact support about a failed call, the request_id from the error envelope is what they can search. A job_id of a different job, or the key you derived, will not find the failed request. Log the error request_id on every non-2xx answer.

One role per column

Give each id one job in your schema, and keep the roles apart.

Ids, their owner and their use (read 2026-10-05)
IdWho makes itUse it for
Idempotency-KeyYou, before the submitUnique key on your intent table, safe retry of the paid call
job_id or run_idSume, in the submit responsePolling, result fetch, cancel, redeliver
request_id on a webhookSume, in the payloadDedupe of deliveries
request_id on an errorSume, in the error envelopeQuote in a support ticket

A schema that keeps them apart

The table definition below follows that split. The intent row is written first, with the key, and the Sume id is filled in after the response. A unique index on the key means that two workers cannot start the same paid job.

Notice the partial unique index. It lets the sume_id column stay empty until the response arrives, and it still prevents two intents from claiming the same Sume job later. Add a column for the delivery request_id in a second table if you keep a record of webhooks.

import sqlite3

db = sqlite3.connect(":memory:")
db.execute('''create table intents (
  idempotency_key text primary key,
  sume_id text,
  state text not null default 'pending')''')
db.execute("create unique index seen_events on intents(sume_id) where sume_id is not null")

def reserve(key):
    cur = db.execute("insert into intents (idempotency_key) values (?) on conflict do nothing", (key,))
    return cur.rowcount == 1

def attach(key, sume_id):
    db.execute("update intents set sume_id = ?, state = 'submitted' where idempotency_key = ?", (sume_id, key))

print(reserve("acme-order-77-promo-v3"), reserve("acme-order-77-promo-v3"))
attach("acme-order-77-promo-v3", "job_demo")
print(db.execute("select * from intents").fetchall())

What the key does on the server side

With a Format run, the same key and the same body returns 200 with the original receipt and idempotency_hit: true, while a fresh run returns 202. The same key with a different body returns 409 idempotency_conflict. Without a key, every call is a new paid run. These rules make the key column the safest place to enforce one run per order.

The idempotency window is the reason to keep the key for as long as the order lives. A retry that comes hours later should still find the same key, and with a stable derivation from ids it will, even if your cache was cleared.

What to dedupe a webhook on

For webhooks, dedupe on the id inside the payload and not on the delivery attempt. Delivery is at least once, and a manual redelivery sends the same event again with a fresh timestamp and signature. A receiver that records request_id before it does any work will process each terminal event once.

Cancelled or skipped runs send no webhook, so they never create a delivery request_id row. Your reconciliation poll reads them by run_id, and that is a further reason to keep the Sume id in its own column.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume