A client sends a payment request and the connection times out. Did the server reject it, finish it, or commit the database write before the response was lost? The client cannot know, so it retries. Without idempotency, a normal network failure becomes a duplicate side effect.
Idempotency describes an observable result
An operation is idempotent when repeating the same intention produces the same externally visible outcome. GET and DELETE are naturally close to that model. Creating a payment, order, or background job usually needs an explicit idempotency key generated by the client.
POST /payments
Idempotency-Key: 4e93a2d8-...
Content-Type: application/json
The server stores the key with a fingerprint of the request and the completed response. A retry with the same key and payload returns the stored result. The same key with a different payload is rejected, because silently treating two different intentions as one hides a client bug.
The key and side effect must commit together
Checking for a key and then performing a write in separate unprotected steps creates a race: two requests can both observe that the key is absent. Use a unique database constraint and a transaction when the datastore supports it. One request wins ownership; the other waits for or reads the result.
In Node.js, do not rely on an in-memory map. It disappears during deployment, is not shared across instances, and cannot coordinate concurrent workers. Persist the record in the same reliability domain as the operation.
Model in-progress and failed attempts
The idempotency record usually needs processing, completed, and recoverable failure states. A crashed worker may leave a lease that another worker can reclaim after a timeout. Permanent validation failures can be stored and replayed, while transient infrastructure failures may allow a controlled retry.
Idempotency does not replace domain constraints
A user can intentionally send two requests with different keys. Business rules still need uniqueness constraints, inventory checks, or a ledger. The idempotency layer protects transport retries; the domain model protects meaning.
The most reliable APIs make retries boring. Give every side-effecting operation a stable identity, enforce it atomically, and return the original result. That turns ambiguous network outcomes into a normal code path instead of an incident.