How to use this page
This page walks you through building one product, start to finish. The Credit module explains why underwriting is hard. The build sheet lists the tools. This page tells you which eight steps there are and how to connect them.
Start with this, because it invalidates most BNPL designs people arrive with. The product you are probably imagining — a credit line, loaded into a wallet, spent at checkout — is not permitted in India. A 2022 RBI circular stopped prepaid instruments being loaded from credit lines, and the digital lending rules stopped money passing through platform accounts. Firms that had built that model pivoted or shut down. What is permitted is a fresh loan, sanctioned for each transaction, disbursed directly. Everything on this page follows from that.
What BNPL actually is in India
BNPL at checkout means: a customer buys something, a lender pays the merchant now, and the customer repays the lender later in instalments.
In India that is digital lending. Not a payment method, not a wallet feature — lending, governed by the RBI Digital Lending Directions, 2025 (issued 8 May 2025), which consolidated the 2022 guidelines, the default loss guarantee framework and the digital-channel outsourcing rules into one rulebook.
Before you write any code, settle which of two things you are:
| You are… | Which means |
|---|---|
| The lender | An NBFC. Registration with RBI, minimum Net Owned Fund ₹2 crore. Your balance sheet, your risk, your licence. |
| An LSP — Lending Service Provider | You build the experience; a regulated entity lends. The RE remains accountable for everything you do, which shapes what they will let you ship. |
What it is not:
- Not a payment product. Calling it “pay later” does not move it out of lending regulation.
- Not a credit line. Each purchase is a separate sanction.
- Not yours to hold. Money moves between the borrower's bank account and the regulated entity. It does not pass through you.
The whole journey, in one table
| # | Step | In plain words |
|---|---|---|
| 1 | Decide what you are | NBFC or LSP. This is an architecture decision, not a legal footnote. |
| 2 | Show the offer | At checkout, in milliseconds, without promising anything you cannot deliver. |
| 3 | The Key Fact Statement | Before sanction. Every fee, one APR, legally binding. |
| 4 | Sanction the loan | A fresh loan, for this purchase, now. |
| 5 | Move the money | Directly. Never through your account. |
| 6 | The cooling-off window | They can walk away paying principal and proportionate APR only. |
| 7 | Repayment | And what happens the first time it fails. |
| 8 | Collections | Regulated speech, with a criminal-law floor underneath it. |
Steps 1, 3 and 6 are the ones product teams treat as paperwork. They are the three that decide whether the thing you built is legal. Step 8 is the one that decides whether you stay in business after your first bad cohort.
Steps 1 and 2 — what you are, and the offer
Step 1 — Decide what you are
Almost every other decision follows from this one, so make it first and write it down.
| Route | What you need | Trade |
|---|---|---|
| Become an NBFC | RBI registration, ₹2 crore Net Owned Fund, capital adequacy, your own reporting. | Full control. Months of process and a permanent compliance function. |
| Be an LSP for a regulated entity | A partnership agreement, and the RE's approval of your flows. | Fast to start. The RE is accountable for what you build, so they will constrain it — and they are right to. |
| Merchant-funded, no credit | Nothing. The merchant discounts instead of lending. | Not BNPL. Worth naming because it is sometimes the honest answer to what the business actually wants. |
The default loss guarantee is where LSP deals get interesting, and it has moved. A DLG between an RE and an LSP is capped at 5% of the outstanding portfolio, must be backed by cash deposit, bank guarantee or lien-marked fixed deposit — a corporate guarantee is not eligible — and needs a board-approved policy at the RE plus explicit disclosure to the borrower that no service depends on it. In February 2026 the RBI allowed NBFCs to recognise DLG when computing Expected Credit Loss again, provided the DLG is integral to the loan structure rather than bolted on, with ECL recomputed whenever it is used or invoked. If your commercial model rests on a DLG, that change is the difference between the deal working and not.
Step 2 — Show the offer
# You have roughly 300ms at checkout. You also have a rule: do not show an
# offer you cannot honour. A declined customer at the payment screen is a lost
# sale AND a complaint, and "pre-approved" is a word with consequences.
def checkout_offer(customer, cart, limits):
# 1. HARD GATES first. Cheap, deterministic, no model involved.
if customer["age"] < 18: return decline("underage")
if not customer["kyc_complete"]: return decline("kyc_required")
if cart["amount_paise"] > limits["max_ticket_paise"]:
return decline("above_max_ticket")
if customer["active_loans"] >= limits["max_concurrent"]:
return decline("concurrent_limit")
if customer["dpd"] > 0: return decline("existing_arrears")
# 2. Only now, the risk decision -- INSIDE what the gates permit.
score = risk_model(customer, cart)
if score < limits["min_score"]: return decline("risk")
# 3. Language matters. "Eligible to apply" is a fact you can support.
# "Pre-approved" implies a sanction you have not made yet.
return {
"show": True,
"wording": "Eligible to apply", # NOT "pre-approved"
"tenures": [t for t in limits["tenures"] if cart["amount_paise"] >= t["min_paise"]],
"indicative_apr": indicative_apr(score), # INDICATIVE. The KFS is binding.
"quote_id": new_quote_id(), # everything downstream ties to this
"expires_at": now() + minutes(15),
}
def decline(reason):
# A decline at checkout is a product event, not an error. Never show the
# reason to the customer at this point -- but always store it.
return {"show": False, "reason": reason}
# WHAT TO CHECK
# [ ] hard gates run BEFORE the model. They are cheap, they are auditable, and
# they keep the model out of decisions that were never its to make
# [ ] the word on the button is a compliance decision. "Pre-approved" before a
# sanction is a promise. Get the wording signed off, not chosen by design
# [ ] indicative APR is labelled indicative EVERYWHERE, including in analytics.
# The KFS number is the binding one and they can differ
# [ ] quote_id threads through KFS, sanction, disbursal and repayment. When
# something goes wrong at step 7 you will reconstruct from this
# [ ] decline reasons are STORED even though they are not shown. The
# distribution is your best early signal that a gate is mis-set
# [ ] the offer expires. An indicative rate from three hours ago is not an offer
# [ ] latency budget is real: at 300ms, a model that takes 400ms is a decline
The gotcha nobody documents: the word on the button. “Pre-approved” reads as a decision already made, and a customer who is then declined has been told two different things by the same company. “Eligible to apply” is a statement you can support at every stage. It converts slightly worse and it removes a whole class of complaint. Treat the wording as a compliance artefact, get it signed off, and do not let it be A/B tested into something stronger.
Steps 3 and 4 — the KFS and the sanction
Step 3 — The Key Fact Statement
What it does: tells the customer, before they are committed, exactly what this will cost.
from decimal import Decimal, ROUND_HALF_UP
# The KFS is shown BEFORE sanction and it is the single source of truth. If an
# interface says 10% and the KFS says 14% APR, the KFS is what binds. EVERY fee
# goes in it -- processing, platform, insurance premium, anything.
def build_kfs(quote, loan, fees, tenure):
P = Decimal(loan["principal_paise"])
total_fees = sum(Decimal(f["paise"]) for f in fees)
# APR must include the fees. A rate quoted on principal alone while fees are
# charged separately is the oldest trick in consumer credit and it is
# exactly what the KFS exists to stop.
apr = compute_apr(principal=P, fees=total_fees,
instalments=tenure["instalments"],
frequency=tenure["frequency"])
return {
"quote_id": quote["quote_id"],
"lender_name": loan["regulated_entity"], # the RE, not your brand
"lsp_name": loan.get("lsp"), # named separately if there is one
"principal": P,
"fees": [{"name": f["name"], "amount": f["paise"]} for f in fees],
"total_fees": total_fees,
"apr_percent": apr.quantize(Decimal("0.01"), ROUND_HALF_UP),
"instalments": tenure["instalments"],
"instalment_amount": instalment_amount(P, total_fees, apr, tenure),
"total_repayable": P + total_fees + interest_total(P, apr, tenure),
"cooling_off_days": loan["cooling_off_days"],
"cooling_off_terms": "Exit by paying principal plus proportionate APR. "
"No prepayment penalty.",
"grievance_officer": loan["grievance_contact"],
"recovery_agent_policy_url": loan["recovery_policy_url"],
"issued_at": now_ist(),
}
# WHAT TO CHECK
# [ ] the KFS is rendered and ACKNOWLEDGED before sanction, not alongside it.
# Order matters and it is checkable from your own logs
# [ ] APR includes every fee. If a fee exists anywhere in the journey and is not
# in the KFS, the KFS is wrong
# [ ] the REGULATED ENTITY is named as the lender. A borrower who thinks your
# brand lent them the money cannot exercise their rights against the RE
# [ ] store the exact rendered KFS, not the inputs. "What did they see" is the
# question, and re-rendering from today's fee table will not answer it
# [ ] a changed credit limit or changed terms needs a FRESH KFS
# [ ] integer paise. Never floats. Rounding on an APR is a regulatory number
# [ ] the grievance route and the recovery-agent policy are on the document,
# not one click away
The gotcha nobody documents: store the rendered document, not the inputs. Teams store the loan parameters and plan to regenerate the KFS if anyone asks. Six months later the fee schedule has changed, the template has changed, and the regenerated document is not what the customer saw. The question is always “what did they see”, and only the artefact answers it. It is a few kilobytes. Keep it.
Step 4 — Sanction the loan
A fresh loan, for this purchase. Not a draw on a line, because a line loaded into a prepaid instrument is the model that was stopped in 2022.
Practically this means your sanction path runs inside the checkout, at checkout latency, for every single transaction — which is the main engineering difference between BNPL and ordinary lending. Budget for it: idempotency on the sanction call, a clear timeout policy, and a defined answer to “the sanction timed out and we do not know if it happened”. That is the same unknown-state problem as payouts in Build Sheet 05, and the same rule applies: a timeout is not a failure, it is a question you must go and ask.
Steps 5 and 6 — the money and the exit
Step 5 — Move the money
# THE RULE: disbursal and repayment flow between the BORROWER'S BANK ACCOUNT
# and the REGULATED ENTITY. Not through the LSP. Not through a platform account.
# Not through any intermediary "pool" account. There is no clever structure.
def disburse(sanction, merchant, re_client):
# The lender pays the merchant. You instruct; you do not hold.
instruction = {
"from": "REGULATED_ENTITY_ACCOUNT", # the RE's own account
"to": merchant["settlement_account"],
"amount_paise": sanction["principal_paise"],
"reference": sanction["loan_id"],
"idempotency_key": f"disb:{sanction['loan_id']}", # intent, not attempt
}
assert instruction["from"] != "PLATFORM_ACCOUNT", \
"money must not pass through the platform"
return re_client.disburse(instruction)
def collect(loan, instalment, re_client):
# Repayment goes borrower -> RE. An e-mandate on the borrower's account,
# presented by the RE. Again: you instruct, you do not receive.
return re_client.present_mandate({
"mandate_id": loan["mandate_id"],
"amount_paise": instalment["amount_paise"],
"due_date": instalment["due_date"],
"idempotency_key": f"coll:{loan['loan_id']}:{instalment['seq']}",
})
# WHAT TO CHECK
# [ ] there is NO account in your architecture that briefly holds borrower money.
# If one exists "for reconciliation convenience", that is the finding
# [ ] idempotency keys derive from the loan and instalment, never from a uuid4
# per attempt. Paying a merchant twice is recoverable; collecting twice from
# a borrower is a complaint and a refund and a trust problem
# [ ] a timeout on disbursal is UNKNOWN, not failed. Query by your own reference
# [ ] the merchant settlement account is verified before the first disbursal,
# not after the first misdirected payment
# [ ] mandate failures are a FIRST-CLASS path with their own state machine, not
# an exception. They are routine, not exceptional
# [ ] reconcile disbursals against sanctions daily. A disbursal with no sanction
# is the most serious break available here
The gotcha nobody documents: the pool account. Almost every payments architecture has one, because it makes reconciliation easier. In digital lending it is the thing that is specifically not allowed, and it is usually introduced by an engineer solving a genuine problem without knowing it is a regulated boundary. Put the constraint in code, as an assertion, on day one. It costs one line and it stops a design decision that is very expensive to unwind.
Step 6 — The cooling-off window
What it does: lets the borrower exit, paying principal plus proportionate APR only, with no prepayment penalty.
Three things teams get wrong:
- They make it hard to find. If exiting requires calling support, the window exists on paper only.
- They charge the processing fee anyway. Proportionate APR means proportionate. A fee retained on exit is the fee the window was meant to protect against.
- They forget the merchant side. If the customer exits the loan but the goods have shipped, somebody has to reconcile that — and it should be decided before launch, not during the first case.
Steps 7 and 8 — repayment and collections
Steps 7 and 8 — Repayment, and collections
from datetime import time, datetime, timedelta, timezone
IST = timezone(timedelta(hours=5, minutes=30))
# A failed instalment is routine. How you behave in the next 48 hours is where
# BNPL firms get into regulatory trouble.
def on_mandate_failure(loan, instalment, reason):
# 1. Distinguish CANNOT PAY from DID NOT PAY. They need opposite responses
# and the same dashboard treats them identically.
if reason in ("insufficient_funds",):
path = "retry_with_notice" # tell them BEFORE re-presenting
elif reason in ("mandate_revoked", "account_closed"):
path = "contact_required"
else:
path = "technical_retry" # our problem, not theirs
return {"path": path, "notify_before_retry": True,
"retry_not_before": datetime.now(IST) + timedelta(days=1)}
# Contact rules are HARD GATES, not guidance. Same shape as Build Sheet 06.
WINDOW = (time(8, 0), time(19, 0))
def may_contact(account, channel, now=None):
now = now or datetime.now(IST)
t = now.timetz().replace(tzinfo=None)
if not (WINDOW[0] <= t <= WINDOW[1]):
return {"allow": False, "why": "outside_0800_1900_IST"} # DIGITAL TOO
if account.grievance_open:
return {"allow": False, "why": "recovery_suspended_grievance_pending"}
if account.hardship_flag:
return {"allow": False, "why": "hardship", "action": "human_review"}
if account.contacts_today(channel) >= account.cap(channel):
return {"allow": False, "why": "frequency_cap"}
return {"allow": True, "must_record": channel == "voice"}
# WHAT TO CHECK
# [ ] the window covers SMS, WhatsApp and push, not just calls. The scheduler is
# where this is usually missing, because the dialler is the obvious place
# [ ] NEVER access contacts, call logs or media files. Prohibited outright.
# Camera, microphone and location need explicit consent and nothing else
# [ ] no social shaming, ever. Contacting anyone other than the borrower about
# the debt is where this stops being a compliance matter
# [ ] outsourcing collections does not outsource liability. Audit the agency's
# logs; an assurance is not evidence
# [ ] a hardship flag raised anywhere -- including in support -- pauses
# collections. Different vendors and different databases is the usual cause
# of a technically compliant message that is indefensible in substance
# [ ] all digital lending data stored EXCLUSIVELY IN INDIA
The gotcha nobody documents: the phone permissions. A lending app may request camera, microphone and location with explicit consent. Contacts, call logs and media files are prohibited outright — no consent makes them acceptable. This is not a privacy nicety; it is the specific abuse the rule was written to end, and an app that requests contacts is making a statement about itself that a supervisor will read exactly as intended.
What it costs
BNPL checkout — what it costs
Verified September 2026Model the unit economics on a COHORT, not a transaction. A BNPL transaction looks profitable on day one and is only actually profitable once that cohort has finished repaying. Firms that grew on transaction-level margin and discovered cohort-level losses twelve months later are the single most common failure pattern in this product category, in every market it has existed in.
Three versions you could build
The honest starting version
Build: LSP partnership with one regulated entity → hard gates plus a simple scorecard → a properly rendered and stored KFS → sanction per transaction → disbursal and collection through the RE → a cooling-off exit that a customer can find without calling anyone → collections by hand, inside the contact window, with everything logged.
You get: a compliant product you can learn from.
It breaks when: volume outruns manual collections — which happens sooner than you expect, because arrears arrive all at once.
The proper version
Build: everything above, plus — two REs so one partner's risk appetite does not cap your product → a real underwriting stack with challenger models → cohort-level unit economics reported weekly → a collections platform with the contact window enforced in code → a hardship flag shared between support and collections → the DLG modelled as tied-up capital rather than a marketing cost.
Trade: you own the risk model and the collections conduct. Both are things you cannot outsource the consequences of.
The version that owns the balance sheet
Build: your own NBFC, your own capital, your own reporting, plus everything above.
It breaks when: you do it before you have a year of repayment data. The licence is the easy part; knowing what your book actually does is not.
If you take one thing from this page: settle step 1 before you design step 2. NBFC or LSP changes the money flow, the KFS wording, who the borrower's rights run against, and who signs off on your button text. Teams that design the checkout first and pick the structure later rebuild the checkout.
What goes wrong
| What goes wrong | Why | Fix |
|---|---|---|
| The model is a credit line | It is the intuitive design and it is the one that was stopped. | Fresh sanction per transaction. |
| Money passes through a platform account | An engineer added a pool account to make reconciliation easier. | Assert against it in code on day one. |
| The KFS cannot be reproduced | Inputs were stored, not the document. | Store the rendered artefact. |
| “Pre-approved” then declined | Marketing wording on a pre-sanction screen. | “Eligible to apply”, signed off, not A/B tested. |
| Cooling-off exists but nobody uses it | It requires calling support. | Put it in the app, one tap, no fee retained. |
| An SMS goes out at 22:00 | The window was built on the dialler, not the scheduler. | Gate every channel, at send time, in IST. |
| The app asks for contacts | Copied from an older lending app. | Prohibited outright. Remove the permission. |
| Profitable per transaction, loss-making per cohort | Margin measured at sale, losses arrive later. | Cohort reporting from week one. |
Where to go next
Credit & Underwriting
The scorecard behind step 2, and the hybrid architecture that keeps the model inside the rules.
Payments & Reconciliation
Disbursal, e-mandates, and the timeout-is-not-a-failure rule step 4 depends on.
Customer Operations
Collections as regulated speech, with the contact guard written out in full.
Video KYC
How the customer at step 2 got verified in the first place.
This page is a guide, not a specification. Digital lending is a licensed activity and the conduct rules carry consequences well beyond a fine. Nothing here is legal advice. Have your structure, your KFS, your money flow and your collections process reviewed by qualified counsel before a real borrower sees a checkout button.
Sources
Every figure, rule and date on this page, and where to check it. Entries are typed so you can see which are primary-sourced and which are industry reporting.
- officialRBI Digital Lending Directions, 2025 (8 May 2025) — the consolidated framework replacing the 2022 guidelines, the default loss guarantee framework and digital-channel outsourcing rules. www.rbi.org.in
- officialRBI Master Direction on Prepaid Payment Instruments — the 2022 restriction that PPIs may not be loaded from credit lines — the rule that ended the original BNPL model. www.rbi.org.in
- officialRBI — Default Loss Guarantee framework — the 5% cap, the eligible forms of cover, the exclusion of corporate guarantees, and the February 2026 change allowing DLG in ECL for NBFCs where it is integral to the loan structure. www.rbi.org.in
- officialRBI — NBFC registration — the ₹2 crore minimum Net Owned Fund for new applications. www.rbi.org.in
- officialDPDP Act, 2023 — consent, purpose limitation, India-only storage and the penalty ceiling. www.meity.gov.in
- industryDigital lending compliance reporting — the KFS contents, cooling-off mechanics and permission restrictions as summarised by practitioners. Verify against the Directions before relying on any specific figure.
Checked September 2026. The DLG treatment changed in February 2026; the date is part of the claim.
Ask an AI about this page
Opens your assistant with this page as the source, and a question rather than a summary. It will ask what you are building before it answers.
Nothing is sent from here. The link carries only this page’s title and address.