# Handle failed subscription payments

When a recurring subscription payment fails, MONEI automatically retries the charge according to a retry schedule. This process — known as dunning — helps recover failed payments before canceling the subscription.

## How retries work[​](#how-retries-work "Direct link to How retries work")

1. A recurring payment fails (e.g., insufficient funds, expired card).
2. The subscription moves to `PAST_DUE` status and a `SUBSCRIPTION_PAST_DUE` webhook is sent.
3. MONEI retries the payment according to the retry schedule.
4. If a retry succeeds, the subscription returns to `ACTIVE` and billing continues normally.
5. If all retries are exhausted, the subscription is `CANCELED` and a `SUBSCRIPTION_CANCELED` webhook is sent.

## Default retry schedule[​](#default-retry-schedule "Direct link to Default retry schedule")

MONEI uses different retry schedules depending on the billing interval:

| Billing interval  | Retry 1 | Retry 2  | Retry 3  |
| ----------------- | ------- | -------- | -------- |
| Day               | +1 hour | +2 hours | +3 hours |
| Week, Month, Year | +1 day  | +5 days  | +9 days  |

Each retry interval is counted from the **previous attempt**, not from the initial failure. For example, if a monthly subscription payment fails:

* **Retry 1** — 1 day after the initial failure
* **Retry 2** — 5 days after retry 1 (6 days total)
* **Retry 3** — 9 days after retry 2 (15 days total)
* If all 3 retries fail, the subscription is canceled

Test intervals

In test mode, `minute` and `hour` intervals use shorter retry schedules (minutes instead of hours/days) for faster testing.

## Custom retry schedule[​](#custom-retry-schedule "Direct link to Custom retry schedule")

You can override the default retry schedule when creating or updating a subscription using the `retrySchedule` parameter.

Each entry in the array defines a retry interval:

<!-- -->

* cURL
* Node.js
* PHP
* Python

POST https\://api.monei.com/v1/subscriptions

```
curl --request POST 'https://api.monei.com/v1/subscriptions' \

--header 'Authorization: YOUR_API_KEY' \

--header 'Content-Type: application/json' \

--data-raw '{

  "amount": 1500,

  "currency": "EUR",

  "interval": "month",

  "intervalCount": 1,

  "callbackUrl": "https://example.com/subscriptions/callback",

  "paymentCallbackUrl": "https://example.com/payments/callback",

  "retrySchedule": [

    {"interval": "day", "intervalCount": 1},

    {"interval": "day", "intervalCount": 3},

    {"interval": "day", "intervalCount": 7},

    {"interval": "day", "intervalCount": 14}

  ]

}'
```

server.js

```
const subscription = await monei.subscriptions.create({

  amount: 1500,

  currency: 'EUR',

  interval: 'month',

  intervalCount: 1,

  callbackUrl: 'https://example.com/subscriptions/callback',

  paymentCallbackUrl: 'https://example.com/payments/callback',

  retrySchedule: [

    {interval: 'day', intervalCount: 1},

    {interval: 'day', intervalCount: 3},

    {interval: 'day', intervalCount: 7},

    {interval: 'day', intervalCount: 14}

  ]

});
```

server.php

```
<?php

$subscription = $monei->subscriptions->create(

  new CreateSubscriptionRequest([

    'amount' => 1500,

    'currency' => 'EUR',

    'interval' => 'month',

    'interval_count' => 1,

    'callback_url' => 'https://example.com/subscriptions/callback',

    'payment_callback_url' => 'https://example.com/payments/callback',

    'retry_schedule' => [

      ['interval' => 'day', 'interval_count' => 1],

      ['interval' => 'day', 'interval_count' => 3],

      ['interval' => 'day', 'interval_count' => 7],

      ['interval' => 'day', 'interval_count' => 14]

    ]

  ])

);

?>
```

server.py

```
subscription = monei.subscriptions.create(

    CreateSubscriptionRequest(

        amount=1500,

        currency="EUR",

        interval="month",

        interval_count=1,

        callback_url="https://example.com/subscriptions/callback",

        payment_callback_url="https://example.com/payments/callback",

        retry_schedule=[

            {"interval": "day", "interval_count": 1},

            {"interval": "day", "interval_count": 3},

            {"interval": "day", "interval_count": 7},

            {"interval": "day", "interval_count": 14}

        ]

    )

)
```

This example retries after 1 day, 3 days, 7 days, and 14 days — giving the customer more time to resolve their payment issue.

caution

For custom retry schedules, the total duration cannot exceed the subscription billing interval. For example, a daily subscription cannot have retries that span more than 1 day.

## Webhook events[​](#webhook-events "Direct link to Webhook events")

MONEI sends webhook notifications at each stage of the retry process:

| Event                    | When                                    | Sent to              |
| ------------------------ | --------------------------------------- | -------------------- |
| Payment callback         | Each retry attempt (success or failure) | `paymentCallbackUrl` |
| `SUBSCRIPTION_PAST_DUE`  | First payment failure                   | `callbackUrl`        |
| `SUBSCRIPTION_ACTIVATED` | Retry succeeds, subscription recovers   | `callbackUrl`        |
| `SUBSCRIPTION_CANCELED`  | All retries exhausted                   | `callbackUrl`        |

Use these webhooks to:

* Notify customers about failed payments and prompt them to update their payment method
* Update your system when a subscription recovers or is canceled
* Track payment retry attempts for analytics

## Recover a past-due subscription[​](#recover-a-past-due-subscription "Direct link to Recover a past-due subscription")

If a subscription is in `PAST_DUE` status, you can recover it by setting `skipIntervalCount` via the [update subscription](https://docs.monei.com/apis/rest/subscriptions-update/.md) endpoint. This tells the system to skip the failed billing cycle and move to the next one — the subscription returns to `ACTIVE` and the retry counter resets.

* cURL
* Node.js
* PHP
* Python

PUT https\://api.monei.com/v1/subscriptions/{id}

```
curl --request PUT 'https://api.monei.com/v1/subscriptions/YOUR_SUBSCRIPTION_ID' \

--header 'Authorization: YOUR_API_KEY' \

--header 'Content-Type: application/json' \

--data-raw '{

  "skipIntervalCount": 1

}'
```

server.js

```
const subscription = await monei.subscriptions.update('YOUR_SUBSCRIPTION_ID', {

  skipIntervalCount: 1

});
```

server.php

```
<?php

use Monei\Model\UpdateSubscriptionRequest;



$subscription = $monei->subscriptions->update(

  'YOUR_SUBSCRIPTION_ID',

  new UpdateSubscriptionRequest([

    'skip_interval_count' => 1

  ])

);

?>
```

server.py

```
from Monei import UpdateSubscriptionRequest



subscription = monei.subscriptions.update(

    "YOUR_SUBSCRIPTION_ID",

    UpdateSubscriptionRequest(

        skip_interval_count=1

    )

)
```

This is useful when a customer has updated their payment method (via [re-activation](https://docs.monei.com/subscriptions/manage-subscriptions/.md#update-payment-method)) and you want to skip the failed cycle rather than waiting for the next retry.

## Email notifications[​](#email-notifications "Direct link to Email notifications")

MONEI can automatically send email notifications to customers and account administrators when subscription events occur:

* **Customer emails** — notify the customer when their subscription status changes (e.g., payment failed, subscription paused)
* **Admin emails** — notify account administrators about subscription events

These are configured in your [MONEI Dashboard notification settings](https://dashboard.monei.com/settings). No code changes are required.

MONEI also sends automatic alerts to the account email when webhook delivery to your `callbackUrl` fails repeatedly in live mode.

## Test failed payments[​](#test-failed-payments "Direct link to Test failed payments")

Use the test card number ending in **5565** to simulate failed recurring payments:

1. Create and activate a subscription in [test mode](https://docs.monei.com/testing/.md) using the test card `4242 4242 4242 5565`.
2. The **initial payment succeeds** — the subscription becomes `ACTIVE`.
3. The **first recurring payment fails** — the subscription moves to `PAST_DUE`.
4. MONEI retries according to the retry schedule. All retries will fail with this test card.
5. After 3 failed retries, the subscription is automatically **canceled**.

This lets you verify your webhook handling for `SUBSCRIPTION_PAST_DUE` and `SUBSCRIPTION_CANCELED` events, and test any customer notification flows you've built.

tip

Use `minute` or `hour` billing intervals in test mode to speed up the retry cycle instead of waiting for days.
