Idempotency-Key over 255 characters: hash long business keys

Sume accepts Idempotency-Key values up to 255 characters. Keep readable keys when short and fall back to a prefixed SHA-256 for long ones. Runnable Python.

5 min readSume
All posts

Keep the key readable when it fits in 255 characters, and replace it with a prefixed SHA-256 digest when it does not. Sume's Format run docs set the Idempotency-Key limit at 255 characters, scoped per Format. A business key built from a SKU list, a campaign slug and a market can pass that length quickly. The hash keeps the one property that matters, which is that the same logical request always yields the same key and a different one yields a different key.

Why not always hash

A readable key like v1:order:77:3 is useful in support work. You can see which order a replayed run belongs to, and you can search your own logs for it. A hash is opaque, so you need a table that maps it back. The hybrid rule gives you both: short keys stay human, long keys stay valid.

Do not truncate. Cutting a long key to 255 characters maps two different requests that share a prefix onto the same key, and the second would then fail as 409 idempotency_conflict (different body) or, worse, replay the wrong run if the bodies happen to match. A hash of the full string does not have that problem.

The function

The snippet joins the parts with a prefix, checks length and printable ASCII, and falls back to v1:sha256:<hex>, which is 74 characters. The prefix is your own version of the key scheme: if you ever change how keys are built, bump it so old and new keys never collide. The assertions at the bottom pin the two behaviours that matter, determinism and sensitivity. It runs on any Python 3.

import hashlib

MAX = 255  # Sume's documented Idempotency-Key length limit

def idem_key(*parts, prefix="v1"):
    plain = ":".join([prefix, *map(str, parts)])
    if len(plain) <= MAX and plain.isascii() and plain.isprintable():
        return plain
    digest = hashlib.sha256(plain.encode()).hexdigest()
    return f"{prefix}:sha256:{digest}"

print(idem_key("order", 77, 3))
long_key = idem_key("catalog", "x" * 400, 1)
print(len(long_key), long_key[:30])
assert idem_key("a", 1) == idem_key("a", 1)
assert idem_key("a", 1) != idem_key("a", 2)

Where this matters most

Bulk runs are the common case. A replay of a bulk create returns 202 with the old queue, so a stable key for the whole create is what makes a restarted importer safe. Build it from the stable parts of the batch (a catalog id, a sorted SKU list hash and a version), not from a position in a list that may be reordered.

Remember the other side of the contract. Same key with a different body is a 409 idempotency_conflict, so your key must change when the paid content changes, and a failed run is retried with a new key or continued with previous_run_id, not by re-sending the old one.

read 2026-10-03
InputLengthKey strategy
order:77:3Fits, readableUse as-is
campaign + 40 SKUs + marketOver 255 charactersHash with prefix
Truncated long keyCollides on shared prefixNever do this
uuid4 per attemptDifferent every retryDefeats idempotency

Test it once

Add the two assertions to your unit tests, plus one that feeds a 400-character part and checks the result is 255 characters or fewer. It is the kind of limit that works in every test with short fixtures and fails in production on the one customer with a long product name.

A note on character sets

The docs state the length limit but a hash also solves a second problem: keys built from user-supplied names can contain spaces, slashes or non-ASCII characters that some proxies and HTTP libraries mangle in headers. The function above falls back to the digest in that case, and the digest is plain lowercase hexadecimal. Keep the prefix constant across your fleet and write it down next to the function, since changing it later makes every in-flight retry look like a new request.

Finally, write down who owns the scheme. A key function is a contract between your producers, retries and recovery tools: when a support engineer needs to find the run for an order at two in the morning, they should be able to rebuild the key from the order id and version without reading code. Put one example next to the function in your repository, with the order id, the version and the exact key it yields, and keep that example as a test so it can never drift from reality.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume