Auto Rescue for SEPA
Intelligent retry strategies for SEPA Direct Debit payment failures and R-transactions
Auto Rescue for SEPA
Auto Rescue for SEPA automatically handles failed SEPA Direct Debit payments, including R-transactions (returns, refusals, and reversals). SEPA Direct Debit has unique timing requirements and failure modes that require specialized retry strategies.
SEPA Direct Debit Lifecycle
Understanding the SEPA payment lifecycle is essential for effective recovery:
R-Transaction Types
SEPA returns (R-transactions) have specific codes and meanings:
Return Codes (Most Common)
| Code | Name | Description | Retryable |
|---|---|---|---|
AC01 | Incorrect Account Number | IBAN is wrong | No |
AC04 | Closed Account | Account has been closed | No |
AC06 | Blocked Account | Account is blocked | Maybe |
AG01 | Transaction Forbidden | Not authorized for DD | No |
AG02 | Invalid Bank Operation Code | Technical error | Yes |
AM04 | Insufficient Funds | Not enough balance | Yes |
AM05 | Duplicate Collection | Already debited | No |
BE05 | Unknown Creditor ID | Creditor not recognized | No |
FF01 | Invalid File Format | Technical error | Yes |
MD01 | No Mandate | Mandate not found/cancelled | No |
MD06 | Refund Request | Customer requested refund | No |
MS02 | Reason Not Specified | Customer refused | Maybe |
MS03 | Reason Not Specified | Agent refused | Maybe |
RC01 | Bank Identifier Incorrect | BIC is wrong | No |
SL01 | Service Not Allowed | DD not permitted | No |
R-transactions typically arrive 2-5 business days after the debit date. Plexy automatically monitors for returns and initiates Auto Rescue when appropriate.
How Auto Rescue Handles SEPA
Enabling Auto Rescue for SEPA
curl -X POST https://api.plexypay.com/v2/payments \
-H "x-api-key: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 4999,
"currency": "EUR",
"customer": "cus_abc123",
"payment_method": "pm_sepa_debit_xyz789",
"payment_method_options": {
"sepa_debit": {
"mandate": "md_abc123"
}
},
"auto_rescue": {
"enabled": true,
"max_retries": 3,
"retry_window_days": 21
},
"metadata": {
"subscription_id": "sub_monthly_123"
}
}'import { Plexy } from '@plexy/plexy-web';
const plexy = new Plexy({
apiKey: process.env.PLEXY_SECRET_KEY,
});
const payment = await plexy.payments.create({
amount: 4999,
currency: 'EUR',
customer: 'cus_abc123',
payment_method: 'pm_sepa_debit_xyz789',
payment_method_options: {
sepa_debit: {
mandate: 'md_abc123',
},
},
auto_rescue: {
enabled: true,
max_retries: 3,
retry_window_days: 21,
},
metadata: {
subscription_id: 'sub_monthly_123',
},
});
console.log('Payment status:', payment.status);import os
from plexy import Plexy
plexy = Plexy(api_key=os.environ['PLEXY_SECRET_KEY'])
payment = plexy.payments.create(
amount=4999,
currency='EUR',
customer='cus_abc123',
payment_method='pm_sepa_debit_xyz789',
payment_method_options={
'sepa_debit': {
'mandate': 'md_abc123',
},
},
auto_rescue={
'enabled': True,
'max_retries': 3,
'retry_window_days': 21,
},
metadata={
'subscription_id': 'sub_monthly_123',
},
)
print(f"Payment status: {payment.status}")SEPA-Specific Configuration
Retry Timing
SEPA has minimum timing requirements:
| Constraint | Requirement |
|---|---|
| Minimum retry delay | 2 business days after R-transaction |
| Due date lead time | D-2 for Core, D-1 for B2B |
| Pre-notification | 14 days before first debit, unless agreed otherwise |
| Settlement time | 2-5 business days |
Configure timing accordingly:
const payment = await plexy.payments.create({
amount: 4999,
currency: 'EUR',
customer: 'cus_abc123',
payment_method: 'pm_sepa_debit_xyz789',
auto_rescue: {
enabled: true,
sepa_options: {
// Minimum days between retry attempts (SEPA rules require >= 2)
min_retry_interval_days: 5,
// Whether to send pre-notification for retries
send_prenotification: true,
// Days of pre-notification before debit (default 14)
prenotification_days: 5, // Reduced if customer agreed
},
max_retries: 3,
},
});R-Code Strategies
Configure different strategies based on R-transaction codes:
const payment = await plexy.payments.create({
amount: 4999,
currency: 'EUR',
customer: 'cus_abc123',
payment_method: 'pm_sepa_debit_xyz789',
auto_rescue: {
enabled: true,
r_code_strategies: {
// Insufficient funds - retry around typical paydays
AM04: {
retry: true,
schedule_days: [5, 12, 20], // Spread across month
notify_customer: true,
},
// Blocked account - retry once, then investigate
AC06: {
retry: true,
max_retries: 1,
schedule_days: [7],
escalate_on_failure: true,
},
// Mandate cancelled - do not retry
MD01: {
retry: false,
action: 'contact_customer',
deactivate_mandate: true,
},
// Customer refund request - do not retry
MD06: {
retry: false,
action: 'process_refund',
},
},
},
});Webhook Events
payment.sepa_return_received
Sent when an R-transaction is received.
{
"id": "evt_abc123",
"type": "payment.sepa_return_received",
"created_at": "2026-03-28T09:15:00Z",
"data": {
"payment": "pay_xyz789",
"customer": "cus_abc123",
"amount": 4999,
"currency": "EUR",
"mandate": "md_abc123",
"r_transaction": {
"code": "AM04",
"reason": "Insufficient Funds",
"received_at": "2026-03-28T09:15:00Z",
"original_debit_date": "2026-03-25"
},
"auto_rescue": {
"enabled": true,
"will_retry": true,
"next_retry_at": "2026-04-02T10:00:00Z"
}
}
}payment.rescue_retry (SEPA)
Sent when a SEPA retry is being attempted.
{
"id": "evt_def456",
"type": "payment.rescue_retry",
"created_at": "2026-04-02T10:00:00Z",
"data": {
"payment": "pay_xyz789",
"customer": "cus_abc123",
"payment_method_type": "sepa_debit",
"retry_number": 1,
"previous_r_code": "AM04",
"debit_date": "2026-04-07",
"prenotification_sent": true,
"retry_strategy": {
"r_code_category": "insufficient_funds",
"timing_reason": "payday_alignment"
}
}
}payment.rescue_succeeded (SEPA)
Sent when a SEPA payment is successfully recovered.
{
"id": "evt_ghi789",
"type": "payment.rescue_succeeded",
"created_at": "2026-04-10T14:30:00Z",
"data": {
"payment": "pay_xyz789",
"customer": "cus_abc123",
"amount": 4999,
"currency": "EUR",
"retry_number": 1,
"total_attempts": 2,
"days_to_recover": 15,
"settlement_date": "2026-04-10"
}
}sepa_mandate.requires_attention
Sent when a mandate issue is detected.
{
"id": "evt_jkl012",
"type": "sepa_mandate.requires_attention",
"created_at": "2026-03-28T09:20:00Z",
"data": {
"mandate": "md_abc123",
"customer": "cus_abc123",
"issue": "mandate_cancelled",
"r_code": "MD01",
"triggered_by_payment": "pay_xyz789",
"recommendation": "Contact customer to set up a new mandate"
}
}Handling R-Transaction Types
Insufficient Funds (AM04)
The most common return reason, often resolved with proper timing.
// Configure payday-optimized retries for AM04
const payment = await plexy.payments.create({
amount: 4999,
currency: 'EUR',
customer: 'cus_abc123',
payment_method: 'pm_sepa_debit_xyz789',
auto_rescue: {
enabled: true,
r_code_strategies: {
AM04: {
retry: true,
// Retry on dates aligned with common paydays in EU
// 1st: Month start, 15th: Mid-month, 25th: End of month
schedule_days: [5, 16, 26],
max_retries: 3,
notify_customer_after_attempt: 1,
},
},
},
});Account Closed (AC04) or Incorrect (AC01)
Permanent issues requiring new account details.
app.post('/webhooks/plexy', async (req, res) => {
const event = verifyWebhook(req);
if (event.type === 'payment.sepa_return_received') {
const { r_transaction, customer } = event.data;
if (['AC01', 'AC04'].includes(r_transaction.code)) {
// Deactivate the payment method
await plexy.paymentMethods.update(event.data.payment_method, {
active: false,
deactivation_reason: r_transaction.reason,
});
// Contact customer for new account details
await sendEmail(customer, 'sepa_account_invalid', {
reason: r_transaction.reason,
update_url: 'https://yoursite.com/update-payment',
});
}
}
res.status(200).send('OK');
});Mandate Issues (MD01, MD06)
Mandate-related returns require special handling.
app.post('/webhooks/plexy', async (req, res) => {
const event = verifyWebhook(req);
if (event.type === 'payment.sepa_return_received') {
const { r_transaction, mandate, customer } = event.data;
if (r_transaction.code === 'MD01') {
// Mandate not found or cancelled
await plexy.mandates.update(mandate, {
status: 'cancelled',
cancellation_reason: 'r_transaction_md01',
});
await sendEmail(customer, 'mandate_cancelled', {
setup_url: 'https://yoursite.com/setup-mandate',
});
}
if (r_transaction.code === 'MD06') {
// Customer requested refund - process it
await plexy.refunds.create({
payment: event.data.payment,
reason: 'customer_request_via_bank',
});
// Note: Consider if subscription should be cancelled
await flagForReview(customer, 'sepa_refund_requested');
}
}
res.status(200).send('OK');
});SEPA Core vs B2B
Auto Rescue handles both SEPA schemes:
| Aspect | SEPA Core | SEPA B2B |
|---|---|---|
| Refund right | 8 weeks (no questions) | None |
| Mandate verification | Optional | Required |
| Minimum lead time | D-2 | D-1 |
| Retry approach | More conservative | Can be more aggressive |
Configure by Scheme
const payment = await plexy.payments.create({
amount: 4999,
currency: 'EUR',
customer: 'cus_abc123',
payment_method: 'pm_sepa_debit_xyz789',
payment_method_options: {
sepa_debit: {
mandate: 'md_abc123',
scheme: 'b2b', // or 'core'
},
},
auto_rescue: {
enabled: true,
sepa_options: {
scheme_specific: {
core: {
max_retries: 2, // Conservative for Core (refund risk)
retry_window_days: 14,
},
b2b: {
max_retries: 3, // More aggressive for B2B
retry_window_days: 21,
},
},
},
},
});Pre-Notification Management
SEPA requires customer notification before debits:
// Configure pre-notification for retries
const payment = await plexy.payments.create({
amount: 4999,
currency: 'EUR',
customer: 'cus_abc123',
payment_method: 'pm_sepa_debit_xyz789',
auto_rescue: {
enabled: true,
sepa_options: {
send_prenotification: true,
prenotification_days: 5, // 5 days before debit
prenotification_template: 'retry_notification',
},
},
});Pre-Notification Webhook
{
"id": "evt_mno345",
"type": "sepa_prenotification.sent",
"created_at": "2026-03-27T10:00:00Z",
"data": {
"payment": "pay_xyz789",
"customer": "cus_abc123",
"amount": 4999,
"currency": "EUR",
"debit_date": "2026-04-01",
"is_retry": true,
"retry_number": 1,
"notification_type": "email"
}
}Best Practices
Timing Around Paydays
In Europe, common paydays include:
- End of month (25th-31st)
- Mid-month (15th)
- Beginning of month (1st-5th)
// Optimal retry schedule for EU paydays
auto_rescue: {
enabled: true,
sepa_options: {
// Schedule debits for 2-3 days after typical paydays
preferred_debit_days: [3, 17, 28],
},
}Monitor Mandate Health
Track mandate-related returns:
// Fetch mandate analytics
const mandateHealth = await plexy.analytics.getMandateHealth({
start_date: '2026-01-01',
end_date: '2026-03-31',
});
// Response includes:
// {
// "total_mandates": 5234,
// "active": 5012,
// "cancelled_by_customer": 89,
// "cancelled_by_r_transaction": 133,
// "r_transaction_breakdown": {
// "MD01": 98,
// "MD06": 35
// }
// }Respect Customer Requests
When you receive MD06 (refund request by customer), consider:
- Processing the refund promptly
- Reviewing the customer's subscription status
- Reaching out to understand the reason
- Potentially pausing future debits
if (r_code === 'MD06') {
// Customer went to their bank for a refund - take it seriously
await pauseSubscription(customer);
await sendEmail(customer, 'subscription_paused', {
reason: 'bank_refund_request',
contact_us: true,
});
}Know When to Stop
Unlike cards, SEPA retries should be more conservative:
| Factor | Recommendation |
|---|---|
| Max retries | 2-3 (not 4-5 like cards) |
| Window | 14-21 days |
| After MD01/MD06 | Never retry |
| Multiple AM04s | Consider contacting customer |
Next Steps
- Auto Rescue Overview - General Auto Rescue concepts
- Auto Rescue for Cards - Card-specific strategies
- SEPA Direct Debit Guide - Full SEPA integration details