Validation and Rejection: ISO 20022 Error Handling

A payment message can fail in many ways: malformed XML, a missing mandatory element, an invalid IBAN, a closed account, or a compliance block. ISO 20022 does not leave error handling to improvisation. It defines layered validation and a structured vocabulary of status and rejection messages so that a failure is communicated precisely and consistently. This post walks through where validation happens, how rejections are reported, and how to design software that handles them cleanly.
The layers of validation
Validation in an ISO 20022 flow happens in stages, and it helps to think of them as a funnel. A message must pass each layer before reaching the next.
1. Syntax validation
First the message must be well-formed and valid against its XSD schema. This catches structural problems: malformed XML, elements in the wrong order, missing mandatory tags, values that violate a data type or length. Because the schema is precise, syntax validation is deterministic — a message either conforms or it does not. This is the cheapest layer and should happen as early as possible.
2. Scheme and usage-rule validation
Passing the base schema is not enough. Each scheme publishes usage guidelines that further constrain the message: which optional fields become mandatory, allowed code values, character-set restrictions, and cross-field rules. A field that is optional in the raw ISO standard may be required by a particular instant-payment scheme. These rules are often expressed as additional constraints and validated after the XSD.
3. Business validation
Finally the message is checked against reality: does the debtor account exist and have funds, is the creditor account open, does the amount fall within limits, does the payment pass sanctions screening. These checks depend on state the receiving institution holds, so they cannot be caught by schema validation. A business rejection is where most real-world declines happen.
How rejections are communicated
ISO 20022 uses dedicated status messages rather than ad-hoc error responses. The right message depends on which side of the chain you are on:
- pain.002 — the CustomerPaymentStatusReport, sent from a bank back to a customer to accept or reject a pain.001 initiation.
- pacs.002 — the FIToFIPaymentStatusReport, sent between banks to report the status of a pacs.008, including acceptance or rejection in a clearing system.
- pacs.004 — the PaymentReturn, used to send funds back when a payment was accepted but later needs to be reversed, for example to a closed account.
- camt.056 — a request to recall or cancel a payment, answered by a resolution message such as camt.029.
A status report carries a group-level and/or transaction-level status. The key values are ACCP (accepted), RJCT (rejected), and pending states like PDNG. When the status is RJCT, the message must explain why.
Reason codes: saying why in a standard way
The heart of good error handling is the reason code. ISO 20022 maintains an external code set of standardised reasons so that a rejection is machine-readable and consistent across institutions. Common examples include:
- AC01 — incorrect account number.
- AC04 — account closed.
- AM04 — insufficient funds.
- AM05 — duplicate payment.
- RC01 — bank identifier incorrect.
- MS03 — reason not specified.
Codes are grouped by prefix: AC for account issues, AM for amount issues, RC for routing, DT for dates, and so on. A rejection carries the code plus optional free-text detail. Designing around the code — not the free text — keeps handling reliable, because the text is not standardised while the code is.
Designing robust handling
- Validate early and cheaply. Run schema and usage-rule checks before submitting so you fail fast and avoid burning a rail round-trip on a preventable error.
- Map reason codes to actions. Decide up front which codes are retryable (transient), which need customer correction (bad account), and which are terminal (compliance block).
- Preserve the original references. Status reports link back to the original message and transaction identifiers; keep them so you can reconcile a rejection to the exact payment.
- Handle partial batch results. A pain.001 batch can be partly accepted and partly rejected; process at the transaction level, not just the group level.
- Never assume free text. Drive logic from the coded reason, and surface the text only for human operators.
Key takeaways
- ISO 20022 validation is layered: XSD syntax, scheme usage rules, then stateful business validation.
- Rejections and statuses are communicated with dedicated messages: pain.002, pacs.002, pacs.004, and camt.056.
- Status values like ACCP and RJCT signal outcome; a rejection must include a standardised reason code.
- Reason codes such as AC04 (account closed) and AM04 (insufficient funds) are grouped by prefix and are machine-readable.
- Robust handling validates early, maps codes to actions, preserves references, and handles partial batch results.