Razorpay/Stripe Idempotent Payments

Razorpay/Stripe Idempotent Payments

How a payment gateway guarantees that a request sent twice charges a customer once, even when the network refuses to say what happened.

Idempotency KeyCore Primitive
State MachinePayment Model
At-least-onceWebhook Delivery
ReconciliationFinal Authority
← Back to Case Studies

Overview

A payment request times out. The customer's card may have been charged, or may not. The merchant's server has no way to tell from the timeout alone — and somebody has to decide what happens next.

This is the defining problem of payment engineering. Networks fail in the least helpful way possible: not by clearly refusing, but by going quiet after the request has already been sent. Retrying might fix nothing and charge twice.

The industry's answer is the idempotency key — a client-generated token that lets the server recognise a retry as the same logical operation and return the original outcome instead of performing a second charge.

Think of it like this: it is like posting a letter and losing your internet connection before the confirmation arrives. You do not know if it was delivered. Writing a reference number on the envelope means the post office can tell you whether they already have that exact letter, rather than sending a second one.

The Core Challenge

Money movement imposes constraints that ordinary application code does not face:

No Undo

A duplicate charge cannot simply be deleted. It requires a refund, a reconciliation entry and often a support conversation.

Ambiguous Failures

A timeout carries no information about whether the operation succeeded, only that the answer did not arrive.

Multiple Parties

A payment traverses merchant, gateway, processor, network and bank, each of which can fail independently.

Asynchronous Outcomes

Many payment methods complete minutes or hours later, so the final state is not known when the request returns.

Regulatory Trail

Every state transition must be auditable long after the fact, which rules out mutating records in place.

High-Level Architecture

The architecture is built around one idea: a payment is not an action, it is a record with a state that changes over time. Every request either creates that record or reports its current state.

01

Idempotency Layer

Sits in front of everything. Looks up the supplied key and either replays the stored response or allows exactly one execution to proceed.

In shortA doorman with a guest list — if your reference is already on it, you get the same answer as last time.

02

Payment Intent Record

A durable record created before any money moves, holding the amount, currency, method and current state. It is the anchor for everything that follows.

In shortA file is opened for the payment before anything is attempted, so there is always something to look up.

03

State Machine

The payment advances through explicitly defined states with only legal transitions permitted, so it cannot jump from created to refunded without passing through captured.

In shortThe payment walks a fixed path, and shortcuts are simply not allowed.

04

Processor Adapter

Talks to the card network or bank, translating between the gateway's model and each provider's protocol, and normalising their varied failure responses.

In shortThe translator that speaks to each bank in its own dialect.

05

Webhook Dispatcher

Notifies the merchant of state changes with retries and signatures, since many outcomes arrive long after the original request closed.

In shortIt calls the merchant back later to say what finally happened.

06

Reconciliation Engine

Compares internal records against the processor's settlement files daily, catching anything the live path missed or recorded incorrectly.

In shortAn end-of-day audit that checks the books against the bank's own statement.

The Life of a Payment

1

Client Generates a Key

Before sending, the merchant's system creates a unique idempotency key for this logical payment and stores it alongside the order.

In shortA reference number is written down before the request is even sent.

2

Gateway Checks the Key

On arrival, the gateway attempts to record the key. If it already exists, the stored response is returned and no charge occurs.

In shortThe gateway checks whether it has seen this reference before.

3

Create the Intent

For a new key, a payment record is persisted in a pending state before any external call is made, so the attempt survives a crash.

In shortThe paperwork is filed first, so nothing is lost if the system dies mid-request.

4

Authorise

The processor is asked to authorise the amount, reserving funds on the customer's card without yet moving them.

In shortThe bank sets the money aside but does not hand it over yet.

5

Capture and Persist

On capture, funds are claimed and the resulting state is written durably before the response is returned to the merchant.

In shortThe money is actually taken, and the outcome is recorded before anyone is told.

6

Notify and Reconcile

A webhook informs the merchant of the final state, and the payment is later matched against the processor's settlement file.

In shortThe merchant is told, and the next day the books are checked.

The Uncertain Response

Every serious payment bug lives in the gap between an operation happening and the caller learning that it happened.

The Two Generals Problem

Two parties communicating over an unreliable channel can never both be certain the other received the last message. It is provably unsolvable, which is why payments manage uncertainty rather than eliminating it.

In shortYou can never be completely sure the other side got your message, so the system is built to cope rather than to be certain.

Timeout After Success

The charge succeeded but the response was lost. A naive retry charges the customer twice for one purchase.

In shortIt worked, but nobody heard back, so it gets done again.

Timeout Before Execution

The request never reached the processor. Here a retry is not only safe but necessary, and the system cannot distinguish this case from the previous one without a key.

In shortIt never happened, so it should be retried — but from the outside it looks identical to the case where it did.

Partial Application

The charge succeeded at the processor but the gateway crashed before recording it, leaving internal state disagreeing with reality until reconciliation.

In shortThe bank thinks it happened and the gateway does not, until the books are checked.

How Idempotency Actually Works

A tempting shortcut is to check whether a similar payment exists before creating one. Under concurrency this is the same read-then-write race that causes double charges in the first place:

Naive Approach
  • Query for a matching recent payment, then create if absent
  • Two concurrent retries both find nothing and both charge
  • Key stored only after the operation completes
  • A crash mid-flight leaves no record that it was attempted
  • Retry safety depends on timing luck
VS
Production Approach
  • Insert the key under a uniqueness constraint before doing anything
  • The database itself rejects the second attempt — no race possible
  • Response persisted against the key and replayed on retry
  • In-flight state recorded, so a concurrent retry is told to wait rather than proceed
  • Key scoped to the request fingerprint, so reuse with different parameters is an error

In short: idempotency is not achieved by checking for duplicates. It is achieved by making the database refuse the second one. The uniqueness constraint on the key is the actual mechanism; everything else is bookkeeping around it.

Webhooks & Reconciliation

Because outcomes arrive late, the notification path needs the same rigour as the request path — and something must still verify both.

At-Least-Once Delivery

Webhooks are retried with backoff until acknowledged, which means merchants will sometimes receive the same event twice and must handle it idempotently themselves.

In shortThe callback may arrive more than once, so the merchant also has to cope with duplicates.

Out-of-Order Arrival

Retries mean a later event can arrive before an earlier one, so consumers must apply events by version or timestamp rather than by arrival order.

In shortMessages can land in the wrong order, so each carries its place in the sequence.

Signature Verification

Payloads are signed so the merchant can confirm the notification genuinely came from the gateway and was not forged by anyone who guessed the endpoint.

In shortEach callback is stamped so you know it is really from the payment provider.

Settlement Reconciliation

The processor's daily file is the ultimate authority. Any disagreement with internal records is flagged and investigated rather than assumed benign.

In shortThe bank's own statement is the final word, and any mismatch gets chased.

Scalability & Reliability

Payment systems optimise for correctness first, and accept architectural constraints that a higher-throughput system might not:

  • Write before acting — state is persisted before any external call, so a crash leaves a recoverable record rather than an invisible charge.
  • Append-only history — state transitions are appended rather than overwritten, preserving a full audit trail and making replay possible.
  • Key expiry with care — idempotency keys are retained long enough to cover any realistic retry window, typically at least twenty-four hours.
  • Isolation by merchant — one merchant's traffic spike or retry storm must not degrade settlement for everyone else.
  • Reconciliation as a safety net — the live path is assumed to be imperfect, and a slower, thorough daily process is what actually guarantees the books balance.

Typical Tech Stack

LayerCommon Choices
Idempotency StoreRelational table with a unique constraint, or Redis with durable backing
Payment RecordsStrongly consistent relational storage with append-only transitions
State MachineExplicit transition tables with database-enforced legal states
Processor IntegrationPer-provider adapters with normalised error taxonomies
Async ProcessingDurable queues for captures, refunds and webhook dispatch
WebhooksSigned payloads, exponential backoff, dead-letter handling
ReconciliationBatch matching against processor settlement files

Trade-offs & Lessons

  • Exactly-once does not exist on the wire — what exists is at-least-once delivery plus idempotent handling, which together produce exactly-once behaviour. The guarantee is built at the endpoint, not in the network.
  • The client must own the key — a server-generated key cannot help, because the retry would generate a new one. Only the caller knows that two requests represent the same intent.
  • Never mutate payment state in place — the audit trail is a product requirement, not an implementation detail, and overwriting destroys the ability to explain what happened.
  • Design for the ambiguous case first — success and clean failure are easy. The system's real quality is determined entirely by how it behaves when it does not know.

Idempotent payments are the canonical answer to a problem every distributed system eventually faces: how to retry safely when you cannot tell whether the first attempt worked. The pattern — a caller-supplied key, a uniqueness constraint, a stored response and a reconciliation pass — transfers directly to any operation that must not happen twice.

Jargon, Decoded

A quick, no-nonsense translation of the technical terms used above.

Idempotency

The property that performing an operation repeatedly has the same effect as performing it once.

Idempotency Key

A unique token supplied by the caller so the server can recognise a retry as the same logical request.

Authorisation

Reserving funds on a card without yet transferring them.

Capture

Actually claiming previously authorised funds.

Two Generals Problem

The proof that two parties on an unreliable channel can never both be certain a message was received.

Webhook

A callback from the gateway to the merchant announcing that something changed.

At-Least-Once Delivery

A guarantee that a message will arrive, possibly more than once, requiring the receiver to deduplicate.

Reconciliation

Comparing internal records against the processor's settlement file to find and resolve discrepancies.

← Back to all Case Studies

Contact Us




Send us a message