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
- A recurring payment fails (e.g., insufficient funds, expired card).
- The subscription moves to
PAST_DUEstatus and aSUBSCRIPTION_PAST_DUEwebhook is sent. - MONEI retries the payment according to the retry schedule.
- If a retry succeeds, the subscription returns to
ACTIVEand billing continues normally. - If all retries are exhausted, the subscription is
CANCELEDand aSUBSCRIPTION_CANCELEDwebhook is sent.
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
In test mode, minute and hour intervals use shorter retry schedules (minutes instead of hours/days) for faster testing.
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
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}
]
}'
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}
]
});
<?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]
]
])
);
?>
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.
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
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
If a subscription is in PAST_DUE status, you can recover it by setting skipIntervalCount via the update subscription 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
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
}'
const subscription = await monei.subscriptions.update('YOUR_SUBSCRIPTION_ID', {
skipIntervalCount: 1
});
<?php
use Monei\Model\UpdateSubscriptionRequest;
$subscription = $monei->subscriptions->update(
'YOUR_SUBSCRIPTION_ID',
new UpdateSubscriptionRequest([
'skip_interval_count' => 1
])
);
?>
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) and you want to skip the failed cycle rather than waiting for the next retry.
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. 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
Use the test card number ending in 5565 to simulate failed recurring payments:
- Create and activate a subscription in test mode using the test card
4242 4242 4242 5565. - The initial payment succeeds — the subscription becomes
ACTIVE. - The first recurring payment fails — the subscription moves to
PAST_DUE. - MONEI retries according to the retry schedule. All retries will fail with this test card.
- 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.
Use minute or hour billing intervals in test mode to speed up the retry cycle instead of waiting for days.