Case study · Zupee · Scale, not 0→1
~7 min read Mar '25 – Jul '25 Payments · KYC · Dynamic Routing

Trust isn't the button. It's everything after the tap.

Where AgriLokal was building a business from nothing, Zupee is the opposite problem: an existing payment stack, wallet, KYC, routing across PSPs, that has to stay trustworthy at a size where every edge case is a real user's money.

Role
Product Management Intern, reporting to a PM Manager
Squad
~25 (Dev + QA)
Stakeholders
Product Head, 1 Product Analyst
Timeline
Mar '25 – Jul '25
TL;DR · 60 seconds
The stakes

Every point of friction in a payments flow, KYC, retry logic, routing, a withdrawal status screen, costs either a deposit or a user's trust, and at Zupee's scale, both are measured in real money.

The move

Went after five separate friction points across the deposit and withdrawal lifecycle: personalized deposit widgets, an Aadhaar-first KYC redesign, QR/PineLabs/QuickPay rollout, SR-based dynamic payment routing, and a high-value withdrawal status screen.

The outcome

+2.37% average deposit, +11pp Aadhaar conversion, -21% failed payment attempts, -15% high-value withdrawal drop-off.

Avg. deposit
+2.37%
Aadhaar conversion
+11pp
Failed payments
−21%
High-value drop-off
−15%
Problem

Five separate leaks across one payment lifecycle

Deposits, KYC, and withdrawals each had their own friction point: generic (non-personalized) deposit prompts, a KYC flow with high fallback usage, limited QR/UPI coverage, static payment-mode ordering that didn't adapt to real-time success rates, and a withdrawal status screen that quietly lost users right after they'd won.

Context

How a UPI payment actually moves

Useful to see the whole path before the rest of this makes sense: a user's tap doesn't go straight to a bank, it passes through several hops, and Zupee's own systems sit specifically at the routing and reconciliation layer, not inside the banks themselves.

User / App (Zupee) PSP (routing layer) NPCI Switch (UPI rails) Remitter bank (debits payer) Beneficiary bank (credits payee) Callback to app success / pending / failed
Every payment is a round trip through NPCI's switch, not a direct app-to-bank call, this is why a "failed" state in-app can still mean the debit happened somewhere upstream.
Research

Wallet and entry-fee trends, and where fallback usage spiked

Deposit personalization started from wallet balance and entry-fee pattern data per user, not a single generic prompt. Separately, KYC fallback usage (users dropping to a slower manual path) was traced to specific friction points in the existing Aadhaar flow, and payment failures were traced by success-rate (SR) band per mode, not just an aggregate failure count.

Insights

Static systems fail differently than dynamic ones

A fixed payment-mode order works until a specific mode's success rate degrades, at which point every user routed to it inherits that failure, regardless of their own history. Aadhaar-first KYC only converts well if the fallback path is rare, not a default users end up on. Both point at the same fix pattern: make the system respond to live signal instead of a fixed configuration.

Hypothesis

Personalize what the user experiences, adapt what the system decides

If deposit prompts adapted to individual wallet/entry-fee behavior, if KYC reduced reliance on fallback through voice-led guidance, and if payment routing responded to real-time success rate instead of a static order, deposit completion and payment success would both improve without adding friction anywhere else.

Experiments

Personalized FTD widgets

Shipped first-time-deposit widgets personalized by each user's wallet balance and entry-fee trends, rather than a single generic deposit prompt. Result: average deposit +2.37%.

QR / Pine Labs / QuickPay: rolled out QR-based UPI flows to 30% of new users, integrated Pine Labs, and built QuickPay, a 1-click last-used-mode reuse flow with downtime-aware fallbacks, to streamline repeat payments specifically.

Iterations

Aadhaar-first KYC, rebuilt around voice, for tier 2-4 cities

That year, government regulation made KYC mandatory, and Zupee's user base skewed heavily toward tier 2, 3, and 4 cities, where comfort with a text-heavy upload flow couldn't be assumed. Redesigned KYC to be Aadhaar-first, with both visual and audio cues at each step, and voice-led onboarding as a primary path rather than an accessibility afterthought, specifically to reduce how often users dropped into the slower manual fallback path. Result: Aadhaar conversion +11pp.

Edge cases considered — KYC & onboarding
  • User has no Aadhaar-linked mobile number
  • Voice input misheard or unsupported dialect
  • User abandons mid-flow and returns later
  • Aadhaar verification service downtime
  • Adaptive UI serving the wrong variant to a returning user
  • Fallback path itself failing
Tradeoffs

Dynamic routing adds complexity you have to own

A static payment-mode order is simple to reason about and simple to debug. Dynamic, SR-based reordering is more resilient but means the "why did this user see this payment mode" question no longer has a fixed answer, every routing decision now depends on a live, changing signal. That's a real cost in debuggability traded for a real gain in success rate.

Launch

Unscheduled downtime, handled with color-coded dynamic switching

Banks and PSPs go down without warning, and a fixed payment-mode order has no way to react. Built success-rate-based fallback logic with three tiers, color-coded so the pattern is instantly legible: Critical (0–25% SR, red), Poor (26–50% SR, orange), Good (51–75% SR, green). A bank or PSP sliding into red gets dynamically deprioritized in real time, and the next-healthiest option moves to the top, instead of users continuing to be routed into a mode that's currently failing.

This ran specifically against a curated set of Zupee's top 10 banks, prioritized because we already had enough historical success-rate and downtime data on them to make the live scoring meaningful, rather than trying to score every possible bank with thin data.

Live SR, top-10 banks Critical (0–25%) Poor (26–50%) Good (51–75%) Switched out of routing Shown after healthier banks Shown first
Red / orange / green scoring across the top 10 banks: the color coding itself was deliberate, so anyone on the team could read system health at a glance without learning a numeric scale.

Recommended payment method: alongside live routing, the system also looked at whether a user had paid with a specific UPI ID or VPA in roughly the last 3-6 months, and surfaced that as a "recommended" option. Live bank health decides what's safe to route to; recent personal usage decides what's fastest for that specific user, the two run together rather than one replacing the other.

User experienced

Their own recently-used UPI ID shows up as "recommended," and whichever bank is healthiest right now is what they're routed to, without ever seeing a bank that's currently down.

System did

Continuously recalculated each of the top-10 banks' live success rate, bucketed it red/orange/green, re-ordered routing accordingly, and separately checked the user's own 3-6 month payment history for a personalized recommendation.

I was thinking

A fixed order is a bet that today's best bank stays the best bank. That bet breaks exactly when a bank has unscheduled downtime, which is precisely when you can least afford it, and the color coding existed so the whole team, not just whoever built it, could reason about system health instantly.

Withdrawal, built for trust with power users: for users depositing over Rs.5k-10k, a slow or unclear withdrawal does more damage to trust than almost anything else, a win that's hard to cash out reads as a product that doesn't want to pay you. Revamped the high-value withdrawal status screen with animations and trust messaging, added a "maximize" button that auto-fills the maximum withdrawable amount in one tap, and an inline flow to switch to an alternate account if the primary one hit an issue, rather than leaving the user stuck. Result: drop-offs down 15% at exactly the moment users had the most reason to worry about their money.

Edge cases considered — high-value withdrawal
  • Primary account fails mid-withdrawal
  • User wants max balance withdrawn, not a manually typed amount
  • Withdrawal initiated during a bank's Critical (red) window
  • User has multiple linked accounts, ambiguous which to use
  • Status screen shown mid-processing vs. truly failed
Metrics

What moved, across five separate initiatives

InitiativeMetricResult
Personalized FTD widgetsAverage deposit+2.37%
Aadhaar-first KYC redesignAadhaar conversion+11pp
QR / Pine Labs / QuickPayNew-user QR coverage30%
SR-based 3-tier routingFailed payment attempts−21%
Withdrawal status screenHigh-value drop-off (>Rs.10k)−15%
Reflection

The five fixes shared one root cause

Personalization, KYC, routing, and the withdrawal screen look like five unrelated tickets. They're the same problem in five places: a system that treated every user and every payment mode identically, when the underlying reality (wallet behavior, KYC readiness, gateway health) was never actually uniform. Once framed that way, "which fix next" stopped being a prioritization guess and became a search for the next place the system was still assuming uniformity that didn't exist.

If I had three more months

Extend SR-based routing with per-user history, not just per-mode aggregate health.
Predict gateway degradation before it crosses into the Critical tier, instead of reacting once it does.
Extend the withdrawal trust-messaging pattern to more transaction types, not just high-value withdrawals.
Reduce Aadhaar fallback further by identifying exactly which user segments still default to it.