ISO 20022 message
pain.002 format: customer payment status report
pain.002 is the bank’s answer to a payment file (a pain.001 or pain.008): received, accepted, pending or rejected, for the whole file, each payment block and each transaction, with reason codes for anything refused. It is the first place a rejected payment shows up.
| Message | pain.002, root element <CstmrPmtStsRpt> |
|---|---|
| Name | Customer payment status report |
| Sent | Bank → account owner |
| Read by | Payment File Viewer |
Who sends it, and when
The bank sends it to the company that sent the file, usually soon after the upload (technical checks) and again as processing goes on. Banks differ in how many reports they send and which statuses they use; some report only rejections.
The structure of a pain.002
Document
└─ CstmrPmtStsRpt
├─ GrpHdr MsgId, CreDtTm, DbtrAgt
├─ OrgnlGrpInfAndSts the file: OrgnlMsgId, OrgnlMsgNmId, GrpSts, StsRsnInf, NbOfTxsPerSts
└─ OrgnlPmtInfAndSts a block: OrgnlPmtInfId, PmtInfSts
└─ TxInfAndSts a payment: OrgnlInstrId, OrgnlEndToEndId, TxSts,
StsRsnInf (Rsn/Cd, AddtlInf), OrgnlTxRefVersions in use
Version .001.03 (2009) and .001.10 (2019) are the most common, paired with pain.001.001.03 and .001.09. From .001.08 the statuses come from the ISO 20022 external code lists instead of fixed lists, and a transaction can carry more values, such as ACPD (accepted by the clearing system) and PATC (awaiting further signatures). The viewer reads .001.02 to .001.13.
An annotated pain.002
An excerpt of the sample the viewer opens (fictitious companies, published test account numbers):
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.002.001.10">
<CstmrPmtStsRpt>
…
<OrgnlGrpInfAndSts>
<OrgnlMsgId>NWT-PAY-20261028-03</OrgnlMsgId>
<OrgnlMsgNmId>pain.001.001.09</OrgnlMsgNmId>
…
<GrpSts>PART</GrpSts>
…
</OrgnlGrpInfAndSts>
<OrgnlPmtInfAndSts>
<OrgnlPmtInfId>PMT-20261028-03</OrgnlPmtInfId>
<PmtInfSts>PART</PmtInfSts>
…
<TxInfAndSts>
<StsId>STS-412-3</StsId>
<OrgnlInstrId>PMT-03-003</OrgnlInstrId>
<OrgnlEndToEndId>NW-2026-403</OrgnlEndToEndId>
<TxSts>RJCT</TxSts>
<StsRsnInf>
…
<Rsn>
<Cd>AC01</Cd>
</Rsn>
<AddtlInf>Account number does not exist at the creditor bank</AddtlInf>
</StsRsnInf>
…“…” marks lines left out.
| In the file | What it says |
|---|---|
<OrgnlMsgId> / <OrgnlMsgNmId> | Which file this answers: its MsgId and message version. |
<GrpSts>PART | Partly accepted: look at each transaction. |
<OrgnlEndToEndId>NW-2026-403 | The payment, by the end-to-end id you set in the pain.001. |
<TxSts>RJCT | This payment is rejected … |
<Cd>AC01 | … because the account number is wrong (AC01). |
Common validation errors
What most often goes wrong in a pain.002, and the rule that reports it in the viewer and the API:
pain.status.reason(Rejection without a reason): A rejection (RJCT) without a reason code.pain.status.mix(Group status does not match transactions): The group status contradicts the transaction statuses (ACCP for a file whose payments are all rejected, for example).pain.status.counts(Status counts differ): NbOfTxsPerSts counts that do not add up to the original number of transactions.
Check a pain.002 from a script or an AI agent
Scripts and AI agents can do the same without a browser: POST /api/v1/validate or the MCP tool validate_payment_file returns every issue with its rule id, severity, line and path, and whether the file is valid. No key is needed, and the file is processed in memory and never stored (API and MCP docs).
curl -s --data-binary @pain002-rejections.xml https://iso20022-navigator.com/api/v1/validate{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "validate_payment_file",
"arguments": {
"content": "<the file text>"
}
}
}