Integrating an instant payment rail. 6 chapters and a final quiz.
The engineering work behind account-to-account collection on your market's rail: choosing between push and request to pay, resolving an alias without hitting the wrong account, building an idempotent notification consumer, reconciling a credit to the second, refunding on a rail with no chargebacks, and classifying failures so you never pay twice.
Choosing between push, QR, request to pay, and a recurring mandate for the collection journey you are building
Wiring alias resolution (Pix keys, VPAs, PayIDs, ShapIDs) and handling ambiguous responses
Building an idempotent notification consumer that tolerates redelivery, out-of-order events, and silence
Reconciling sale, credit, and settlement using the rail's end-to-end IDs
Chapter 1. Push or request to pay: choosing the initiation model.
An instant rail has no authorization and no capture: the payment either goes or it doesn't. With no intermediate step, the design must settle up front who triggers the payment. In the push model, the payer initiates from their banking app and the payee learns of the credit after the fact. In the request to pay model, the payee sends a request that the payer approves. The code differs, the reconciliation differs, and so does the abandonment rate.
Model
Who triggers it
What the payee receives
Leading rails
Free-form push
The payer, from their banking app
An unannounced credit, matched after the fact
SCT Inst, FedNow Service, RTP network, SPEI, Faster Payments Service
QR-initiated push
The payer scans a code generated by the payee
A credit carrying the reference encoded in the QR code
Pix (EMVCo QR), PromptPay (Thai QR Payment, EMV QRCPS merchant-presented mode), DuitNow QR
Request to pay
The payee, who sends the request to the payer
An accept or decline response, then a separate credit
RTP network and FedNow Service (pain.013 / pain.014), SEPA Request-to-Pay, UPI collect request
Recurring mandate
The payee, under a mandate the payer authorized in advance
A credit on the due date, with no action from the payer
Pix Automático (Brazil, since June 16, 2025), PayTo (Australia, 2022), Variable Recurring Payments (UK)
The four initiation models in live use, and the rails that support them
A request to pay, seen from the payee's system
Payee's system
Creates the request
Amount, order reference, expiry date. The end-to-end ID is set here and nowhere else
➜
Payee's PSP
Sends the request over the rail
`pain.013` on the RTP network and the FedNow Service; collect request on UPI; dynamic cobrança on Pix
➜
Payer's PSP
Displays the request in the app
The payer sees the requester's name, the amount, and the reference, without typing anything
➜
Payer
Approves, declines, or lets it expire
Three outcomes, not two. Silent expiry is the case testing most often misses
➜
Payer's PSP
Responds, then pushes the payment
The `pain.014` carries the response. The `pacs.008` that follows is a separate transaction with its own outcome
➜
Payee's system
Matches the response to the credit
Two events to correlate. An acceptance without a credit is still just a receivable
⚠️
An acceptance is not a payment received
A positive pain.014 means the payer consented, not that the money arrived. In between, the payment can still be rejected for insufficient funds, an exceeded limit, or a closed account. An integration that releases the order on the request-to-pay response is extending credit without knowing it. The trigger is the confirmed credit, never the intent.
Remote sales with a shopping cart: request to pay or a dynamic QR code, so the order reference travels with the payment
In-store payments: a merchant-presented QR code, or a contactless tap where the rail supports it (Pix por Aproximação in Brazil)
B2B invoices: request to pay, the only model that carries the due date and amount without rekeying
Payouts (refunds, payroll, claims): pure push; the business becomes the payer and the rail works like an ordinary credit transfer
Subscriptions: a recurring mandate, with authorization and revocation rules specific to each market
One question decides the model: does the payee know the amount before the payment? If so, a request to pay or a dynamic QR code removes manual entry, and with it wrong amounts and lost references. If not (a donation, a top-up, a partial payment), free-form push is the only option, and the integration must match unexpected credits to open receivables, a problem covered in chapter 4.
🎯 Quick question
On the RTP network, your system receives a positive pain.014. What can you conclude?
Chapter 2. Addressing: from alias to account, without paying the wrong person.
Nearly every modern instant rail has replaced account details with an alias: a mobile number, a national ID, an email address, or a random string. A central directory resolves the alias to an account at payment time. The extra step looks harmless. In practice, it shifts part of the risk to the integrator, who must decide what to do when the lookup returns anything other than “yes.”
Rail (market)
Alias name
Accepted formats
Watch out for
Pix (Brazil)
Chave (Pix key), resolved through the DICT directory
CPF (individual taxpayer ID), CNPJ (business ID), mobile number, email, random key
The holder can delete and recreate a key at will, so a stored key can go stale without warning
UPI (India)
VPA (Virtual Payment Address)
A string of the form name@bank, linked to a checking or savings account
Direct access is limited to banks; a fintech operates as a TPAP (third-party app provider) under a sponsor bank
NPP (Australia)
PayID
Mobile number, email, ABN, organization ID
The payee's name is shown before the payer confirms, Australia's main fraud safeguard
PayShap (South Africa)
ShapID
Mainly mobile numbers
ISO 20022 rail, but merchant payment coverage is still partial
Raast (Pakistan) and CliQ (Jordan)
Raast ID, CliQ ID
Mobile number; CliQ also addresses mobile wallets
Bank accounts and wallets share the same addressing scheme
PromptPay (Thailand)
PromptPay registration
Mobile number, national ID, business tax ID, e-wallet ID
More than 81 million registrations in mid-2025; one holder often has several
Bre-B (Colombia)
Llave
ID document, mobile number, email, alphanumeric code
Rail opened to the general public on October 6, 2025, so directory practices are still new
Alias directories on the main rails, and what they accept
Alias resolution: the guards to put around the call
def resolve_alias(alias, expected_name, amount):
# 1. Never cache persistently: holders can reassign an alias.
# Resolve on every payment, even for a known customer.
r = directory.resolve(alias, timeout_ms=800)
if r.status == "NOT_FOUND":
return Decline("unknown alias", retryable=False)
if r.status == "UNAVAILABLE":
# Directory outage: do NOT guess. Offer manual account entry.
return Decline("directory unavailable", retryable=True)
# 2. Name check: compare, don't trust.
score = compare_names(r.holder_name, expected_name)
if score == "MISMATCH":
return Decline("different account holder", retryable=False)
if score == "CLOSE":
# Gray zone: show the returned name and require confirmation.
return Confirmation(displayed_name=r.holder_name)
# 3. Log the resolved (alias, account, timestamp) tuple.
# This record is what will defend the case in a dispute.
log.write(alias, r.account, r.holder_name, now())
return Ok(r.account)
🔑
Euro area: Verification of Payee is now mandatory
Regulation (EU) 2024/886 has required euro area payment service providers to offer Verification of Payee since October 9, 2025, free of charge, on all SEPA credit transfers, not just instant ones. The European Payments Council's Verification of Payee rulebook sets a maximum of 5 seconds to return a response, with a target of 1 second or less. There are four possible responses: match, no match, close match (with the name returned), and verification not possible. The last two require a decision screen for the payer. Treating them as a success defeats the purpose of the rule.
Never cache an alias long-term: the holder can delete or reassign it at any time, and the rail notifies no one
Treat a directory outage as a retryable decline, distinct from an unknown alias: each calls for the opposite response
Show the returned name before confirmation, even when the rail doesn't require it, because it's the only check the payer can make
Log the resolution with a timestamp: without that record, no dispute can be defended
Count the characters: a hand-keyed alias can be off by one digit, and the rail will execute a perfectly valid payment to a stranger
105M
llaves (aliases) registered on Bre-B by its seventh month of operation (Colombia)
Banco de la República, May 2026
81M+
PromptPay registrations in Thailand
Bank of Thailand, mid-2025
5 s
maximum response time for Verification of Payee in the euro area
EPC, Verification Of Payee Scheme Rulebook
🎯 Quick question
A Verification of Payee check in the euro area returns “close match” with the account holder's name. What should you do?
Chapter 3. Notifications and idempotency: the asynchronous contract behind real time.
On an instant rail, the notification is everything. There is no clearing file the next morning, no intermediate status to query, no delayed capture. The credit lands, a message reports it, and the payee's system acts. Yet two clocks are running, and confusing them is the most common integration mistake. The rail's clock runs in seconds; the clock for the notification delivered to the merchant depends on a queue, a network, and a server.
Signal
Typical latency
What it proves
Recommended use
PSP notification (webhook)
From seconds to a few minutes
That the PSP has recorded a credit
Primary trigger, never the only proof
Status API query
On demand
The current status as the PSP sees it; the source of truth when in doubt
Safety net, called on silence or inconsistency
Statement or account report
Delayed, depending on the PSP
The actual credit to the settlement account
Accounting reconciliation and end-of-day check
Payer's screen (screenshot, receipt on display)
Immediate
Nothing binding
None; see the warning below
Three credit signals, three levels of trust
⚠️
A receipt shown by the payer never releases goods
On consumer QR rails, fake-receipt fraud is routine. The payer shows a doctored confirmation screen, the cashier hands over the goods, and the credit never arrives. The rail can do nothing, since no payment was ever sent. The defense is as much organizational as technical. Confirm payment on the notification received by the merchant's own system, never on the customer's phone screen. Training checkout staff on that one point prevents more losses than any scoring engine.
From confirmed credit to order release
PSP
POST to the merchant's endpoint
Signed body, including the rail's end-to-end ID
➜
Merchant
Verifies the signature
On the raw body, before any deserialization
➜
Merchant
Deduplicates on the rail ID
Not on the PSP's event ID, which changes on redelivery
➜
Merchant
Checks amount, currency, and reference
A partial or excess credit does not settle an order
➜
Merchant
Returns 2xx, then processes from a queue
Business processing runs outside the HTTP cycle
Deduplicate on the rail ID, not the message ID: the first is unique to each payment and stable; the second belongs to the delivery layer
Check the amount received against the amount expected: with free-form push, the payer decides what to send, and a payment one cent short is not a settlement
Handle out-of-order events: on request-to-pay rails, the response and the credit travel separately and sometimes arrive in reverse order
Set a silence timeout: once the scheme's own time limit has passed, query the status API rather than keep waiting, because a missing message is not neutral information
Make business processing replayable: delivery is guaranteed at least once, so duplicates are the normal case, not an incident
Receiving an instant credit: deduplication and amount check
app.post("/notifications/instant", async (req, res) => {
if (!verifySignature(req.rawBody, req.headers)) {
return res.status(400).send("invalid signature");
}
const evt = JSON.parse(req.rawBody);
// Dedup key = the RAIL's end-to-end ID.
// Pix: endToEndId. ISO 20022: EndToEndId in the pacs.008. UPI: UPI
// transaction ID. The PSP's event ID, by contrast, changes on redelivery.
const isNew = await db.credits.insertIfAbsent({
railId: evt.endToEndId,
amount: evt.amount,
currency: evt.currency,
receivedAt: new Date(),
});
if (!isNew) return res.status(200).send("already processed");
// A payer can send less, or more, than the expected amount.
const order = await db.orders.byReference(evt.reference);
if (!order || order.amountDue !== evt.amount || order.currency !== evt.currency) {
await queue.enqueue("credit-to-review", evt); // manual review
return res.status(200).send("ok");
}
await queue.enqueue("release-order", { order: order.id, credit: evt.endToEndId });
return res.status(200).send("ok");
});
The silence timeout needs an explicit setting. The European Payments Council's SEPA Instant Credit Transfer rulebook sets a maximum execution time of 10 seconds, with a time-out deadline of 20 seconds in exceptional circumstances. A system that waits 30 minutes before raising a flag has already lost its customer; one that retries after two seconds creates duplicates. Base the value on the scheme, not on a hunch.
🎯 Quick question
Which field should you use to deduplicate credit notifications from an instant rail?
Chapter 4. Real-time reconciliation: the ID that carries everything.
On an instant rail, reconciliation is no longer an overnight batch. It becomes an online check that runs while the customer waits for the confirmation screen. It hinges on a single element: an ID that travels with the payment from initiation to the account statement. Choosing it, generating it in the right place, and storing it correctly is most of the work. The rest is a join.
Rail
ID carried
Who generates it
What to store alongside it
Pix (Brazil)
endToEndId, 32 characters, plus the cobrança's txid
The direct or indirect participant, or the initiating provider (up to 35 characters for the txid)
The txid links the credit to the invoice; the endToEndId links the credit to the SPI
EndToEndId in the pacs.008, plus structured remittance data
The payment originator
The structured remittance reference, which carries the invoice number
UPI (India)
UPI transaction ID, plus the merchant reference in the request
The app or provider that originated the request
The payer's VPA and the order ID sent in the collect request
QR rails (PromptPay, DuitNow QR, Pix QR)
The reference encoded in the QR code itself
The payee, when it generates the code
The QR ↔ order link, with its expiry date
The reconciliation ID, rail by rail
Anatomy of a Pix endToEndId: 32 characters you can read
E 12345678 202603151432 a1b2c3d4e5f
| | | |
| | | +-- 11 alphanumeric characters, generator's sequence number
| | +--------------- yyyyMMddHHmm in UTC (12 characters)
| +------------------------ ISPB of the agent that generated it, or first 8 digits
| of the initiating provider's CNPJ (8 digits)
+-------------------------- literal "E"
Binding rules (Manual de Padroes para Iniciacao do Pix, BCB):
- strict uniqueness: never send the same value to the SPI twice
- the sequence number must be unique within a given minute yyyyMMddHHmm
- 12-hour tolerance, early or late, relative to actual processing
🔑
Three clocks, one moment of finality
The customer's clock stops when their app shows the confirmation. The rail's clock stops when the acceptance message comes back, within seconds. The interbank settlement clock stops when the providers' accounts are debited and credited. On TIPS, Brazil's SPI, or the FedNow Service, that third clock runs continuously in central bank money; on Zelle or Interac e-Transfer, it goes through deferred clearing. A treasury dashboard that confuses these three moments reports liquidity that doesn't exist yet.
Reconcile at three points: the receivable in the merchant's books, the credit reported by the PSP, and the line on the settlement account statement
Put a unique constraint in the database on the rail ID: a technical constraint beats application-level vigilance
Keep the reference even after a rejection: a rejected payment that is resent gets a new ID, and only the payee holds the link between the two
Measure the lag between notification and statement: a growing lag signals a stuck queue at the PSP before the customer complains
Handle orphan credits in a dedicated queue, with a stated processing time: an instant transfer without a reference is still a payment you must honor
Free-form push inevitably produces credits with no usable reference: the payer sent the money from their app without copying the order number. Three levers reduce the problem. The first is a short, readable reference that the payer can copy without mistakes. The second is a virtual account per customer, when the PSP offers one, so the credited account identifies the payer. The third is to switch to a QR code or a request to pay, both of which carry the reference automatically.
🎯 Quick question
In the format ExxxxxxxxyyyyMMddHHmmkkkkkkkkkkk, what do the 12 middle characters of a Pix endToEndId represent?
Chapter 5. Irrevocability: refunds and disputes without chargebacks.
An accepted instant transfer is final. No network reverses it, no dispute window opens, and no reason code brings it back. The merchant is rid of chargebacks but loses the ability to cancel. Two distinct operations must therefore be built separately. A refund is decided by the payee; a dispute is raised by the payer with their own provider. Many integrations confuse the two.
⚠️
A refund is an outgoing payment, with everything that implies
A refund on an instant rail is a new payment, from the merchant's account to the customer's. It draws on available cash, is subject to the rail's limits, fails if the recipient's account has been closed, and is itself irrevocable. That has three operational consequences: four-eyes approval above a threshold, a daily limit separate from the collection limit, and mandatory matching of each refund to the original sale. A refund API wired in without these safeguards is an exit door for insider fraud.
Market
Framework
Key takeaway for integrators
Brazil (Pix)
MED (Mecanismo Especial de Devolução), plus devolução through the Pix API
The voluntary devolução is triggered by PUT /pix/{e2eid}/devolucao/{id} and results in a pacs.004 with reason code MD06. The MED is something else: a claim the victim opens with their bank within 80 days, which recovers only the funds still in the account.
US (RTP network, FedNow Service)
Return of funds request (camt.056)
The payee is not required to return the money. The camt.056 requests; it does not order. No specific federal regime covers authorized push payment fraud on these rails, so risk allocation is negotiated by contract.
India (UPI)
URCS, with automatic chargeback acceptance or rejection
Since NPCI circular UPI-OC-No-213-FY-2024-25 of February 10, 2025, effective February 15, 2025, chargebacks are accepted or rejected automatically based on the TCC or RET the beneficiary bank submits in the next settlement cycle. Slow reconciliation on the beneficiary side turns into automatic losses.
UK (Faster Payments Service)
Mandatory reimbursement of authorized push payment fraud since October 7, 2024
Capped at £85,000, with the cost split equally between the sending and receiving providers. Collecting payments in the UK therefore exposes a receiving provider to half the cost of any such fraud whose proceeds land in its merchant customers' accounts.
Recourse and recovery: four jurisdictions, four regimes that don't compare
Pix refunds: the call, and what to check first
PUT /v2/pix/{e2eid}/devolucao/{id}
Host: api-pix.example.com.br
Content-Type: application/json
{
"valor": "120.50",
"natureza": "ORIGINAL",
"descricao": "Devolucao pedido 2026-4471"
}
# Before calling:
# - {e2eid} is the endToEndId of the Pix payment RECEIVED, not of the refund
# - {id} is generated by the payee's system and serves as the idempotency key:
# replaying the same {id} does not create a second refund
# - total refunds cannot exceed the amount received
# - the reason code on the pacs.004 will be MD06 for a standard Pix payment
Deterministic refund ID: derive {id} from the order, never from a counter or a random value, so that a replay after an incident is harmless
Running-total check: add up the partial refunds already issued before authorizing another
Separation of duties: above a set threshold, the person who enters a refund is not the one who approves it
Full traceability: from a sale, find every refund; from a refund, find the sale
Honest timelines: tell the customer the rail's actual timing, not a card-style delay of several business days
Customer-facing messaging deserves the same rigor as the code. Promising “protection” on an instant rail invites claims the rail doesn't cover. Brazil's MED returns whatever remains in the fraudster's account and nothing more, while the UK regime is capped at £85,000 and applies only to eligible payments. In the US, no federal law requires reimbursement of authorized push payment fraud. Describing the actual protections, limits included, costs less than a dispute.
🎯 Quick question
On the RTP network, your customer paid a supplier by mistake. What does a camt.056 sent by their bank do?
Chapter 6. Failures, limits, and pre-launch testing.
An instant payment can fail in four ways, and only one looks like a card decline. An explicit rejection returns a code; an expiry closes a request to pay that got no response; an exceeded limit blocks the payment before it even reaches the rail. Silence, finally, returns nothing at all. Handling all four with the same retry logic produces double payments, which on an irrevocable rail must be recovered by hand.
Card type
Signal received
Retry?
What to do
Explicit rejection
Negative status message with a reason code: AC04 closed account, AM04 insufficient funds, AC03 invalid account number
Depends on the code
Map each code to a customer message and an action. Retry AM04 later, never AC04.
Expiration
No response from the payer before the request expires
Yes, with a new request
Close the state cleanly, release reserved inventory, and resend with a new ID.
Cap
Declined by the payer's provider, often before reaching the rail
No, as long as the limit applies
Offer to split the payment or use another method. The limit is set either by the scheme or by the payer's bank.
Silence
Nothing, by the end of the scheme's time limit
Never blindly
Query the PSP's status API before resending anything. On SCT Inst, no response within the time limit counts as a rejection.
The four failure types and how to handle each
10 s / 20 s
maximum execution time and time-out deadline of the SEPA Instant Credit Transfer scheme
EPC, SCT Inst Rulebook
$10M
FedNow Service per-transaction limit, raised from the $100,000 default
Federal Reserve Financial Services, 2025
₹5 lakh
UPI per-transaction limit for person-to-merchant payments in verified categories, with a cap of ₹10 lakh per day
NPCI, effective September 15, 2025
Rp 2,500
maximum regulated fee for a BI-FAST transaction in Indonesia
Bank Indonesia
⚠️
Limits are the top cause of rejections in production
Limits vary by scheme, by provider, and sometimes by customer. The FedNow Service applies a default of $100,000 per transaction, with a network maximum raised to $10 million in 2025. Aani caps transfers at AED 50,000. UPI sets ₹1 lakh between individuals and ₹5 lakh to a merchant in a verified category. An average order value set without checking these figures will hit a wall on the first large order. The limit must be a configuration setting that can be changed without a code release.
🧪
Test failures before successes
Testing starts with rejection, expiry, and silence. The happy path tests itself the day it works; the other three only show up in production, at the worst possible moment.
🔁
Replay every notification twice
Deliberately deliver the same message twice, then in reverse order. A correct integration records exactly one payment and releases the order exactly once.
💸
Test refunds in the sandbox
Refunds are the least-tested flow and the most expensive to fix. Check the partial running total, the closed account, and recovery after an incident using the same ID.
📉
Ramp up volume gradually
Shift a small share of traffic, measure the gap between credits notified and credits on the statement, then ramp up. A stable gap clears you to scale.
📟
Instrument all four failure types
One counter per failure type, broken down by reason code. Only that view distinguishes a PSP outage from a shift in customer behavior.
Then there is the question of access to the rail, which drives the timeline far more than the code does. In India, direct access to UPI is reserved for banks, and a fintech operates as a TPAP under a sponsor bank. In Brazil, a payment institution can join the SPI. In Turkey, payment institutions and e-money institutions participate directly in FAST. The answer shapes margins, dependencies, and lead times. Settle it before the first line of code, not after testing.
🎯 Quick question
A request to pay gets no response by the end of the scheme's time limit. What is the right move?