Product Guide 05
Fintech AI

Recurring Payments: How to Build It

A step-by-step guide to building recurring payments on e-mandates in India. Eight stages, the options at each one, exactly how each step connects to the next, real costs, and what breaks. Written for someone who has not built this before.

Verified September 2026Free · No signupOfficial sources only
BeginnerStart here. No prior knowledge assumed.

How to use this page

This page walks you through building one capability, start to finish: charging a customer repeatedly, automatically, with their permission. Subscriptions, SIPs, insurance premiums, loan instalments.

It feeds every product that bills more than once.

Watch out

The rules changed on 21 April 2026 and most published guidance is out of date. The RBI's Digital Payments — E-mandate Framework, 2026 (Circular RBI/DPSS/2026-27/396) consolidated eight earlier circulars into one rulebook, effective immediately. If you are reading an integration guide that cites the 2019 or 2021 circulars as current, it is describing a framework that no longer exists.

What an e-mandate actually is

An e-mandate is a standing permission: the customer authenticates once, and you may then debit them repeatedly within agreed limits, without asking again each time.

The 2026 framework covers credit cards, debit cards, prepaid instruments and UPI, for domestic and cross-border recurring transactions. Three rails, one rulebook.

RailWhat it isBest for
Card e-mandateStanding instruction against a card.Subscriptions, international customers, higher ticket sizes.
UPI AutoPayMandate against a UPI ID, approved in the customer's UPI app.Consumer India. Highest reach, lowest friction to register.
eNACHStanding instruction registered directly against a bank account.Loan EMIs, larger amounts, customers without cards.
Note

UPI AutoPay runs under the same framework as card mandates. The pre-debit notification requirement and the AFA thresholds apply identically. The payment instrument changes; the compliance obligation does not. That matters when you build — one set of rules, three integrations, and it is tempting to build three different rule implementations by accident.

What it is not:

  • Not a stored card. A saved card you charge is not a mandate, and charging one recurringly without a registered mandate is the thing this framework exists to stop.
  • Not permanent. Every mandate has a validity period the customer can change.
  • Not retryable like a normal decline. See step 6 — this is the part teams get most wrong.

The whole journey, in one table

#StepIn plain words
1Pick the railCard, UPI AutoPay, or eNACH. Often more than one.
2Register the mandateOne authentication, up front. Get this right or nothing else runs.
3Store what you may debitAmount, cap, frequency, validity. This is your permission, in a row.
4Notify, 24 hours beforeMandatory. Amount, date, merchant name.
5Present the debitWith or without AFA, depending on amount and category.
6When it failsDiagnose the mode. Do not send everyone the same email.
7Confirm, afterPost-debit notification, with the grievance route on it.
8Modify, pause, revoke, reissueThe customer is in control, and cards get replaced.
Note

Steps 4, 6 and 7 are where the money is. Registration is a one-off engineering problem; collection is an operational one you will run forever, and the difference between a good and a bad implementation of step 6 is worth more than everything else on this page combined.

IntermediateBuild it. Pipelines, tools and working code.

Steps 1 to 3 — rails, registration and limits

Step 1 — Pick the rail

Most products end up on two. Cards for reach and international, UPI AutoPay for Indian consumer volume, eNACH for larger recurring amounts and customers without cards.

The useful design decision: build the rails behind one internal mandate model, so your business logic never branches on rail. The framework treats them identically; your code should too. Teams that build three separate implementations end up with three slightly different interpretations of the same rule, and the differences surface as compliance gaps rather than bugs.

Step 2 — Register the mandate

One AFA, up front. A 3DS flow on cards, a UPI PIN in the customer's UPI app, or the bank's flow for eNACH.

Two things worth building properly at registration, because retrofitting them is painful:

  • Capture the customer's preferred notification channel. They choose SMS or email, and you owe them a notification before every debit. Ask once, at registration.
  • Set the variable-amount cap explicitly. If the amount can change, the customer sets an upper limit. Make that a deliberate field in your flow rather than a default you chose.

Steps 3 and 5 — What you may debit

Python — steps 3 and 5, the limits, and the exception everyone gets wrong
from decimal import Decimal

# AFA (Additional Factor of Authentication -- usually OTP or UPI PIN) is
# ALWAYS required for: registration, modification, withdrawal, the FIRST
# transaction, a customer opt-out, and any debit above the threshold.
#
# After registration, recurring debits run without AFA UP TO A LIMIT.

GENERAL_LIMIT = Decimal("15000")      # per recurring transaction

# The higher limit applies to EXACTLY THREE CATEGORIES. This is the most
# misreported rule in the whole framework.
ENHANCED_LIMIT = Decimal("100000")
ENHANCED_CATEGORIES = {
    "INSURANCE_PREMIUM",
    "MUTUAL_FUND_SIP",
    "CREDIT_CARD_BILL",
}
# NOT INCLUDED: loan EMIs. Not personal loans, not BNPL instalments, not auto
# loans. An EMI above Rs 15,000 needs AFA on every single debit, and a lending
# product built on the assumption of Rs 1 lakh headroom will fail at collection
# for exactly the customers whose instalments matter most.

def needs_afa(mandate, amount: Decimal, is_first: bool) -> dict:
    if is_first:
        return {"afa": True, "why": "first_transaction"}
    limit = (ENHANCED_LIMIT if mandate["category"] in ENHANCED_CATEGORIES
             else GENERAL_LIMIT)
    if amount > limit:
        return {"afa": True, "why": "above_threshold", "limit": limit}
    if amount > mandate["max_amount"]:
        # The CUSTOMER's own cap, set at registration for variable amounts.
        # Separate from the regulatory limit and often lower.
        return {"afa": False, "blocked": True, "why": "exceeds_customer_cap"}
    return {"afa": False}

# WHAT TO CHECK
# [ ] ENHANCED_CATEGORIES is a closed set in code, not a config someone can
#     widen. Adding "LOAN_EMI" to it is a one-line compliance breach
# [ ] the customer's own cap is enforced separately from the regulatory limit,
#     and a debit exceeding it is BLOCKED, not escalated to AFA
# [ ] amounts in Decimal or integer paise. Never float
# [ ] "first transaction" means first under THIS mandate, not first ever for
#     this customer. A re-registered mandate has a new first transaction
# [ ] validity period is stored and enforced. A debit after expiry is
#     unauthorised, and customer liability rules then apply to you
# [ ] issuers may NOT charge the customer for the e-mandate facility. If a fee
#     appears anywhere in your flow, it is yours to absorb, not theirs
Watch out

The ₹1 lakh exception covers insurance premiums, mutual fund subscriptions and credit card bills. It does NOT cover EMIs. This is the single most misreported line in the framework, and it is repeated confidently in a lot of published guidance. A loan instalment above ₹15,000 requires AFA on every debit. If you are building lending collections on the assumption of ₹1 lakh of headroom, your collection rate will fall off a cliff at exactly the ticket sizes that matter most to the book.

Step 4 — the notification that is a gate

Step 4 — Notify, 24 hours before

Python — step 4, the notification that is a hard gate
from datetime import datetime, timedelta, timezone
IST = timezone(timedelta(hours=5, minutes=30))

# 24 hours before EVERY debit. Not a courtesy -- a condition of debiting.
# It must carry the amount, the debit date and the merchant name, and it goes
# on the channel the customer chose at registration.

NOTIFY_LEAD = timedelta(hours=24)
# The only carve-out: auto-replenishment of FASTag and NCMC balances.
EXEMPT = {"FASTAG_REPLENISH", "NCMC_REPLENISH"}

def schedule_pre_debit(mandate, instalment):
    if mandate["category"] in EXEMPT:
        return {"required": False}
    send_at = instalment["debit_at"] - NOTIFY_LEAD
    return {
        "required": True,
        "send_at": send_at,
        "channel": mandate["notify_channel"],        # chosen at registration
        "content": {
            "amount": instalment["amount"],
            "debit_date": instalment["debit_at"].astimezone(IST).date().isoformat(),
            "merchant_name": mandate["merchant_display_name"],
            "opt_out_url": opt_out_link(mandate, instalment),   # AFA-protected
        },
    }

def may_debit(instalment) -> dict:
    # The gate. A debit whose notification did not go out 24h earlier is not
    # a debit you are entitled to present.
    n = instalment.get("pre_debit_notification")
    if instalment["category"] in EXEMPT:
        return {"allow": True}
    if not n or n["status"] != "delivered":
        return {"allow": False, "why": "pre_debit_notification_not_delivered"}
    if instalment["debit_at"] - n["delivered_at"] < NOTIFY_LEAD:
        return {"allow": False, "why": "notification_too_late"}
    if instalment.get("customer_opted_out"):
        return {"allow": False, "why": "opted_out_for_this_debit"}
    return {"allow": True}

# WHAT TO CHECK
# [ ] may_debit() runs at PRESENTATION time, not at scheduling time. A
#     notification that failed to deliver overnight must stop the debit
# [ ] "delivered", not "sent". A queued SMS is not a notification
# [ ] the opt-out link is AFA-protected and works for a SINGLE debit as well as
#     the whole mandate. Both are required and teams usually build only the second
# [ ] the merchant name in the notification is the name the customer RECOGNISES,
#     not your legal entity. Unrecognised names drive chargebacks and complaints
# [ ] a moved debit date needs a fresh 24-hour notification. Rescheduling does
#     not inherit the old one
# [ ] notification delivery is logged per instalment. In a dispute this is the
#     evidence that you were entitled to debit at all

The gotcha nobody documents: the notification is a gate, not a message. Teams build it as a fire-and-forget alert on the scheduler and never wire the result back into the debit decision. If the SMS bounced, the number changed, or the queue backed up overnight, the debit should not go out — and in a dispute the delivery log is what proves you were entitled to present it. Check delivery at presentation time, not at scheduling time.

Steps 6 to 8 — failure, confirmation and control

Step 6 — When it fails

Python — step 6, the part that decides your recovery rate
# A mandate failure is NOT a retryable soft decline. Retrying it is pointless:
# the mandate itself is the problem, and only the customer can fix it.
#
# THREE FAILURE MODES, THREE DIFFERENT CUSTOMER ACTIONS. Sending the same
# "update your payment method" email to all three routes people to the wrong
# action and collapses recovery.

RECOVERY = {
    "mandate_missing_or_cancelled": {
        "customer_action": "re-register the mandate",
        "how": {"CARD": "3DS authentication", "UPI": "approve in your UPI app",
                "ENACH": "re-authorise with your bank"},
        "retryable_without_customer": False,
    },
    "above_threshold": {
        "customer_action": "approve this one payment",
        "how": {"CARD": "OTP for this transaction", "UPI": "UPI PIN for this transaction",
                "ENACH": "authorise this debit"},
        "retryable_without_customer": False,
        "note": "the mandate is fine; this single amount needs AFA",
    },
    "pre_debit_notification_failed": {
        "customer_action": "confirm in your banking app before the next attempt",
        "retryable_without_customer": False,
    },
    "insufficient_funds": {
        "customer_action": "add funds",
        "retryable_without_customer": True,        # the ONLY genuinely retryable one
        "retry_after_days": 3,
    },
}

def recovery_plan(failure, mandate):
    plan = RECOVERY.get(failure["code"])
    if not plan:
        return {"path": "human_review", "code": failure["code"]}
    msg = plan["customer_action"]
    how = plan.get("how", {}).get(mandate["rail"])
    return {"tell_customer": f"{msg}" + (f" — {how}" if how else ""),
            "auto_retry": plan["retryable_without_customer"],
            # If card re-registration goes unanswered, offer UPI AutoPay. It is
            # a lower-friction registration and recovers subscribers that a
            # second 3DS attempt will not.
            "fallback_rail": "UPI" if mandate["rail"] == "CARD" else None}

# WHAT TO CHECK
# [ ] mandate failures are a DISTINCT failure class in your dunning logic, not
#     lumped in with card declines. They behave nothing alike
# [ ] the message names the specific action. "Update your payment method" is
#     wrong for two of the three modes
# [ ] auto-retry ONLY on insufficient funds, and with notice before re-presenting
# [ ] offer UPI AutoPay as a fallback when card re-registration goes unanswered.
#     Different friction, different success rate, same framework
# [ ] measure recovery rate PER FAILURE MODE. A blended number hides that one of
#     the three is broken
# [ ] a failure never silently stops the subscription. It starts a defined
#     sequence with an end state

The gotcha nobody documents, and the most expensive one on this page: a mandate failure is not a soft decline. Ordinary card declines are retryable — the issuer might approve tomorrow. A mandate failure means the permission is broken, and no number of retries will fix it. Only the customer can, and the action they must take is different in each of the three modes. Most billing systems send one generic email to all of them, which routes people to the wrong action and quietly destroys the recovery rate. Splitting that one message into three is probably the highest-return change available in this entire product area.

Step 7 — Confirm, after

A post-debit notification after every automated collection, carrying the grievance redressal route. The framework is explicit that the grievance mechanism must be disclosed in the notification and must actually work for recurring-transaction disputes — not a generic support link.

The RBI's customer-liability rules for unauthorised transactions apply here too. A debit the customer did not authorise is not a billing dispute; it is an unauthorised transaction with clocks attached.

Step 8 — Modify, pause, revoke, reissue

EventWhat must be possible
ModifyAmount cap or validity period, by the customer, with AFA.
PauseWithout cancelling. Build it; customers who cannot pause, cancel.
RevokeAt any point, with AFA. Immediate effect on the next debit.
Opt out of one debitA single skipped payment, not the whole mandate. Frequently missed.
Card reissuedIssuers may map existing card e-mandates to the reissued card — which removes the old problem of every mandate silently lapsing when a card expired.
Note

Card reissue mapping is quietly one of the most valuable changes in the 2026 framework. Mandate churn on card expiry used to be a large, invisible source of involuntary subscriber loss — the customer never chose to leave, the card simply expired. Ask your acquirer whether they support the mapping, because it is the difference between losing a cohort every three years and not.

What it costs

Recurring payments — what it costs

Verified September 2026
The facility, to the customerdirect
Nothing. Issuers are prohibited from charging customers for using an e-mandate. If a fee exists in your flow, you absorb it.
Per transactiondirect
Ordinary payment processing, per successful debit. See Build Sheet 05 for the rails and their rates.
Mandate registrationdirect
Sometimes a separate small fee per registered mandate, sometimes bundled. Worth asking, because a high-churn product registers far more mandates than a stable one.
Notificationsdirect
Two per debit cycle, minimum — one 24 hours before, one after. On SMS at scale this is a real line, and it is proportional to your billing frequency rather than your revenue.
Failure handlingdirect
The line that decides the economics. Recovery is customer-action-dependent in three of four failure modes, so it costs communication and time rather than compute. Getting it right is worth more than any per-transaction saving.
Building three railsdirect
Card, UPI AutoPay and eNACH are three integrations under one rulebook. Budget three integrations and one rule engine — not three of each.
Where to buy these: Payments Reconciliation Build Sheet names every tool with its unit cost. Getting Access covers which ones you can sign up for today, which need a sales call, and which are licensed.
Watch out

Model involuntary churn, not just failed debits. A failed instalment that is never recovered is a lost customer who did not choose to leave. Measure recovery rate per failure mode and involuntary churn separately from voluntary cancellation — most billing dashboards collapse all three into “churn” and hide the one you can actually fix.

AdvancedShip it. Failure modes, thresholds and evidence.

Three versions you could build

The starting version

Build: one rail, usually UPI AutoPay → registration with AFA → a mandate row storing category, cap, frequency and validity → a 24-hour notification job with delivery checked at presentation → post-debit confirmation with the grievance route → three distinct failure messages.

It breaks when: customers arrive without UPI, or ticket sizes cross ₹15,000 in a category without the exception.

The proper version

Build: everything above, plus — two or three rails behind one internal mandate model → the enhanced-category list as a closed set in code → the customer cap enforced separately from the regulatory limit → single-debit opt-out as well as full revocation → pause without cancel → card-reissue mapping confirmed with your acquirer → recovery rate measured per failure mode → UPI AutoPay offered as a fallback when card re-registration goes unanswered.

Trade: the rule engine is the asset. Three rails is three integrations; three interpretations of the rules is a compliance gap waiting to be found.

The version at scale

Build: everything above, plus — intelligent retry timing on the one genuinely retryable mode → notification channel optimisation → cohort-level involuntary churn reporting → a dunning sequence that escalates across rails rather than repeating on one.

Note

If you take one thing from this page: split your dunning message into three. Cancelled mandate, above-threshold, and notification failure need three different customer actions, and one generic email serves none of them. It is a week of work and it moves recovery more than any other change available here.

What goes wrong

What goes wrongWhyFix
EMI collections fail above ₹15,000The ₹1 lakh exception was assumed to cover loans.It covers insurance, mutual funds and credit card bills only.
Recovery rate is poorOne generic dunning email for three different failure modes.Three messages, three actions.
Debits presented without notificationThe notification is fire-and-forget on the scheduler.Check delivery at presentation time.
Chargebacks from unrecognised namesThe legal entity name in the notification.Use the name the customer recognises.
Mandates lapse on card expiryReissue mapping not enabled.Ask the acquirer. It is supported now.
Customers cancel instead of pausingThere is no pause.Build pause. Cancelling is a decision they cannot undo easily.
Three rails, three rule implementationsEach integration was built by itself.One mandate model, three adapters.
A debit after expiryValidity stored but not enforced.Enforce it. It is an unauthorised transaction.

Where to go next

Watch out

This page is a guide, not a specification. Recurring debits move customer money on a standing permission, and presenting one you are not entitled to is an unauthorised transaction rather than a billing error. Nothing here is legal advice. Have your mandate model, notification logic and dunning sequence reviewed by qualified counsel and your payment partner before the first live debit.

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.

  1. officialRBI — Digital Payments – E-mandate Framework, 2026 (Circular RBI/DPSS/2026-27/396, 21 April 2026) — the consolidated rulebook replacing eight earlier circulars: AFA triggers, the ₹15,000 general limit, the ₹1 lakh enhanced limit and its three categories, the 24-hour pre-debit notification, post-debit confirmation, revocation, the prohibition on customer charges, card-reissue mapping, and acquirer responsibility for merchant compliance. www.rbi.org.in
  2. officialRBI — customer liability in unauthorised electronic transactions — the liability framework that applies to recurring debits the customer did not authorise. www.rbi.org.in
  3. officialNPCI — UPI AutoPay — the UPI mandate rail, registration in the customer’s UPI app, and the mandate lifecycle. www.npci.org.in
  4. officialNPCI — NACH — the eNACH rail for bank-account standing instructions. www.npci.org.in
  5. industryPayment provider and billing-platform reporting — the failure-mode taxonomy and recovery guidance in step 6, and the observation that a single generic dunning message collapses recovery. Practitioner-sourced; test against your own cohort.
  6. industryFramework commentary, April–June 2026 — summaries of the 2026 framework used to cross-check the limits and exemptions. Verify any specific figure against the circular before relying on it.

Checked September 2026. This framework was rewritten in April 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.