Idempotency-Key for a SaaS: customer, order and version

Derive a Format run's Idempotency-Key from customer id, order id, Format slug and a version you bump on purpose, so double clicks never make a second paid run.

5 min readSume
All posts

Hash your tenant id, your order id, the Format slug and a version string you control, and send the result as Idempotency-Key. The key then identifies the thing being made rather than the moment you asked, so a double click or a redelivered job returns the original run instead of starting a second paid one.

A random UUID per request defeats this. It makes the header decorative, because every retry looks like a new run.

A key derived from what is being made

Namespace by customer. A key built only from the order id lets two tenants with colliding ids share a run. Include a version you bump when you deliberately want a re-run of the same order, for example after the customer edits the brief. Keep it short: the sample below truncates a SHA-256 to 40 hex characters.

import hashlib


def run_key(customer_id: str, order_id: str, version: int = 1) -> str:
    raw = f"{customer_id}:{order_id}:product-promo:v{version}"
    return hashlib.sha256(raw.encode()).hexdigest()[:40]


print(run_key("cust_42", "order_9001"))
print(run_key("cust_42", "order_9001", version=2))

What a replay returns

The behavior on the wire is exact, and your handler should expect all three outcomes. A conflict means your key derivation is unstable, so fix that before retrying rather than adding a retry loop.

Replay semantics for a Format run (read 2026-10-03)
ReplayResult
Same key, same body200 with the original receipt and idempotency_hit: true, no second run and no second charge
Same key, different body, including a different instruction409 idempotency_conflict, nothing runs
No keyEvery call starts a new paid run

Store the run id before you answer the browser

Write the returned run_id against your record before you respond. You can re-derive the key later to find the run, but a stored id is one lookup instead of one replay. If a run fails, retry with a new key: the old one is bound to the receipt you already have, and reusing it returns that same failed receipt.

For a batch, mint a fresh key per batch. A bulk replay of a spent key returns 202 with the old queue, and keys are scoped to one Format.

Use the spend cap to express plan tiers

The same request is the place to set generation_spend_cap_usd. The cookbook sketches a mapping where a free plan gets a small cap, a pro plan a larger one, and enterprise inherits the Format's own cap. Those numbers are an example from the docs, not a recommendation; pick yours from what a run of your Format really spends.

Remember that result URLs are durable and public to anyone holding them. If one customer must never see another's output, copy the media on the webhook or proxy it through your own authenticated route before you mark the order ready.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume