6 min read
Idempotency is most of what a payments API is
The happy path in a payment system takes a fraction of the time. Everything else is retries, duplicates and reconciliation.
Building ArcPay taught me that the interesting part of a payments API is not accepting a payment. It is deciding what to do the second time you see the same one.
Three identical requests, three different meanings
A network retry, a double-clicked submit button, and a customer genuinely buying the same thing twice arrive at your API looking nearly identical. Guess wrong in one direction and you charge someone twice. Guess wrong in the other and you silently drop revenue.
The only way out is for the client to tell you which is which — an idempotency key, generated per logical operation, stored with the result.
What that forces
Once you commit to idempotency, a lot follows:
- Results must be durable. Returning the cached response to a retry means storing the response, not just the outcome.
- Keys need scope and expiry. Forever is a storage problem; too short reintroduces the duplicate.
- Concurrent retries need locking. Two copies of the same request arriving simultaneously is the normal case under a flaky connection, not the edge case.
On-chain adds its own version
Chain confirmations are asynchronous and occasionally reorganise. A payment that verified can un-verify. Settlement tracking therefore has to be a state machine with explicit transitions rather than a boolean, or you will eventually issue a receipt for a payment that no longer exists.
None of this is glamorous. It is also why payment infrastructure is a real category and not a weekend project.