SEPA R-Transactions: A Technical Reference
Reject, Return, Refund, Reversal. Four distinct lifecycle events, each with different ledger impact, PSD2 timelines, and ISO reason codes.
Understanding SEPA R-Transactions: A Technical Guide for Payment Engineers
"The payment was returned." In SEPA, this sentence can mean four different things. Each has a different trigger, a different PSD2 timeline, a different settlement lifecycle, and a different treatment in the ledger. Most implementations treat them as a single "reversal" event with a reason code attached. This leads to incorrect holding period management, missed PSD2 deadlines, and reconciliation discrepancies that surface weeks after the fact.
SEPA R-transactions are not one concept. They are four.
Four Types, Not One
| Type | Trigger | When | Legal Basis | Ledger Impact |
|---|---|---|---|---|
| Reject | Bank rejects before interbank settlement | D+1 (before settlement) | EPC rulebook | None, no money moved. Bank operations only. |
| Return | Bank returns after settlement, within holding period | D+5 (Core), D+2 (B2B) | PSD2 Art. 71 | Reverse the settled amount. Mark RETURNED. |
| Refund | Payer disputes after holding period expires | Up to 8 weeks (Core), no right (B2B) | PSD2 Art. 76 | New debit against creditor. Not a reversal, a fresh claim. |
| Reversal | Originator requests cancellation after settlement | No fixed deadline | camt.056 | Correction entry (Stornobuchung per HGB §239). |
The distinction is not academic. It determines what the system must do with the money, when it must do it, and what the ledger entry looks like.
A Reject is silent from the creditor's perspective. The payment never settled. The debtor's bank rejected it pre-settlement (wrong IBAN, closed account, blocked account). The creditor's system may not even need to know, unless it was tracking the expected incoming payment.
A Return is a reversal within the holding period. The money was settled (credited to the creditor's account in the clearing system), but during the holding period the debtor's bank sends a return. The creditor's ledger must reverse the credit. The funds transition from SETTLED_PENDING back to the debtor.
A Refund happens after the holding period has expired and the funds are SETTLED_AVAILABLE. The creditor has the money. The debtor disputes, within 8 weeks for SEPA Core Direct Debit (PSD2 Art. 76), with no dispute right for B2B. A refund is not a reversal of the original entry. It is a new, separate debit against the creditor. The original entry remains in the ledger, unchanged and immutable.
A Reversal is initiated by the originator (not the debtor's bank) via a camt.056 cancellation request. This typically happens when the originator discovers a duplicate or an error after settlement. In German accounting, this is a Stornobuchung, a correction entry that references the original via a correction_of foreign key. The original entry stays. The correction entry carries the same amount with opposite sign.
Holding Periods and Settlement Lifecycle
Between settlement and availability, funds pass through a holding period. The duration depends on the scheme:
| Scheme | Holding Period | Calendar |
|---|---|---|
| SEPA Direct Debit Core | 5 TARGET2 business days | ECB TARGET2 calendar |
| SEPA Direct Debit B2B | 2 TARGET2 business days | ECB TARGET2 calendar |
"Business days" is not "calendar days." TARGET2 has its own holiday calendar: weekends, New Year's, Good Friday, Easter Monday, May 1, Christmas Day, December 26. German national holidays that are not TARGET2 holidays do not count. Spanish national holidays that are not TARGET2 holidays do not count. The business day calculation must use the TARGET2 calendar, not a national calendar.
During the holding period, funds are in a SETTLED_PENDING state. The creditor's balance shows the credit, but the funds are not available for withdrawal or further transfer. After the holding period expires, funds transition to SETTLED_AVAILABLE. Returns are no longer possible (but Refunds still are, for Core DD).
Incoming SEPA Direct Debit credited to creditor account.
Funds visible but not available for withdrawal. Returns are possible during this window.
Credit reversed. Funds transition back to debtor. Settlement voided.
Funds released and available. Returns no longer possible.
New debit against creditor. Not a reversal, a separate claim. Original entry unchanged.
Settlement complete. No further R-transactions possible.
ISO Reason Codes Drive the Logic
pacs.004 (Payment Return) carries a ReasonCode field from the ExternalStatusReason1Code enumeration. This is not free text. It is a closed set defined by ISO 20022 and constrained by EPC rulebooks.
The reason code determines the R-transaction type. The type determines the settlement action. The action determines the PSD2 timeline. The chain is deterministic.
| Reason Code | Meaning | Classification Logic |
|---|---|---|
| AC01 | Incorrect account number | Pre-settlement → Reject; post-settlement → Return |
| AC04 | Closed account number | Return |
| AC06 | Blocked account | Return |
| AM05 | Duplicate payment | Return |
| MD01 | No mandate (debtor denies) | Return |
| MS02 | Refusal by debtor (no reason) | Within holding period → Return; after → Refund |
| MS03 | Reason not specified | Return |
| SL01 | Specific service offered by debtor agent | Return |
| DUPL | Duplicate sending | Refund (originator-initiated) |
| CUST | Requested by originator | Reversal (via camt.056) |
| AGNT | Incorrect agent | Reject (pre-settlement) |
| FOCR | Following cancellation request | Reversal (response to camt.056) |
The classification function:
classify(reason_code, within_holding_period) → R-transaction type
if pre_settlement(reason_code): → REJECT
if within_holding_period: → RETURN
if originator_initiated(reason_code): → REVERSAL
else: → REFUND
When this classification is implemented directly on the ISO reason code, without an intermediate mapping table, the handling is deterministic. A new reason code in a future EPC rulebook version requires adding one enum variant. The compiler identifies every handler that doesn't cover it. No mapping table to update. No deployment to synchronize.
Implementation Architecture
Three responsibilities, three services. Each owns one concern.
Classification service: receives incoming pacs.004, extracts the reason code, classifies into Reject/Return/Refund/Reversal based on the code and the holding period status of the original transfer.
Settlement service: based on the classified type, executes the correct ledger operation:
- Reject: no ledger action (the original transfer never settled).
- Return: void the pending settlement. Transition from
SETTLED_PENDINGtoRETURNED. - Refund: create a new debit transfer against the creditor. The original entry is untouched.
- Reversal: create a correction entry (Stornobuchung) with a
correction_ofreference to the original transfer.
Audit service: logs every R-transaction with: type, reason code, original transfer reference (resolved by transfer_id → EndToEndId → external_id, fallback chain), PSD2 deadline, and DORA event ID.
The original transfer is resolved through three lookup paths:
- Direct transfer_id (if the R-transaction carries it)
- EndToEndId matching (the ISO end-to-end reference)
- External_id matching (the bank's reference)
The fallback chain handles the real-world reality that not all pacs.004 messages carry all reference fields. Some banks populate EndToEndId. Some use their own external reference. The system must handle all three.
The Stornobuchung (Correction Entry)
German commercial law (HGB §239) requires that corrections to ledger entries are recorded as new entries. Never as modifications to the original. Never as deletions.
A Reversal is not DELETE FROM transfers WHERE id = original_id. It is:
INSERT INTO transfers (
debit_account_id, -- original credit account (direction reversed)
credit_account_id, -- original debit account (direction reversed)
amount, -- same amount as original
correction_of, -- FK → original transfer
code, -- reversal code
...
)
The original entry remains in the ledger. Immutable. The correction entry references it. An auditor sees both: the original posting and its correction, linked by foreign key. The ledger balance reflects the net effect. The audit trail shows the complete history.
This is not a German peculiarity. It is sound accounting practice adopted by any system that takes immutability seriously. If the ledger allows modifications to historical entries, the audit trail is unreliable. If corrections are new entries that reference the original, the trail is complete and tamper-evident.
What Gets Wrong in Practice
Three common implementation mistakes:
1. Treating all R-transactions as reversals. A refund is not a reversal. A reversal reverses the original entry (correction posting). A refund is a new claim that creates a new debit. Conflating them leads to incorrect ledger entries and incorrect balance calculations.
2. Using calendar days for holding periods. SEPA holding periods are counted in TARGET2 business days. A payment settled on Wednesday before a TARGET2 holiday on Thursday: the holding period does not count Thursday. Using calendar days means releasing funds too early (regulatory risk) or too late (liquidity cost).
3. Ignoring the B2B vs. Core distinction. SEPA Direct Debit B2B has a 2-day holding period and no refund right (the debtor has no Art. 76 claim). SEPA Core has a 5-day holding period and an 8-week refund window. If the system applies Core rules to B2B transactions, it over-holds funds. If it applies B2B rules to Core transactions, it under-holds, and is exposed to returns it cannot cover.
Read more: Payments, Payment Orchestration | Connectivity, Financial Rails
Sources:
- European Payments Council: EPC016-06 (SEPA Core Direct Debit Rulebook, v2024.1)
- European Payments Council: EPC222-07 (SEPA B2B Direct Debit Rulebook)
- PSD2, Directive 2015/2366, Art. 71 (Unauthorized transactions), Art. 76 (Refund rights for direct debits)
- HGB §239, German Commercial Code, requirements for correction of accounting entries
- ECB TARGET2 calendar (https://www.ecb.europa.eu/paym/target/target2/profuse/calendar/html/index.en.html)
- ISO 20022 ExternalStatusReason1Code, pacs.004.001.11 reason code enumeration