India runs on four payment rails, and a system that treats them as interchangeable will reconcile none of them correctly. UPI, IMPS, NEFT and RTGS differ in timing, in cost, in how failures return, and — most consequentially for anyone building a ledger — in which identifier the settlement file joins on. Choosing a rail is a product decision. Modelling all four is an architecture one.
UPI: instant to the customer, cyclical to the merchant
The rail everyone means when they say digital payments in India. Real-time from the payer’s perspective, addressed by a virtual payment address rather than an account number, and effectively free for merchants to accept.
Its defining engineering characteristic is the split between experience and settlement: the payer sees success in seconds, the merchant receives a net credit on a later cycle. Every reconciliation failure I have seen on UPI comes from collapsing those two events into one. The detail is worth its own piece, but the short version is that the success callback is authorisation, not arrival.
Reversals are real and they arrive after the fact. Indeterminate transactions — where the payer’s bank did not answer definitively in time — are a normal category at volume, not an edge case.
IMPS: the payout workhorse
Account-to-account, around the clock, immediate. This is what most disbursement flows actually use when money has to reach a bank account at two in the morning.
Its usefulness is precisely its risk: it is immediate and it is final. There is no batch window in which a mistake can be caught. A payout system on IMPS needs its idempotency to be genuinely correct rather than approximately correct, because a duplicate submission moves real money that must then be chased back manually.
The failure mode that catches teams is the return. A credit that fails at the beneficiary’s bank comes back as a separate inbound entry, later, with its own reference. If your code reverses its own ledger entry the moment it sees a failure and then also books the inbound return, you have credited the same money twice. I have found this bug in production more than once, and it is invisible until someone reconciles against the bank.
NEFT: batches, and why that is often fine
Settled in batches rather than individually. Modern NEFT runs continuously with frequent settlement, but the model is still fundamentally a queue rather than an instant transfer.
For scheduled bulk disbursement — payroll, vendor runs, commission payouts — this is usually the right rail: cheaper at volume, and a delay measured in tens of minutes is irrelevant when the payment was scheduled anyway. Building a payout system that uses IMPS for everything because it is faster is a common and expensive default.
The reconciliation implication is that a transfer initiated before a cut-off may settle after it. If your books date the transaction by initiation and the bank dates it by settlement, month-end will show a difference every single time and someone will spend a day rediscovering why.
RTGS: high value, gross, individual
Real-time gross settlement, intended for large single transfers, with a floor on the transaction value. Each transaction settles individually rather than being netted against others.
Most product engineers touch it rarely, which is exactly why it deserves a moment. Because volume is low, RTGS transactions are often handled manually or semi-manually — and manual handling is where out-of-band corrections enter the ledger. A single unrecorded RTGS transfer is materially larger than a thousand unrecorded UPI ones.
Idempotency is per-rail, not global
Every rail can deliver you the same instruction twice, and each does it differently, so a single retry-safety mechanism written for one will leak on the others.
A collection can arrive as a duplicate callback where the network retried a notification you already processed. A payout can be submitted twice because your own service retried after a timeout it could not distinguish from a failure. These are different problems: the first is someone else telling you the same thing twice, the second is you doing the same thing twice.
The first is solved by keying on the rail’s reference and rejecting a second write against a reference already terminal. The second needs an idempotency key generated before the request leaves your system, stored, and reused on every retry of that same logical instruction — so that a retry is recognisably the same intent rather than a new one.
Teams routinely build the first and assume it covers the second. It does not, and the failure mode is asymmetric: a duplicate collection callback is an accounting nuisance, while a duplicate payout is money out of the door that has to be recovered by asking someone to send it back. On an immediate, final rail there is no window in which to catch it.
The rule I hold to is that any operation which moves money carries a key generated by the caller, and the ledger enforces uniqueness on it. Not the network’s reference — yours, created before you asked for anything.
What this means for the ledger
Four rails, four sets of identifiers, four reversal behaviours, four sets of cut-offs. A ledger designed around one of them acquires the others as special cases, and special cases are where reconciliation goes to die.
Design for the general shape instead. Every money movement records: the rail, the rail’s own reference, the counterparty reference, the initiated timestamp and the settled timestamp as separate fields, and a state that includes “indeterminate” alongside success and failure. Store the identifiers even when today’s code does not use them, because they cannot be backfilled once the transaction is old.
Reconcile per rail, daily, against the provider’s file rather than its dashboard. Different rails will have different exception profiles, and a combined view hides which one is actually costing you.
None of this is exotic. It is the difference between a payments system that can answer an auditor’s question in a query and one that answers it in three engineers and two days — which is the whole argument of why ledgers drift, and the reason ledger work starts with identifiers rather than with features.
If you are picking rails now, or already carrying a system that models one and improvises the rest, that is a thirty-minute conversation rather than a proposal — describe the problem in your own words and I will tell you which of the four you actually need.
Questions I actually get
Which rail should a product default to?
UPI for customer-initiated collections under the per-transaction ceiling, because it is instant, cheap and universally adopted. IMPS for payouts that must land immediately at any hour. NEFT for scheduled bulk disbursement where a delay of up to an hour is acceptable. RTGS only for high-value single transfers. Most products need three of the four, which is why the ledger has to be rail-aware from the start.
Why does the choice of rail affect ledger design?
Because each rail has a different relationship between authorisation and settlement, a different reversal mechanism, and a different reference identifier. A ledger that models one rail and treats the others as variations will be unable to reconcile the ones it did not model, since it will not have stored the fields their settlement files use to join.
Are failed transfers actually reversed automatically?
Usually, but not instantly and not silently. A failed credit typically returns to the source within a defined window, arriving as a separate inbound entry rather than as the cancellation of the original. If the ledger reverses its own entry on failure and then also records the return, the amount is credited twice — a common and expensive bug.
What is the practical difference between IMPS and UPI for payouts?
UPI is designed around a payer-initiated pull or push using a virtual address; IMPS is the account-to-account rail underneath much of the real-time system and is what most payout flows actually use. For disbursement at arbitrary hours to a bank account number, IMPS is the usual answer, and its reference format is what the reconciliation must key on.
Does a business need to integrate directly with NPCI?
Almost never. Most businesses reach these rails through a bank or a licensed intermediary, which is the right call — direct participation carries compliance and operational obligations that are only justified at significant scale. The engineering implication is that your counterparty is the provider, and their file formats and cut-offs become your constraints.
How much of this matters before scale?
The identifier storage matters from transaction one, because it cannot be backfilled. The reconciliation cadence matters once daily volume passes the point where a human can eyeball it. The rail-aware ledger structure matters before you add your second rail, which happens sooner than most roadmaps assume.