Fintech technology
Businesses Fintech August 22, 2026 • 8 min read

UPI Integration Cheatsheet for Backend Engineers

For: A backend engineer at a Series A Indian SaaS or e-commerce company who has been handed a Jira ticket to integrate UPI payments and is now drowning in NPCI docs, PSP-specific SDKs, and contradictory Medium posts — trying to understand which flow to implement, how refunds actually work, and what happens when a payment is 'pending' for 90 minutes

If you only remember one thing from this page: UPI's pending status is a settlement state, not a loading spinner. It can silently expire after ~30 minutes with no webhook, and if your order logic treats pending as "almost success," you will double-fulfill orders or strand customer money. Everything below is written around that fact.

This is a working reference for backend engineers integrating UPI through a PSP (Razorpay, Cashfree, PayU, Juspay, PhonePe PG, etc.) into an Indian SaaS or e-commerce backend. It assumes you have a merchant account and API keys and are trying to figure out which UPI payment flow to ship, how the webhooks actually behave, and which errors to retry.

1. Pick the right UPI flow

FlowHow it worksBest forGotchas
CollectYou push a collect request to a VPA. User approves in their UPI app.Web checkout where user types VPA; recurring remindersHighest failure rate. VPA typos. User ignores notification. 2–5 min timeout typical.
IntentYou open a UPI deep link (upi://pay?...) that hands off to the user's UPI app.Mobile web and native appsDesktop won't work. You lose the return-to-app callback if user swipes away.
QR (Static/Dynamic)Render a QR encoding the same UPI URI. User scans with any UPI app.Desktop web, POS, invoices, kiosksStatic QRs don't carry a txn ID — reconcile by amount+timestamp, which is fragile at scale. Use dynamic QR.
UPI AutoPay (e-mandate)One-time mandate approval, then debit on schedule.Subscriptions, SaaS billing, EMIMandate approval is its own state machine. Debits still fail on insufficient balance — build dunning.

Decision rule

2. The payment state machine (the part everyone gets wrong)

Regardless of PSP, the underlying NPCI states you care about are:

StateMeaningYour order should be...
CREATED / INITIATEDYou created the transaction, user hasn't actedCart locked, order not created
PENDING / DEEMEDDebited from payer but credit to merchant not confirmed. Legally distinct from success.Do not fulfill. Order in AWAITING_SETTLEMENT.
SUCCESSCredit confirmed to merchant account by NPCIFulfill.
FAILURETerminal failureRelease cart, allow retry with new txn ID.
EXPIREDTimeout (Collect ~2–5 min, Intent ~5–9 min, QR varies)Same as failure. But verify before releasing.

Why pending is dangerous

Rule of thumb

Never derive order state from a webhook alone. On any terminal-looking event, call the PSP's fetch payment status API and treat that response as the source of truth. Webhooks are triggers; the status API is truth.

3. Webhook handling checklist

4. UPI failure codes: retryable vs terminal

These are the NPCI response codes your PSP will surface (sometimes renamed). Handle by category, not by code.

CodeMeaningAction
U30, ZMDebit failed at payer bankTerminal. Show "try another method."
U69, Z9Insufficient fundsTerminal for this attempt. Prompt retry with different account.
U16, UXRisk / fraud declineTerminal. Do not auto-retry.
U67, BTTimeout at PSP/bankRetryable — but only with a new txn ID. Status may still resolve; check before retry.
U54Transaction expiredTerminal.
XB, XD, XFFormat / invalid VPATerminal. Validate input.
U28, U88Beneficiary bank offline / NPCI downtimeRetryable after backoff. Consider fallback to card/netbanking.

5. Refunds

6. Testing what sandbox won't show you

PSP sandboxes give you deterministic success/failure. Production gives you:

Before you ship: run a load test that injects delayed webhooks, out-of-order events, and duplicate payment attempts against your order service. If your fulfillment logic doesn't survive that, it won't survive week one in production. Teams building payment-heavy backends — we've seen this on GimBooks and Cashpo — spend more time on reconciliation than on the happy path, and that's the correct ratio.

7. A minimum viable integration checklist

  1. Signed webhook receiver with idempotency key on event_id.
  2. Order state machine with an explicit AWAITING_SETTLEMENT state that blocks fulfillment.
  3. Status-fetch call as source of truth on every terminal-looking webhook.
  4. Reconciliation cron for payments pending > 20 minutes.
  5. Failure-code classifier (retryable vs terminal vs risk).
  6. Refund webhook handler, separate from payment webhook handler.
  7. Structured logs keyed by merchant_txn_id and npci_rrn.
  8. Alerting on: webhook lag > 60s, pending rate > baseline, U28 spike (bank outage).

Frequently Asked Questions

What's the difference between UPI Collect and UPI Intent from a backend perspective?

Collect is a pull — you push a request to a VPA and wait for the user to approve in their app. Intent is a handoff — you generate a upi:// deep link and the user's UPI app takes over. Backend state handling is nearly identical; the difference is failure rate (Collect is worse) and where the timeout lives (Collect: your server; Intent: the UPI app).

How long should I keep a UPI payment in pending before marking it failed?

Don't mark it failed based on time alone. Poll the PSP's status API. A pragmatic pattern: after 20 minutes of pending, poll every 5 minutes for up to 2 hours, then escalate to a manual reconciliation queue. Never auto-release inventory or refund on timeout without a definitive status response.

Do I need to integrate with NPCI directly?

No, and you almost certainly shouldn't. Direct NPCI integration requires being a PSP or a TPAP, which involves bank sponsorship and regulatory compliance. Use Razorpay, Cashfree, Juspay, PayU, or similar — they abstract NPCI and handle settlement.

How do I handle a customer claiming they were debited but the order failed?

Ask for the UPI reference number (RRN) from their bank SMS. Look it up via your PSP's search API against the RRN, not your internal order ID. If the PSP shows the payment as failed but the customer's bank shows a debit, NPCI will auto-reverse within T+2. If it doesn't, raise a chargeback via your PSP dashboard.

Should I build UPI AutoPay for subscriptions or use recurring Collect requests?

AutoPay. Recurring Collects require the user to approve every single charge, which kills conversion. AutoPay uses an e-mandate: one approval, scheduled debits. It's a different integration surface (mandate creation, mandate state machine, debit notifications 24 hours before charge) but it's the only production-viable subscription flow on UPI.

Found this useful? CodeNicely publishes engineering and product playbooks weekly. Browse the archive or tell us what you're building.