> ## Documentation Index
> Fetch the complete documentation index at: https://razorpay.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# UPI Autopay Intent: How to Test Your Integration

> Test UPI Autopay mandate registration and subsequent debits through Intent on your S2S integration in Test Mode.

Available in
🇮🇳 India

Register mandates, run auto-debits, simulate failures and manage tokens in Test Mode, then work through the pre-launch checklist before you go live.

Use Test Mode to check your UPI Autopay integration before you go live. Test Mode simulates the responses from NPCI and the customer's bank, so you can register mandates, run auto-debits and handle failures without moving real money or using a UPI app. This page describes how to test the request and response payload across each API call, and what error scenarios to expect.

**UPI Autopay flows:** Intent · [QR](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/recurring/upi-autopay-qr.md) · [Collect](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/recurring/upi-autopay-collect.md)

Start with the [overview](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s.md) for test keys and webhook setup.

## 1. Test Mode Coverage

### How live mode and test mode differ

| Behaviour | Live mode | Test mode |
| - | - | - |
| Intent link | Opens the customer's UPI app on the same phone with the mandate pre-filled | An intent URL is returned so you can see the response shape, but no request reaches a UPI app. The amount decides the outcome. |
| App detection (Smart Intent) | Your app lists the UPI apps installed on the device so the payer can pick one, and you hand off to the one they choose with the mandate pre-filled. | Not part of Test Mode (client-side). There is no simulator UPI app to detect or hand off to, so build this and check it on real Android and iOS devices. |
| Mandate approval by the customer | The customer is deep-linked into their UPI app from the intent link, and approves the mandate there with their UPI PIN. | The simulator approves or declines on its own. The intent link does not need to be opened, and the outcome is decided by the amount you send rather than by anything the customer does. |
| Auto-debit timing and the pre-debit notification window | Razorpay sends the pre-debit notification first. NPCI requires at least 24 hours between the notification and the debit, and Razorpay schedules the debit 25 hours after it. The payment sits in `created` until then. | Razorpay sends the pre-debit notification and the debit together in Test Mode, without waiting out the actual window. The debit is attempted as soon as you create the payment rather than being scheduled, so the payment reaches `captured` or `failed` within the same test run instead of a day later. Do not use Test Mode to benchmark your end-to-end debit timing or size your reconciliation windows. Coming soon: a gap between the pre-debit notification and the debit, so Test Mode mirrors the live sequence. |
| Pre-debit notification webhooks | A real-time call is made with the gateway to send a notification to the customer about the upcoming debit. The `order.notification.delivered` and `order.notification.failed` webhooks fire depending on whether that gateway call succeeds. Note the `order.` prefix on both. | The notification itself is simulated, and in Test Mode it always succeeds. Neither `order.notification.delivered` nor `order.notification.failed` is sent, so subscribe to both and verify them against live traffic. See 3.4 for the notification failure scenarios that are coming soon. |
| Automatic retries of a failed auto-debit | Razorpay retries a failed attempt on a pre-debit notification, following the retry template configured on your account: the default 1-hour template retries twice, the 3-hour and 24-hour templates retry three times, and some accounts are on no retry. One notification covers one debit attempt plus its retries, and only one debit can succeed against it. | There are no retries in Test Mode. A failed debit stays `failed`, and the `payment.failed` webhook is sent to you immediately, whatever retry configuration your account is on. |
| Who performs the retry | On the coupled flow, Razorpay. The retries are internal, run against the same pre-debit notification, and need no code from you. On the decoupled flow there are no Razorpay retries, so recovery is yours to build. | Neither flow retries automatically in Test Mode. On the decoupled flow you can still exercise your own recovery logic: create a fresh payment on the same `order_id` — new payment ID, same notification — and it is accepted. Once one debit has succeeded against that order, any further attempt returns `mandate_already_executed`. |
| Customer cancels, pauses or resumes the mandate | The customer does this from their UPI app. Razorpay sends the `token.cancelled` or `token.paused` webhook, and approving a change to a mandate follows the same route. | Coming soon. These customer actions are not simulated yet, and cancelling a token through the API is coming soon too. Fetching a token and fetching all tokens for a customer do work in Test Mode. |
| Mandate registration through a Registration Link | Razorpay hosts the registration page and the customer authorises asynchronously. | Coming soon. Only the API flow (`POST /v1/payments/create/upi`) is available in Test Mode today. |
| Webhook delivery | Razorpay sends the webhook once the gateway has authorised the payment. That round trip is real, so delivery can take up to 12 minutes and your reconciler has to allow for it. | The gateway is simulated, so the webhook is sent as soon as the create-payment request succeeds, almost instantly. Do not use Test Mode to size your reconciliation windows or to judge how long to wait before polling. |

### What you can test

| Area | What you can test |
| - | - |
| Mandate registration | Successful registration on the Intent flow, and its failure scenarios, using the amounts in the scenarios below. |
| Auto-debits | Successful debits on both the coupled and the decoupled flow, and failure scenarios, using the amounts in the scenarios below. |
| Token management | Fetching a token and fetching all tokens for a customer. |
| Webhooks | Payment, order and token webhooks for each of the above, delivered to the URL configured in your Test Mode Dashboard. See below for which events fire today. |

### Endpoint coverage in test mode

| API | Endpoint | Available on test mode |
| - | - | - |
| Create a customer | `POST /v1/customers` | Supported |
| Create an order | `POST /v1/orders` | Supported |
| Create an authorisation payment | `POST /v1/payments/create/upi` | Supported on the Intent flow |
| Create a recurring payment | `POST /v1/payments/create/recurring` | Supported |
| Fetch a payment | `GET /v1/payments/:payment_id` | Supported |
| Fetch a token by payment ID | `GET /v1/payments/:payment_id` | Supported |
| Fetch a token | `GET /v1/customers/:customer_id/tokens/:token_id` | Supported |
| Fetch all tokens for a customer | `GET /v1/customers/:customer_id/tokens` | Supported |
| Cancel a token | `PUT /v1/customers/:customer_id/tokens/:token_id/cancel` | Coming soon |
| Delete a token | `DELETE /v1/customers/:customer_id/tokens/:token_id` | Coming soon |
| Create a registration link | `POST /v1/subscription_registration/auth_links` | Coming soon |

### Webhooks: what fires, and what it lets you decide

Every webhook UPI Autopay fires in live mode is listed below, along with whether it also fires in test mode. Test mode webhooks have the same shape as live mode ones and are delivered to the URL set in your Test Mode Dashboard.

| Webhook | Fires when | In test mode |
| - | - | - |
| `payment.authorized` | The registration or debit payment is authorised. Carries the token ID. | Yes |
| `payment.captured` | The payment is captured, when the order was created with `payment_capture` set to true. | Yes |
| `payment.failed` | The registration or debit fails. | Yes |
| `order.paid` | The order is fully paid. | Yes |
| `token.confirmed` | The mandate is registered and the token is active. | Yes |
| `token.rejected` | Registration failed, so the token was never confirmed. | Yes |
| `token.cancellation_initiated` | A cancellation has been requested and is awaiting the gateway callback. | Coming soon |
| `token.cancelled` | The cancellation is confirmed and the token is closed. | Coming soon |
| `token.paused` | The customer paused the mandate from their UPI app. | Coming soon |
| `order.notification.delivered` | The pre-debit notification reached the customer. | Coming soon |
| `order.notification.failed` | The pre-debit notification could not be delivered. | Coming soon |
| `refund.processed` | A refund on a captured debit has left Razorpay. | Available |
| `refund.failed` | A refund could not be processed. | Coming soon |

**Which webhooks each scenario fires**

| Scenario | Webhooks | In test mode |
| - | - | - |
| Registration succeeds | `payment.authorized`, `payment.captured`, `order.paid`, `token.confirmed` | Available |
| Registration fails | `payment.failed`, `token.rejected` | Available |
| Registration rejected before the payment is created, for example a validation failure | None. The request is rejected before a payment exists, so no webhook fires. Read the API error response instead. | Available |
| Registration through a Registration Link | None | Coming soon |
| Debit succeeds | `payment.captured`, `order.paid` | Available |
| Debit fails | `payment.failed` | Available |
| Token cancelled | `token.cancellation_initiated`, then `token.cancelled` once the gateway confirms | Coming soon |

> [!NOTE]
>
> **Watch out.** Note the `order.` prefix. The pre-debit notification events are named `order.notification.delivered` and `order.notification.failed`, not `notification.delivered` and `notification.failed`. We recommend subscribing to them and verifying them against live traffic, since neither fires in Test Mode yet.

> [!NOTE]
>
> **Handy tip.** A webhook can be delivered more than once. Use the event ID to make sure you process each event only once. This applies in live mode as well.

### Token states

Charge a token only while it is `confirmed`.

| State | Means | Can you debit? | Webhook | Available on test mode |
| - | - | - | - | - |
| `initiated` | Registration request sent to the customer's UPI app | No | — | Supported |
| `confirmed` | Mandate approved (or resumed) | Yes | `token.confirmed` | Supported |
| `rejected` | The customer declined, or registration failed | No | `token.rejected` | Supported |
| `paused` | The customer paused the mandate in their UPI app | No | `token.paused` | Coming soon |
| `cancellation_initiated` | You called Cancel Token; waiting for confirmation | No | — | Coming soon |
| `cancelled` | Cancelled by the customer or by you | No | `token.cancelled` | Coming soon |

## 2. Before you begin

* Generate your test mode API keys from the Dashboard. Keys that start with `rzp_test_` send all requests to test mode. You do not need to enable any separate flag.
* Use the same APIs and request formats you use in live mode. Only the API keys change.
* Pass every amount in paise, as you do in live mode. For example, send `201` for ₹2.01 and `100` for ₹1.
* Configure a webhook URL in your test mode Dashboard to receive test events. Test mode webhooks are separate from live mode ones and do not carry over.

In test mode the amount you send decides the outcome, because there is no real bank on the other end. Send `100` (₹1) and the payment succeeds. Send one of the reserved amounts and the payment fails with the error mapped to that amount.

* Send `100` (₹1) on both the order and the payment to succeed.
* Send a reserved amount from [3.3 Autopay error scenarios](#3-3-autopay-error-scenarios) on both calls to fail. For debits, keep it below the mandate's `max_amount`.
* You don't need to open the intent link or approve anything in a UPI app.
* Any amount that is not in the reserved list succeeds.

**Coupled or decoupled?** Both debit flows are covered below as scenarios — scenario 2 (coupled) and scenario 4 (decoupled). The flows themselves, the `notification` object and who owns the retries are defined in the published integration doc: [Subsequent Payments](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/recurring-payments/upi/subsequent-payments.md). This page does not redefine them.

## 3. Scenarios to Test Your Integration

This section lists every scenario we think is worth running against your UPI Autopay Intent integration before you go live. They are grouped by what each one proves, rather than by how urgent they are, so that you can work down the page in order and know that you have covered the integration end to end.

**Must-have.** We highly recommend running these scenarios first. They cover the successful path and the handful of outcomes you are almost certain to meet once you are live, so getting through them is the shortest route to an integration you can trust.

**Other common scenarios.** These come up in normal day-to-day running and are shared across one-time UPI and UPI Autopay, so the same handling has to work on both. They are not errors as such, but your integration still needs to recognise them and respond correctly, so we recommend covering them before launch.

**Error scenarios.** These are failures you can bring on deliberately, using a reserved trigger, so that you can check your error handling without waiting for a real failure to happen. Most rows use a single trigger, which keeps the error you get back unambiguous.

**Not available in test mode yet.** These scenarios are real and your integration still has to handle them, but they cannot be reproduced in test mode today. We recommend reading through them and checking your handling on live traffic or in a controlled live test before you ship.

### 3.1 Must-build Autopay scenarios

<table>
  <colgroup>
    <col width="6%" />

    <col width="18%" />

    <col width="14%" />

    <col width="62%" />
  </colgroup>

  <thead><tr><th>#</th><th>Scenario</th><th>Trigger creds</th><th>Steps to follow</th></tr></thead>

  <tbody>
    <tr><td>1</td><td>Register a mandate</td><td>`amount: 100`</td><td>Create a customer (`POST /v1/customers`) and store the returned `customer_id`. Create an order (`POST /v1/orders`) with `method: "upi"`, the `customer_id` and a `token` object, using the trigger amount. Create the authorisation payment (`POST /v1/payments/create/upi`, `recurring: 1`) with the same amount. `upi.flow: "intent"`. Read the intent link (no need to open it in test mode). Webhooks received: `payment.authorized`, `payment.captured`, `order.paid`, `token.confirmed`. Status changes: the payment moves to `captured` and the token to `confirmed`. Store the token ID carried on `payment.authorized`. On the order and the payment.</td></tr>
    <tr><td>2</td><td>Subsequent debit succeeds</td><td>`amount: 100`</td><td>Register a mandate (#1). Create a debit order without the `notification` object, which is what places the payment on the coupled flow, and a recurring payment (`POST /v1/payments/create/recurring`) with the trigger amount. Confirm `payment.captured` and `order.paid`. Token stays `confirmed`.</td></tr>
    <tr><td>3</td><td>Debit fails: insufficient funds</td><td>`amount: 201`</td><td>Register a mandate (#1). Create a debit order and a recurring payment (`POST /v1/payments/create/recurring`) with the trigger amount. Confirm `payment.failed` with `insufficient_funds`. Token stays active.</td></tr>
  </tbody>
</table>

### 3.2 Common scenarios in UPI

<table>
  <colgroup>
    <col width="6%" />

    <col width="18%" />

    <col width="14%" />

    <col width="62%" />
  </colgroup>

  <thead><tr><th>#</th><th>Scenario</th><th>Trigger creds</th><th>Steps to follow</th></tr></thead>

  <tbody>
    <tr><td>4</td><td>Debit with your own pre-debit notification</td><td>`amount: 100`</td><td>Register a mandate (#1). Create the debit order (`POST /v1/orders`) with the `notification` object, setting `notification.token_id` and `notification.payment_after`. Create the recurring payment (`POST /v1/payments/create/recurring`) with the same amount, passing `order_id`, `customer_id`, `token` and `recurring: true`. Confirm `payment.captured`.</td></tr>
    <tr><td>5</td><td>Fetch tokens</td><td>A sample token ID</td><td>Take a sample token ID from a mandate you registered earlier. `GET /v1/customers/:customer_id/tokens/:token_id` and `GET /v1/customers/:customer_id/tokens`. Confirm `status`, `max_amount`, `expire_at`, `frequency`.</td></tr>
    <tr><td>6</td><td>Fetch a payment and reconcile (also on one-time UPI)</td><td>A payment ID from the registration in scenario 1 and one from any row in 3.3</td><td>Poll `GET /v1/payments/:payment_id` for each payment. Confirm the status your reconciler reads matches the status the webhook carried. Confirm a payment still in `created` is never treated as final.</td></tr>
    <tr><td>7</td><td>Full and partial refund (also on one-time UPI)</td><td>A captured debit payment from scenario 2</td><td>Refund in full with `POST /v1/payments/:payment_id/refund`, and refund another captured debit in part. Webhooks received: `refund.processed`. Confirm the mandate is untouched. A refund does not cancel the token, and the next debit still runs.</td></tr>
    <tr><td>8</td><td>API validation errors (also on one-time UPI)</td><td>Missing `amount`, `amount: 0`, wrong key secret</td><td>Send each bad request to the order and the payment endpoints. Confirm HTTP 4xx, no payment created and no webhook. Read the API error response rather than waiting for an event, because none fires.</td></tr>
  </tbody>
</table>

### 3.3 Autopay error scenarios

**Registration**

These happen while the customer is setting the mandate up, so the token never reaches `confirmed` and no debit is possible. In each case the customer has to start the registration again.

<table>
  <colgroup>
    <col width="5%" />

    <col width="18%" />

    <col width="10%" />

    <col width="22%" />

    <col width="45%" />
  </colgroup>

  <thead><tr><th>#</th><th>Scenario</th><th>Trigger creds</th><th>Error object</th><th>Steps to follow</th></tr></thead>

  <tbody>
    <tr><td>9</td><td>Customer cancels or declines the mandate request</td><td>`amount: 302`</td><td>`code` BAD\_REQUEST\_ERROR\
    `source` internal\
    `step` payment\_response\
    `reason` mandate\_cancelled\_by\_user</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Read the intent link (no need to open it in test mode). Confirm `payment.failed` and `token.rejected`. Let the customer restart.</td></tr>
    <tr><td>10</td><td>Customer declines the mandate request — `mandate_cancelled_by_user`, source: customer</td><td>`amount: 304`</td><td>`code` GATEWAY\_ERROR\
    `source` customer\
    `step` payment\_authentication\
    `reason` mandate\_cancelled\_by\_user</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Read the intent link. Confirm `payment.failed` and `token.rejected`. Let the customer restart.</td></tr>
    <tr><td>11</td><td>Customer never approves it and the request expires</td><td>`amount: 301`</td><td>`code` GATEWAY\_ERROR\
    `source` customer\
    `step` payment\_authentication\
    `reason` mandate\_collect\_request\_expired</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Read the intent link. Confirm `payment.failed` and `token.rejected`. Let the customer restart.</td></tr>
    <tr><td>12</td><td>Registration fails: insufficient funds</td><td>`amount: 201`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_response\
    `reason` insufficient\_funds</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm `insufficient_funds`.</td></tr>
    <tr><td>13</td><td>Registration fails: remitter account inactive or dormant</td><td>`amount: 203`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_authentication\
    `reason` account\_state\_forbidden</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm `remitter_account_dormant` (source: customer, step: payment\_debit\_request).</td></tr>
    <tr><td>14</td><td>Registration fails: wrong UPI PIN</td><td>`amount: 204`</td><td>`code` GATEWAY\_ERROR\
    `source` customer\
    `step` payment\_initiation\
    `reason` incorrect\_pin\_entered</td><td>Create a customer, an order and the authorisation payment with the same amount. Confirm `incorrect_pin` (step: payment\_authentication) and `token.rejected`. Let the customer retry.</td></tr>
    <tr><td>15</td><td>Registration declined by the customer's bank</td><td>`amount: 402`</td><td>—</td><td>Register as above with the trigger amount. Confirm `payment_declined` (source: remitter\_bank) and `token.rejected`. Offer another account or method — retrying the same one will decline again.</td></tr>
    <tr><td>16</td><td>Duplicate registration request — `duplicate_request`</td><td>`amount: 406`</td><td>`code` BAD\_REQUEST\_ERROR\
    `source` internal\
    `step` payment\_request\
    `reason` duplicate\_request</td><td>Register as above with the trigger amount, sending it on both the order and the authorisation payment. Confirm 400 Bad Request with `duplicate_request` (source: internal, step: payment\_request) and the description "The request is duplicate". Do not retry the same request. If the earlier payment succeeded no action is needed, otherwise start a fresh registration.</td></tr>
    <tr><td>17</td><td>Bank technical error — "Your bank is temporarily unavailable. Please try again later or use a different account."</td><td>`amount: 102`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_response\
    `reason` bank\_technical\_error</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm the error object above, and that the token ends at `rejected`.</td></tr>
    <tr><td>18</td><td>Timeout at remitter bank — "Your bank took too long to respond. Please try again."</td><td>`amount: 104`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_response\
    `reason` timeout\_at\_remitter\_bank</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm the error object above, and that the token ends at `rejected`.</td></tr>
    <tr><td>19</td><td>Bank technical error — "UPI Autopay setup couldn't be completed due to a temporary issue. Any debited amount will be refunded in 4-5 business days."</td><td>`amount: 107`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_authentication\
    `reason` bank\_technical\_error</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm the error object above, and that the token ends at `rejected`.</td></tr>
    <tr><td>20</td><td>Customer invalid mobile number — "Your mobile number has been changed or removed from this account. Please update your details or use a different account."</td><td>`amount: 202`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_debit\_response\
    `reason` customer\_invalid\_mobile\_number</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm the error object above, and that the token ends at `rejected`.</td></tr>
    <tr><td>21</td><td>Customer mpin not set — "UPI PIN is not set for this account. Please set up your UPI PIN and try again."</td><td>`amount: 205`</td><td>`code` GATEWAY\_ERROR\
    `source` customer\
    `step` payment\_debit\_response\
    `reason` customer\_mpin\_not\_set</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm the error object above, and that the token ends at `rejected`.</td></tr>
    <tr><td>22</td><td>Incorrect pin entered — "You've exceeded the number of PIN attempts. Please wait and try again later, or contact your bank if you've forgotten your PIN."</td><td>`amount: 206`</td><td>`code` GATEWAY\_ERROR\
    `source` customer\
    `step` payment\_response\
    `reason` incorrect\_pin\_entered</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm the error object above, and that the token ends at `rejected`.</td></tr>
    <tr><td>23</td><td>Transaction amount limit exceeded — "This amount is above your transaction limit. Please try a lower amount or contact your bank for assistance."</td><td>`amount: 208`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_initiation\
    `reason` transaction\_amount\_limit\_exceeded</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm the error object above, and that the token ends at `rejected`.</td></tr>
    <tr><td>24</td><td>Transaction amount limit exceeded — "This amount is above your transaction limit. Please try a lower amount or contact your bank for assistance."</td><td>`amount: 209`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_authentication\
    `reason` transaction\_amount\_limit\_exceeded</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm the error object above, and that the token ends at `rejected`.</td></tr>
    <tr><td>25</td><td>Transaction frequency limit exceeded — "You've exceeded the number of allowed transactions on this account. Please try again later or contact your bank."</td><td>`amount: 210`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_response\
    `reason` transaction\_frequency\_limit\_exceeded</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm the error object above, and that the token ends at `rejected`.</td></tr>
    <tr><td>26</td><td>Account state blocked — "Your account is blocked or frozen. Please contact your bank to resolve this."</td><td>`amount: 212`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_status\_request\
    `reason` account\_state\_blocked</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm the error object above, and that the token ends at `rejected`.</td></tr>
    <tr><td>27</td><td>Beneficiary account not found — "Payment authorisation failed due to a technical issue. Please try again or contact your bank if this continues."</td><td>`amount: 213`</td><td>`code` BAD\_REQUEST\_ERROR\
    `source` business\
    `step` payment\_response\
    `reason` beneficiary\_account\_not\_found</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm the error object above, and that the token ends at `rejected`.</td></tr>
    <tr><td>28</td><td>Invalid VPA — "Invalid UPI ID entered. Please enter a valid UPI ID and retry."</td><td>`amount: 401`</td><td>`code` GATEWAY\_ERROR\
    `source` customer\
    `step` payment\_debit\_request\
    `reason` invalid\_vpa</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm the error object above, and that the token ends at `rejected`.</td></tr>
    <tr><td>29</td><td>Risk declined by remitter bank — "Autopay setup was blocked for security reasons. Please contact your bank or use a different account."</td><td>`amount: 404`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_response\
    `reason` risk\_declined\_by\_remitter\_bank</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm the error object above, and that the token ends at `rejected`.</td></tr>
    <tr><td>30</td><td>Risk declined by remitter bank — "Autopay setup was declined for security reasons. Please contact your bank for assistance."</td><td>`amount: 405`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_initiation\
    `reason` risk\_declined\_by\_remitter\_bank</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm the error object above, and that the token ends at `rejected`.</td></tr>
    <tr><td>31</td><td>Customer PSP fingerprint mismatch — "Payment was declined for security reasons. Please contact your bank for more details."</td><td>`amount: 407`</td><td>`code` GATEWAY\_ERROR\
    `source` customer\
    `step` payment\_response\
    `reason` customer\_psp\_fingerprint\_mismatch</td><td>Create a customer, an order and the authorisation payment as in #1, using the trigger amount. Confirm the error object above, and that the token ends at `rejected`.</td></tr>
  </tbody>
</table>

**Auto-debit**

A debit on a live mandate can run on either of two flows. On the coupled flow you create the order and the payment together and Razorpay raises the pre-debit notification, schedules the debit and runs the retries for you. On the decoupled flow you send the `notification` object on the order, raise the notification yourself and create the payment separately once the window has passed, and no retries are run on your behalf. For the full description of both, see [Subsequent Payments](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/recurring-payments/upi/subsequent-payments.md).

These happen on a mandate that is already live. The token stays usable, so the right response is to handle the failed charge rather than to ask the customer to register again.

<table>
  <colgroup>
    <col width="5%" />

    <col width="18%" />

    <col width="10%" />

    <col width="22%" />

    <col width="45%" />
  </colgroup>

  <thead><tr><th>#</th><th>Scenario</th><th>Trigger creds</th><th>Error object</th><th>Steps to follow</th></tr></thead>

  <tbody>
    <tr><td>32</td><td>Debit above `max_amount`</td><td>amount above the token's `max_amount`</td><td>—</td><td>Create a debit order and a recurring payment (`POST /v1/payments/create/recurring`) for an amount higher than the token's `max_amount`. Confirm you get 400 Bad Request with `transaction_limit_exceeded` (source: remitter\_bank), and that no payment object is created.</td></tr>
    <tr><td>33</td><td>Debit fails: remitter bank unavailable</td><td>`amount: 101`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_response\
    `reason` remitter\_bank\_error</td><td>Register a mandate (#1). Create a debit order and a recurring payment with the trigger amount. Confirm the failure and that your retry logic treats it as temporary.</td></tr>
    <tr><td>34</td><td>Debit fails: unmapped gateway error — `gateway_technical_error`</td><td>`amount: 103`</td><td>`code` GATEWAY\_ERROR\
    `source` gateway\
    `step` payment\_response\
    `reason` gateway\_technical\_error</td><td>Register a mandate (#1). Create a debit order and a recurring payment with the trigger amount. Confirm `gateway_technical_error` (source: gateway, step: payment\_response). This is the simulator's catch-all for an error it has no mapping for, so treat it as a temporary failure and retry.</td></tr>
    <tr><td>35</td><td>Remitter bank unavailable — "Your bank is temporarily unavailable. Please try again in a few moments."</td><td>`amount: 102`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_debit\_request\
    `reason` remitter\_bank\_unavailable</td><td>Register a mandate first. Create a debit order and a recurring payment with the trigger amount. Confirm the error object above, and that the token stays `confirmed` so the mandate can be charged again.</td></tr>
    <tr><td>36</td><td>Timeout at remitter bank — "Your bank took too long to respond. Please try again."</td><td>`amount: 104`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_debit\_response\
    `reason` timeout\_at\_remitter\_bank</td><td>Register a mandate first. Create a debit order and a recurring payment with the trigger amount. Confirm the error object above, and that the token stays `confirmed` so the mandate can be charged again.</td></tr>
    <tr><td>37</td><td>Remitter bank unavailable — "Your bank is temporarily unavailable. Please try again in a few moments."</td><td>`amount: 107`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_request\
    `reason` remitter\_bank\_unavailable</td><td>Register a mandate first. Create a debit order and a recurring payment with the trigger amount. Confirm the error object above, and that the token stays `confirmed` so the mandate can be charged again.</td></tr>
    <tr><td>38</td><td>Customer mobile changed — "Your registered mobile number has been updated. Please update your UPI profile or contact your bank."</td><td>`amount: 202`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_request\
    `reason` customer\_mobile\_changed</td><td>Register a mandate first. Create a debit order and a recurring payment with the trigger amount. Confirm the error object above, and that the token stays `confirmed` so the mandate can be charged again.</td></tr>
    <tr><td>39</td><td>Customer mpin not set — "UPI PIN is not set for this account. Please set up your UPI PIN and try again."</td><td>`amount: 205`</td><td>`code` GATEWAY\_ERROR\
    `source` customer\
    `step` payment\_authentication\
    `reason` customer\_mpin\_not\_set</td><td>Register a mandate first. Create a debit order and a recurring payment with the trigger amount. Confirm the error object above, and that the token stays `confirmed` so the mandate can be charged again.</td></tr>
    <tr><td>40</td><td>Incorrect pin entered — "You've exceeded the number of PIN attempts. Please wait and try again later, or contact your bank if you've forgotten your PIN."</td><td>`amount: 206`</td><td>`code` GATEWAY\_ERROR\
    `source` customer\
    `step` payment\_authentication\
    `reason` incorrect\_pin\_entered</td><td>Register a mandate first. Create a debit order and a recurring payment with the trigger amount. Confirm the error object above, and that the token stays `confirmed` so the mandate can be charged again.</td></tr>
    <tr><td>41</td><td>Transaction limit exceeded — "Payment amount exceeds your bank's limit. Please try with a lower amount or contact your bank to increase the limit."</td><td>`amount: 208`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_debit\_response\
    `reason` transaction\_limit\_exceeded</td><td>Register a mandate first. Create a debit order and a recurring payment with the trigger amount. Confirm the error object above, and that the token stays `confirmed` so the mandate can be charged again.</td></tr>
    <tr><td>42</td><td>Spending limit exceeded — "You've exceeded your total UPI spending limit. Please try again later or contact your bank to increase the limit."</td><td>`amount: 209`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_debit\_response\
    `reason` spending\_limit\_exceeded</td><td>Register a mandate first. Create a debit order and a recurring payment with the trigger amount. Confirm the error object above, and that the token stays `confirmed` so the mandate can be charged again.</td></tr>
    <tr><td>43</td><td>Frequency limit exceeded — "You've exceeded the number of allowed transactions. Please try again later or contact your bank."</td><td>`amount: 210`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_initiation\
    `reason` frequency\_limit\_exceeded</td><td>Register a mandate first. Create a debit order and a recurring payment with the trigger amount. Confirm the error object above, and that the token stays `confirmed` so the mandate can be charged again.</td></tr>
    <tr><td>44</td><td>Account blocked — "Your account is blocked or frozen. Please contact your bank to resolve this."</td><td>`amount: 212`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_debit\_request\
    `reason` account\_blocked</td><td>Register a mandate first. Create a debit order and a recurring payment with the trigger amount. Confirm the error object above, and that the token stays `confirmed` so the mandate can be charged again.</td></tr>
    <tr><td>45</td><td>Risk declined — "Payment was declined for security reasons. Please contact your bank for assistance."</td><td>`amount: 404`</td><td>`code` BAD\_REQUEST\_ERROR\
    `source` business\
    `step` payment\_debit\_response\
    `reason` risk\_declined</td><td>Register a mandate first. Create a debit order and a recurring payment with the trigger amount. Confirm the error object above, and that the token stays `confirmed` so the mandate can be charged again.</td></tr>
    <tr><td>46</td><td>Risk declined — "Payment was declined for security reasons. Please contact your bank for assistance."</td><td>`amount: 405`</td><td>`code` GATEWAY\_ERROR\
    `source` remitter\_bank\
    `step` payment\_debit\_response\
    `reason` risk\_declined</td><td>Register a mandate first. Create a debit order and a recurring payment with the trigger amount. Confirm the error object above, and that the token stays `confirmed` so the mandate can be charged again.</td></tr>
  </tbody>
</table>

**If you are on the decoupled flow**

Everything above assumes the coupled flow, which is where most integrations start and which Razorpay recommends. If you send the `notification` object on the debit order you are on the decoupled flow, and four things change. Run the cases above first, then these.

| What changes | Why it matters | Scenario |
| - | - | - |
| You raise the pre-debit notification yourself | You send `notification.token_id` and `notification.payment_after` on the order, and create the payment separately once the window has passed. | Scenario 4 |
| Razorpay does not retry for you | A failed decoupled debit goes terminal immediately. There is no ladder and no `created` holding state, so every recovery attempt is yours to make. | Scenario 3, then retry it yourself |
| You retry with a new payment on the same order | Create a fresh payment against the same `order_id`. It is a new payment ID on the same notification, and it is accepted. This is the recovery loop a coupled merchant never has to build. | Scenario 4 after scenario 3 |
| Only one debit can succeed per notification | Once a debit has succeeded against that order, any further attempt returns `mandate_already_executed`. This is what stops your retry loop double-charging. | Scenario 4 twice on one order |

If you integrate against coupled behaviour and later move to decoupled without building the retry loop, every failed debit is silently lost. That is the single most expensive mistake on this flow.

The full list of UPI Autopay error reasons, including the ones no trigger reproduces in test mode, is on the [Error index](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/best-practices/error-handling.md).

### 3.4 Not available in test mode yet

Not being able to trigger a scenario is not a reason to leave it out of your test suite. Each row below gives the behaviour your code has to implement and the assertion worth writing now, so that the test is already in place when the trigger arrives and so that you can run it against live traffic in the meantime.

| Scenario | What it is | What your integration has to do, and what to assert |
| - | - | - |
| 47 | Handoff to the UPI app and the return to your app — there is no simulator UPI app, so test mode cannot tell you whether your app hands the customer off correctly or resumes when they come back. Check this on a real Android and iOS device. | — |
| 48 | UMN not found at registration — no reserved amount reproduces this in test mode. Check your handling on live traffic. | — |
| 49 | Customer pauses, resumes or revokes in the UPI app | On `token.paused` stop debiting and hold the schedule; on `token.confirmed` resume it; on `token.cancelled` close the mandate and stop permanently. Assert that a debit attempted while your stored state is paused or cancelled is never sent to Razorpay. |
| 50 | Cancel and delete tokens | The token moves to `cancellation_initiated` first and only later to `cancelled`. It is not chargeable from the moment cancellation starts. Assert that a token in `cancellation_initiated` is treated as not chargeable, rather than waiting for `cancelled`. |
| 51 | Pre-debit notification fails | Razorpay sends `order.notification.failed` and the debit cannot proceed. A fresh order, and so a fresh notification, is required. Assert that you do not create a payment against an order whose notification failed, and that you raise a new order instead. |
| 52 | Retries and the 25-hour debit window | On the coupled flow the payment stays in `created` through every retry and only goes terminal once the ladder is exhausted. Assert that `created` is never treated as a final state and that you do not start your own recovery before a terminal status arrives. |
| 53 | App selection and deep link into the UPI app | The customer picks a UPI app, is handed off with the mandate pre-filled, and your app resumes when they return. There is no simulator UPI app, so this has to be checked on a real Android and iOS device. |
| 54 | TPV: the payer approves the mandate from the bank account you registered them with | This only applies if you are a TPV merchant, where the payer's bank account is fixed at onboarding. The payer picks an account inside their UPI app, and nothing stops them picking a different one. A mandate authorised from the wrong account registers successfully and then fails every single debit, so the damage does not show up until the first charge. There is no simulator UPI app and no account picker in test mode, so this cannot be reproduced here. Prompt the payer to choose their registered account before app selection, and assert that your own records tie the token back to the account you onboarded them with rather than assuming a confirmed token is chargeable. |

## 4. Common Integration Gaps to Avoid

These are the mistakes that most often cause failed payments, missed debits or double charges after go-live.

**Webhook and token updates**

| Gap | What goes wrong in production | What to do |
| - | - | - |
| Token ID not stored at registration | You can't debit the customer next cycle. | Store the token ID from `payment.authorized` against the customer. |
| Customer actions in the UPI app not handled | The customer pauses or revokes the mandate in their UPI app; you keep debiting. | Update token status on `token.paused`, `token.cancelled` and `token.confirmed` (resume). Treat `cancellation_initiated` as not chargeable. |
| Notification webhooks ignored | The pre-debit notification fails and the debit fails 25 hours later with no warning. | Subscribe to `order.notification.delivered` and `order.notification.failed` (note the `order.` prefix). Re-send a notification before a fresh debit. |

**Error handling and messaging to the payer**

| Gap | What goes wrong in production | What to do |
| - | - | - |
| Temporary and permanent failures handled the same | Retrying a revoked mandate burns attempts. Not retrying a bank outage loses collectable money. | Retry on `remitter_bank` and `customer_psp` errors. Stop on mandate cancelled or revoked. Notify the customer on `insufficient_funds`. |
| Registration failure messages not shown | The customer declines or the request expires in the UPI app, and your page just spins. | Show `error.description` and let the customer restart registration. |
| Customer not told a debit failed | The customer isn't present at debit time and the plan silently lapses. | Notify the customer with the reason once the payment is final, not after the first attempt. |
| Mandate approved from the wrong bank account (TPV) | Registration succeeds and then every debit fails. | Prompt the customer to choose their registered account before app selection. |

**Fetch API when webhooks are late**

| Gap | What goes wrong in production | What to do |
| - | - | - |
| Starting your own recovery while Razorpay is still retrying | On the coupled flow the payment stays `created` while retries run. A fresh order and debit from you can lead to two debit attempts. | Wait for the final status. Retries finish within about 32–120 hours depending on your retry template. |
| Polling too early or not at all | Live webhooks can take up to 12 minutes. Without a fallback you miss registrations during outages. | If nothing arrives within a minute, poll `GET /v1/payments/:payment_id` and the token fetch APIs. |

## 5. Best Integration Practices

These apply to every Razorpay integration. "On this method" says what each one means here; "Test it" links to where it is tested once for every method.

| Topic | What to know | On this method | Test it | Read more |
| - | - | - | - | - |
| Difference between order and payment | An order is what you want to collect. A payment is one attempt to pay it. One order can have many attempts but only one captured payment. Create the order on your server so the amount can't be tampered with. | One debit order is one pre-debit notification. Each debit attempt is a payment on that order. | BP-24, BP-25 | [Difference between order and payment ↗](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/best-practices/order-payment-relationship.md) |
| Order states | Orders move `created` → `attempted` → `paid`. An order stays `attempted` through failed attempts, and stays `paid` even after a refund. Handle Orders API errors (for example an invalid amount or currency) when you create the order. | On the coupled flow, the debit order stays `attempted` while Razorpay retries. | BP-09 | [Order states ↗](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/best-practices/payment-states-and-late-authorisation.md) · [Orders API error codes ↗](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/orders.md) |
| Late authorisation | A payment with no bank response is marked `failed` after 10 minutes, but Razorpay keeps polling the bank for 3 days. If the bank confirms, the payment becomes `authorized` (under 0.5% of payments). Listen for `payment.authorized` and capture it, or let it auto-refund. | Applies to the registration payment, which happens in the customer's UPI app. | BP-06 | [Late authorisation ↗](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/best-practices/payment-states-and-late-authorisation.md) |
| Capture settings | Choose auto-capture or manual capture and set the timeouts. An authorized payment you don't capture in time is auto-refunded. You can set capture behaviour per order through the Orders API. | Pass `payment_capture: true` on debit orders to capture automatically. | BP-14 | [Capture settings ↗](https://razorpay.com/docs/payments/payment-gateway/rainy-day/capture-settings) |
| Multiple payments on the same order | Once an order is paid, no more payments are accepted on it. While a payment on the order is authorized, a new attempt isn't allowed. An attempt that authorises late on a paid order is auto-refunded. Retry on the same order so a customer can't pay twice. | Coupled: create one payment per order; Razorpay retries under it, and extra payments you create fail. Decoupled: up to 4 payment IDs per order. After that, a new order (and a new notification) is needed. | BP-09 · BP-08 | [Multiple payments on the same order ↗](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/best-practices/payment-states-and-late-authorisation.md) |

All best practices: [Overview](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/best-practices) · [Webhooks and Fetch APIs](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/best-practices/webhooks-and-fetch-apis.md) · [Payment states and late authorisation](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/best-practices/payment-states-and-late-authorisation.md) · [Error handling](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/best-practices/error-handling.md) · [Downtime handling](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/best-practices/downtime-handling.md) · [Security](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/best-practices/security.md)

### Related Information

* [UPI Autopay QR](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/recurring/upi-autopay-qr.md)
* [UPI Autopay Collect](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/recurring/upi-autopay-collect.md)
* [Card Recurring](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/recurring/card-recurring)
* [Orders](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/orders.md)
* [Refunds](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/refunds.md)
* [Best Practices](https://razorpay.com/docs/payments/payment-gateway/s2s-integration/test-s2s/best-practices)
