·11 min read

Per-bank ISO 20022 dialect drift — the unaccountable moat

ISO 20022 is a single standard; every bank implements a slightly different dialect. The catalog of those differences is a moat that does not show up on a feature comparison sheet.

ISO 20022 looks, on the surface, like a single standard. One XSD per message version. One reason-code catalog. One <PstlAdr> grammar. A naive integration would expect that an emitter validated against the pain.001.001.09 schema would be accepted by every bank that consumes pain.001.001.09.

In production: it is not. Every bank consumes a slightly different dialect of the standard. The CGI-MP profile gets you 80% of the way; the remaining 20% is per-bank-specific. Worse, the 20% is invisible until you submit, and the rejects come back as pain.002 reason codes that nominally say "incorrect format" without specifying which field.

Examples of dialect drift

A non-exhaustive sample of real-world per-bank quirks (sources: published bank Implementation Guidelines, customer-side UAT logs, ISO 20022 RMG correspondence):

  • UBS Switzerland requires <PmtInf>/<NbOfTxs> to match the count of <CdtTrfTxInf> children exactly. PostFinance requires it at both the <PmtInf> and <GrpHdr> levels and rejects mismatches with a generic AC04.
  • Deutsche Bank promotes <Cdtr>/<CtryOfRes> as a recommended field for tax-relevant cross-border payments. BNP Paribas does not look at it.
  • JPMorgan strips <EndToEndId> on certain inter-branch hops; the durable reference becomes <InstrId>. HSBC routinely preserves <EndToEndId>.
  • Crédit Agricole accepts unstructured <Ustrd> remittance with embedded SCOR references; UBS demands a structured <Strd>/<CdtrRefInf>/<Ref> for SCOR.
  • Citi WorldLink uses <AcctSvcrRef> as the durable identifier on FX-converted-at-source flows; the original <EndToEndId> is not preserved.
  • Bank of America CashPro emits the inbound camt.053 <BkTxCd>/<Domn>/<Cd> in a slightly different code-list than the CGI-MP profile; downstream reconciliation engines that hard-code the CGI-MP set silently miss BofA entries.

Each of these is a single line of customer-side code (or a single rule-pack toggle). The cost is the discovery: the customer learns about the quirk after a batch rejects, after the cash-application engine produces N false-negative unmatched entries, after a treasury reconciliation falls behind by two weeks.

Why the dialect drift exists

Three causes:

  • 1. Pre-ISO-20022 history. Most banks had a fixed-format text protocol (Swift MT, German DTAUS, US NACHA, UK Standard 18) before they ran ISO 20022. The ISO 20022 implementation often started as a mapping from the legacy schema, with the legacy quirks preserved.
  • 2. Internal-system constraints. The ISO 20022 message is parsed by the bank's payment-engine which is itself a multi-decade-old mainframe / midrange stack. The dialect quirks reflect what the engine can / cannot represent natively.
  • 3. Local rail overlays. SEPA, Swiss SIX QR-bill, UK FPS, Japan Zengin all layer local rules on top of the ISO 20022 base. A bank that participates in multiple rails has to merge the overlays — and the merge is bank-specific.

These causes do not disappear. The CBPR+ migration smooths the surface but does not eliminate it. A decade from now, banks will still have dialect drift.

Why this is a moat

The catalog of per-bank quirks is built by the iso-compliant rule-pack registry. Each accepted bank UAT round seeds the registry with one more pack. The packs compound: every fix is a permanent improvement.

A new entrant has to rebuild that catalog from scratch — and the catalog is not in any public document; it is in the head of the bank's implementation team and in the tribal knowledge of customers who have lived through the UAT cycle.

This is the durable moat. It does not show up on a feature comparison sheet because it is invisible to a buyer until they ship.

How rule packs work

A rule pack is a versioned JSON descriptor pinned to a bank slug + a date. Example: ubs-ch@2026.06. The pack carries:

  • Field-level overrides — e.g. <PmtInf>/<NbOfTxs> must equal the count of child <CdtTrfTxInf>.
  • Character-set overrides — e.g. <Nm> may not contain accented characters.
  • camt.053 carrier overrides — e.g. AcctSvcrRef is promoted to high confidence; EndToEndId is demoted to medium.
  • Reason-code reclassifications — e.g. UBS-specific XT01 maps to auto_retry; BNP-specific XT04 maps to hitl_required.

Customers pin a rule pack via the bank_ruleset request parameter. The builder at apps/api/src/lib/pain001-builder.ts enforces the pack rules at emission; the camt.053 parser at apps/api/src/lib/camt053-parser.ts re-ranks candidate carriers per the pack.

Today the registry ships with a SKELETON pack for ubs-ch. The Tier-2 development scope is the first two production packs — UBS Switzerland and PostFinance Switzerland — targeted for 2026-Q3.

← All posts