Idempotency Key (sender side)
The sender-side counterpart of the Idempotent Receiver. Without a stable idempotency key on the sender side, receiver-side deduplication is pointless.
Problem
A naive retry that regenerates a fresh UNB CTRL, a fresh
ISA13 or a fresh eb:MessageId on every attempt
fully breaks the Idempotent Receiver pattern: the partner
receives two different keys, concludes that two separate orders were
issued and books both. The idempotency pattern is not enough on the
receiver side — it must be primed on the sender side.
Forces
- Persisting the key is non-negotiable. An in-memory counter is lost at restart: the key must live in a database, an outbox or the send journal.
- The key must be generated before the first send, not at send time. Otherwise a crash after the send but before persistence leaves the system unable to retry with the same key.
- One key per business intent, not per network attempt. One business order = one key, regardless of how many retries.
- The key must be contractual. Sender and receiver must agree on its structure, length and uniqueness window (often 90 days in EDI).
Solution
When the business application decides to issue a message, it generates
a deterministic idempotency key (typically <type>-<date>-<counter> or a persisted
UUID v4), stores it alongside the message in an outbox, then lets the
sender worker use it as UNB CTRL / ISA13 /
eb:MessageId on every attempt. The naive code becomes a
two-step workflow: register the intent (with its key), then execute
the send (which can be retried any number of times with the
same key).
T0 Business app → outbox.insert(domainEvent, idempotencyKey=ORD-20260514-0001)
T1 Sender worker → SELECT ... FROM outbox WHERE status='pending'
T2 Sender worker → POST AS2 with UNB CTRL = ORD-20260514-0001
T3 Network ↛ network error, no MDN received
T4 Sender worker → retry (same bytes) reusing the same key
T5 Partner → recognises duplicate (Idempotent Receiver) → re-emits the same MDN
T6 Sender worker → outbox.markDelivered(idempotencyKey) EDI implementation
In EDI, the sender worker reads the outbox, takes the tuple (idempotencyKey, payload), writes the key into the
matching envelope segment (UNB+...+ORD-20260514-0001' in
EDIFACT, ISA13=900042001 in X12, eb:MessageId
in AS4) and sends. A retransmission replays the same outbox row with
the same key. The outbox table carries a status (pending,
sent, acked) and a tries column to observe
network redundancies. Walmart, Stellantis and OpenPEPPOL all require a
stable sender-side idempotency key in their partner manuals.
Anti-patterns
- New key per retry. Defeats receiver-side deduplication — exactly what the pattern is meant to prevent.
- In-memory counter. Crash = reset counter = collisions or duplications.
- Key generated at send time, not before. If the worker crashes between send and persistence, retry traceability is lost.
- Key decoupled from business intent. If the same order can produce two keys, the idempotency benefit vanishes.
Related patterns
- Idempotency (receiver) — the matching pattern to prime.
- Outbox — the natural carrier for the persisted key.
- Retry & backoff — the policy that replays with the same key.
Sources
- Hohpe G., Woolf B. — Enterprise Integration Patterns, pattern Idempotent Receiver. enterpriseintegrationpatterns.com — Idempotent Receiver
- Stripe API — Idempotency Keys. The most widely deployed contemporary reference of the pattern, beyond EDI. stripe.com/docs/api/idempotent_requests
- IETF draft — HTTP Idempotency-Key Header. The IETF standardisation attempt for a transverse HTTP header. datatracker.ietf.org — Idempotency-Key
- Walmart Supplier Center — EDI Guidelines. Convention
on the structure of sender-side
UNB CTRLandISA13.