A pacs.008 sent over Swift CBPR+ must carry exactly one transaction, a mandatory UETR and a charge bearer of DEBT, CRED or SHAR. Send the same payment into SEPA and the only charge bearer allowed is SLEV, which CBPR+ rejects. Same message name, same version number (pacs.008.001.08), and the two rulebooks cannot accept one shared value for a field as basic as who pays the fees.
That is the part the ranking explainers for "pain.001 vs pacs.008" leave out. They describe the ISO catalogue. Your code talks to a market practice, and every rail has its own.
What Is the Difference Between pain.001 and pacs.008?
pain.001 is what a customer sends to its bank. pacs.008 is what banks send to each other. The rest of the differences follow from that.
| pain.001 (Customer Credit Transfer Initiation) | pacs.008 (FI to FI Customer Credit Transfer) | |
|---|---|---|
| Sender | Corporate, fintech, PSP client | Debtor agent, intermediary agent, clearing system |
| Receiver | Debtor's bank | Next agent or clearing system |
| Structure | Group header → payment information blocks → transactions | Group header → transactions |
| Batching | Many transactions per payment-information block, sharing a debtor account and execution date | CBPR+ fixes NbOfTxs at 1; clearing schemes such as SEPA batch |
| Settlement data | None; you request an execution date | Interbank settlement amount and date |
| Status response | pain.002 | pacs.002 |
| Typical SEPA version | pain.001.001.09 | pacs.008.001.08 |
If you are a fintech without direct scheme access, you almost certainly produce pain.001 (or a bank's JSON wrapper around it) and never see a pacs.008 until something goes wrong and a bank forwards you one during an investigation. If you are a direct participant in FedNow, CHAPS or a SEPA clearing house, pacs.008 is your payment order, and pain.001 is what your own corporate customers send you.
ISO 20022 Message Types Explained: pain, pacs and camt
The four-letter prefix is the business area. For payments work you need three.
- pain (payments initiation):
pain.001initiation,pain.002status report,pain.013/pain.014request-to-pay and response. - pacs (payments clearing and settlement):
pacs.008customer transfer,pacs.009bank-to-bank transfer,pacs.004return,pacs.002status,pacs.028status request. - camt (cash management):
camt.052intraday report,camt.053end-of-day statement,camt.054debit/credit notification,camt.056recall request,camt.029resolution of investigation.
pacs.008.001.08 is variant 001, version 08. Version numbers matter more than the explainers suggest, because the big infrastructures did not all pick the same ones.
Which ISO 20022 Versions Do SEPA, Fedwire, FedNow and CHAPS Use in 2026?
| Rail | Customer transfer | Return | Status | Notes |
|---|---|---|---|---|
| SEPA SCT and SCT Inst | pacs.008.001.08 | pacs.004.001.09 | pacs.002.001.10 | 2025 implementation guides, effective 5 October 2025 |
| Swift CBPR+ | pacs.008.001.08 | not checked | not checked | Single-transaction profile, UETR mandatory |
| Fedwire Funds | pacs.008.001.08 | pacs.004.001.10 | not checked | Live 14 July 2025; header head.001.001.03 |
| FedNow | pacs.008.001.08 | pacs.004.001.10 | pacs.002.001.10 | Per the Fed's readiness guide (v1.3, 2022) |
| CHAPS | pacs.008.001.08 | pacs.004.001.09 | pacs.002.001.10 | Live 19 June 2023; header head.001.001.02 |
Look at the return column. The Federal Reserve built Fedwire on the 2019 message versions with one exception, pacs.004.001.10 from 2020. CHAPS and the EPC stayed on .09. So if you run a returns service that handles both GBP and USD, you are maintaining two schemas for the same business event, and your XSD validation needs to be selected by rail, not by message name. The business application header differs too: head.001.001.02 in CHAPS, .03 in Fedwire.
FedNow has its own quirks. It has no REST endpoint for sending a payment, and its pacs.009 is for liquidity transfers only.
CBPR+ vs SEPA: Field Rules That Break a Shared Payment Model
Most teams start with one internal payment object and serialise it to whichever rail a payment is routed over. This table shows where that breaks. Both columns describe pacs.008.001.08.
| Element | Swift CBPR+ | SEPA Credit Transfer |
|---|---|---|
ChrgBr | DEBT, CRED, SHAR | SLEV only |
UETR | Mandatory (1..1), UUID v4 | Optional (0..1), same UUID v4 pattern |
EndToEndId | 1–35 chars; NOTPROVIDED if the originator gave none | 1–35 chars |
Ustrd (unstructured remittance) | Restricted to one occurrence, 140 chars | 140 chars |
| Character set, references | a-z A-Z 0-9 / - ? : ( ) . , ' + and space | Same Latin set; IDs cannot start or end with / or contain // |
| Character set, names and addresses | Extended set adds ! # $ % & * = ^ _ ~ " ; < > @, braces, square brackets, backslash and the vertical bar | Latin set only |
AdrLine | ISO allows 7, CBPR+ allows 3 at 70 chars; 35 chars per line if the address is unstructured only | Unstructured-only addresses not allowed from 22 November 2026 per the 2025 guide |
| Hybrid address minimum | TwnNm and Ctry mandatory, at most 2 AdrLine | TwnNm and Ctry |
The UETR regex is stricter than "any UUID". CBPR+ enforces version 4 with the variant nibble in [89ab]:
[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}
Lowercase only. A UUID v7 fails it, and so does any library that emits uppercase hex. Switching an ID generator to v7 for index locality is a sensible-looking database change that would break every cross-border payment, and it would sail through domestic testing on a rail where UETR is optional.
The character sets bite differently. A customer name like O'Brien & Sons is valid in CBPR+ (both ' and & are in the extended set) but the & fails SEPA validation. Mapping that ampersand to + or and has to happen per rail, at egress, and never in the stored customer record.
The 35-character line limit for unstructured CBPR+ addresses exists because the MT format it replaced used 35-character lines. When Swift's in-flow translation squeezes an MX into an MT for a receiver that still wants FIN, fields that do not fit are truncated and marked with a trailing +. If you reconcile against inbound MTs from a correspondent, a + at column 35 is not data. It is a flag telling you data was lost.
pacs.002 and pain.002 Status Codes: ACSP vs ACSC vs ACCC
The ISO external code set defines the transaction status ladder. These are the codes that show up in production:
| Code | Meaning | Has money moved? |
|---|---|---|
RCVD | Received | No |
ACTC | Authentication and syntax/semantic validation passed | No |
ACCP | Technical validation and customer profile check passed | No |
ACSP | All checks passed, accepted for execution, settlement in process | No |
ACSC | Settlement completed | Interbank leg, yes |
ACCC | Settlement on the creditor's account completed | Beneficiary credited |
ACWC | Accepted with change | Depends on what changed |
ACWP | Accepted, not posted to the creditor | Not to the beneficiary |
PDNG | Pending | No |
BLCK | Blocked, after ACWP | Funds held |
RJCT | Rejected | No |
The expensive mistake is mapping ACSP to "paid". It means the bank has accepted the instruction and settlement is in progress. ACSC means the interbank leg settled. Only ACCC tells you the beneficiary's account was credited, and plenty of banks never send it. If your product shows "Payment complete" to users, key it off ACSC at the earliest and document the gap.
pain.002 is reject-only. The EPC customer-to-PSP guide allows just RJCT, at group, payment-information or transaction level. In the scheme-standard flow, silence is success. Build a state machine that waits for a positive pain.002 and your SEPA payments sit in PENDING forever. Individual banks can offer positive reports through their own bilateral profiles, so confirm the behaviour per bank, in writing, before go-live.
ISO 20022 Reason Codes for Rejects, Returns and Recalls
When a status is RJCT or a pacs.004 arrives, the StsRsnInf or return reason carries an external code. The ones worth mapping to specific user-facing messages:
AC01account number invalid or missingAC04account closedAC06account blockedAM04insufficient fundsAM05duplicationBE01customer identification inconsistent with accountFF01invalid file formatMS02/MS03not specified, by customer / by agentRR01–RR04regulatory reasonsRC01BIC incorrect
AM05 deserves special handling. It is a scheme telling you your own idempotency controls failed, and it should page someone rather than show a retry button.
SEPA narrows these further. A camt.056 recall may only give DUPL, TECH or FRAD as its reason. A negative camt.029 answer is restricted to AC04, AM04, ARDT, CUST, LEGL, NOAS or NOOR. The recall flow runs camt.056 → pacs.004 if accepted, or camt.056 → camt.029 if refused, with pacs.028 as the chaser. A pacs.004 is a new credit transfer travelling back, not a reversal of the original entry. Your ledger should post it as a new inbound movement linked to the original, not un-post the outbound one.
MT103 to pacs.008: What Replaced Each Swift MT Message
MT/MX coexistence for cross-border payments ended on 22 November 2025. The mappings, per BNY's end-of-coexistence notice:
| Legacy MT | ISO 20022 replacement |
|---|---|
| MT103, MT103 STP | pacs.008 |
| MT200, MT202, MT205 | pacs.009 |
| MT202 COV, MT205 COV | pacs.009 COV |
| MT103/202 return | pacs.004 |
| MT103/202 reject | pacs.002 |
| MT940, MT950 | camt.053 |
| MT941, MT942 | camt.052 |
Two details from that notice matter more than the table. MT102, MT102 STP, MT103 REMIT, MT201 and MT203 were withdrawn outright. And the statement and customer-initiation messages (MT101, MT900, MT910, MT940, MT942, MT950 among them) stay on FIN "until an end date is defined by Swift". So your treasury integration probably still parses MT940 while your payments integration emits pacs.008. Swift also began charging for in-flow MX-to-MT translation in January 2026, which gives banks still asking to receive MTs a reason to stop, even though no end date has been set.
CHAPS ISO 20022 Enhanced Data Dates in the UK
CHAPS went live on ISO 20022 on 19 June 2023 and has layered data mandates on top:
- 1 May 2025: LEIs and purpose codes mandatory for payments between financial institutions, plus six property-transaction purpose codes.
- November 2027: structured remittance data mandatory, purpose codes on all CHAPS payments, LEIs on
pacs.004andpacs.009. - November 2026: rejection of unstructured addresses. On 27 August 2026 the Bank of England said its November 2026 RTGS standards release "has therefore been deferred in its entirety", following Swift's deferral of every payments change in Standards Release 2026.
Should You Build One Canonical ISO 20022 Payment Model?
My position: yes, one internal model, but never one serialiser. Build the canonical payment as a superset of the ISO elements you use, sized to the most generous limits (140-char names, full structured address, UETR always populated). Then put a rail profile at egress that owns charge bearer, character transliteration, cardinality, address form and message version. Validate against the rail's own XSD and usage guideline at that layer, and persist the exact XML you sent and received next to the canonical record.
Two reasons. First, the mandates are diverging, not converging. Swift's payments timeline is unknown until December, Fedwire and CHAPS both point at November 2027, and SEPA's address date has not moved. Those are three separate calendars, and a single global validator cannot follow three. Second, disputes are argued over the message, not your model. When a correspondent says your address arrived truncated, the stored pacs.008 settles it in minutes.
I'd also generate a UETR for every payment on every rail, including SEPA where it is optional. It is the only identifier designed to survive every hop, and populating it costs nothing.
My prediction is that per-rail divergence gets worse through 2027, not better. The December Swift update will set a new CBPR+ date, the domestic infrastructures will each pick their own response, and "ISO 20022 compliant" will mean even less as a single claim than it does today.
What This Means for Payment Engineers
1. Key XSD selection by rail and message version, not message name. pacs.004.001.09 and .10 coexist in production today.
2. Test your UUID generator against the CBPR+ UETR regex. Lowercase, version 4, variant [89ab]. Add it as a unit test so a library upgrade cannot break it silently.
3. Transliterate at egress, per rail. Keep the customer's original name in storage; produce SEPA-safe Latin output only when serialising.
4. Map ACSP to "processing", never "paid". Decide explicitly whether ACSC or ACCC drives your completed state, and write down which banks never send ACCC.
5. Confirm positive pain.002 behaviour with each SEPA bank. The scheme default is reject-only.
6. Post pacs.004 as a new inbound entry. Do not reverse the original outbound posting.
7. Treat a trailing + in an inbound MT field as truncation. Flag the record for review instead of reconciling on it.
8. Put the SEPA 22 November 2026 address date in your plan until the EPC says otherwise, and keep hybrid address output configurable per rail and per counterparty.
None of this is in the ISO catalogue, because the catalogue describes messages and the rulebooks describe payments. If you'd like to compare notes on building rail profiles, Tom Wang is the easiest place to find me.