<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: ART</title>
    <description>The latest articles on Forem by ART (@artarna).</description>
    <link>https://forem.com/artarna</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3927370%2Ff6872d33-7a98-4b74-a858-d0c4dae34f94.jpeg</url>
      <title>Forem: ART</title>
      <link>https://forem.com/artarna</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/artarna"/>
    <language>en</language>
    <item>
      <title>Why I Built an Elixir ISO 20022 Parser (And why I hate banking standards)</title>
      <dc:creator>ART</dc:creator>
      <pubDate>Tue, 12 May 2026 21:07:52 +0000</pubDate>
      <link>https://forem.com/artarna/why-i-built-an-elixir-iso-20022-parser-and-why-i-hate-banking-standards-1h70</link>
      <guid>https://forem.com/artarna/why-i-built-an-elixir-iso-20022-parser-and-why-i-hate-banking-standards-1h70</guid>
      <description>&lt;p&gt;I'll be honest: I didn't build this library because I love ISO 20022. I built it because I had to work with it and I didn't find much in elixir to help with that.&lt;/p&gt;

&lt;p&gt;ISO 20022 is the global messaging standard for financial transactions - used by SWIFT, SEPA, the UK's CHAPS system, and increasingly everywhere that money moves between banks. It is also, and I say this with the confidence of someone who has spent way too long staring at it, an XML format that feels like it was designed by a committee. Slowly. Over twenty years. With no one in the room who had ever actually written a parser.&lt;/p&gt;

&lt;p&gt;This is the story of how I went from "I just need to parse one bank statement" to building &lt;a href="https://github.com/ARTARNA/ex_iso20022" rel="noopener noreferrer"&gt;&lt;code&gt;ex_iso20022&lt;/code&gt;&lt;/a&gt;, an open-source Elixir library for parsing and generating ISO 20022 messages. Against my better judgement.&lt;/p&gt;




&lt;h2&gt;
  
  
  It Started With a Real Problem
&lt;/h2&gt;

&lt;p&gt;I was working on a transaction processing system and needed to ingest camt.053 files - the ISO 20022 message type for end-of-day bank statements. A bank sends you one of these at the end of each business day and it contains every transaction that hit your account: amounts, counterparties, references, balances.&lt;/p&gt;

&lt;p&gt;Here's what it looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Document&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"urn:iso:std:iso:20022:tech:xsd:camt.053.001.02"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;BkToCstmrStmt&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;GrpHdr&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;MsgId&amp;gt;&lt;/span&gt;9327649450&lt;span class="nt"&gt;&amp;lt;/MsgId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;CreDtTm&amp;gt;&lt;/span&gt;2023-10-20T08:09:05.015Z&lt;span class="nt"&gt;&amp;lt;/CreDtTm&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/GrpHdr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Stmt&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Acct&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Id&amp;gt;&amp;lt;Othr&amp;gt;&amp;lt;Id&amp;gt;&lt;/span&gt;5000007775&lt;span class="nt"&gt;&amp;lt;/Id&amp;gt;&amp;lt;/Othr&amp;gt;&amp;lt;/Id&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Ccy&amp;gt;&lt;/span&gt;EUR&lt;span class="nt"&gt;&amp;lt;/Ccy&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/Acct&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Bal&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Tp&amp;gt;&amp;lt;CdOrPrtry&amp;gt;&amp;lt;Cd&amp;gt;&lt;/span&gt;CLBD&lt;span class="nt"&gt;&amp;lt;/Cd&amp;gt;&amp;lt;/CdOrPrtry&amp;gt;&amp;lt;/Tp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Amt&lt;/span&gt; &lt;span class="na"&gt;Ccy=&lt;/span&gt;&lt;span class="s"&gt;"EUR"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;12000.00&lt;span class="nt"&gt;&amp;lt;/Amt&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;CdtDbtInd&amp;gt;&lt;/span&gt;CRDT&lt;span class="nt"&gt;&amp;lt;/CdtDbtInd&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/Bal&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Ntry&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- individual transactions... --&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/Ntry&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Stmt&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/BkToCstmrStmt&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Document&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, &lt;code&gt;CdtDbtInd&lt;/code&gt;. Yes, &lt;code&gt;BkToCstmrStmt&lt;/code&gt;. Yes, &lt;code&gt;NtryDtls&lt;/code&gt;. The ISO 20022 working group apparently had a strict four-character budget for every field name and they certainly wouldn't waste it on vowels.&lt;/p&gt;

&lt;p&gt;To make it more fun: the standard has been revised many times. You might get a v02 from one bank and a v08 from another. They're subtly different in ways that will bite you exactly once in production and never again in development. I've found this with the different samples I've collected from Deutsche, Goldman, etc. &lt;/p&gt;




&lt;h2&gt;
  
  
  Why Elixir Is Actually Great For This
&lt;/h2&gt;

&lt;p&gt;I know, I know. But hear me out - despite the format being a nightmare, Elixir is genuinely a good fit for parsing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pattern matching makes XML parsing expressive.&lt;/strong&gt; Instead of nested conditionals checking node types and attributes, you write function clauses that read almost like the schema spec itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structs give you a clean data layer.&lt;/strong&gt; When you're done parsing, you want Elixir structs, not raw maps, not XML nodes dangling in memory. Something you can pass around, inspect in IEx, and pattern match on downstream without screaming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;{:ok, result} | {:error, reason}&lt;/code&gt; convention fits perfectly.&lt;/strong&gt; Bank messages are full of optional fields, missing data, and values that may or may not be present depending on the bank, the version, and apparently the phase of the moon. Elixir's error tuple pattern handles this without exceptions blowing up your pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-version support is a first-class problem in the language.&lt;/strong&gt; Function clauses dispatching on version while keeping the external API identical. Tidy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building the Parser
&lt;/h2&gt;

&lt;p&gt;The core of the library is a pipeline: raw XML string → parsed XML tree → Elixir structs.&lt;/p&gt;

&lt;p&gt;For the XML layer I used &lt;code&gt;SweetXml&lt;/code&gt;, which wraps Erlang's &lt;code&gt;:xmerl&lt;/code&gt; and gives you an XPath-like query interface. Here's a simplified version of how a transaction entry gets parsed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;ExIso20022&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Camt053&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Entry&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;defstruct&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:credit_debit_indicator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:booking_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:transaction_details&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;amount:&lt;/span&gt; &lt;span class="no"&gt;SweetXml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;~x"./Amt/text()"&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;parse_decimal&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="ss"&gt;currency:&lt;/span&gt; &lt;span class="no"&gt;SweetXml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;~x"./Amt/@Ccy"&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="ss"&gt;credit_debit_indicator:&lt;/span&gt; &lt;span class="no"&gt;SweetXml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;~x"./CdtDbtInd/text()"&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;parse_cdi&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="ss"&gt;status:&lt;/span&gt; &lt;span class="no"&gt;SweetXml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;~x"./Sts/text()"&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="ss"&gt;booking_date:&lt;/span&gt; &lt;span class="no"&gt;SweetXml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;~x"./BookgDt/DtTm/text()"&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;parse_datetime&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="ss"&gt;transaction_details:&lt;/span&gt; &lt;span class="n"&gt;parse_transaction_details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;parse_cdi/1&lt;/code&gt; function converts &lt;code&gt;"CRDT"&lt;/code&gt; and &lt;code&gt;"DBIT"&lt;/code&gt; to &lt;code&gt;:credit&lt;/code&gt; and &lt;code&gt;:debit&lt;/code&gt; atoms. Small thing, but it means downstream code can pattern match on &lt;code&gt;:credit&lt;/code&gt; rather than string-comparing &lt;code&gt;"CRDT"&lt;/code&gt; everywhere, because life is too short to write &lt;code&gt;== "CRDT"&lt;/code&gt; in 2026.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Multiple Versions
&lt;/h3&gt;

&lt;p&gt;This was the most annoying part, and I say that having dealt with the abbreviations. The namespace in the XML tells you which version you're looking at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;urn:iso:std:iso:20022:tech:xsd:camt.053.001.02  → v02
urn:iso:std:iso:20022:tech:xsd:camt.053.001.08  → v08
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I extract the version upfront and dispatch from there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;parse_xml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml_string&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
       &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;detect_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
       &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;parse_for_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;detect_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SweetXml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;~x"//@xmlns"&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Regex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;~r/camt\.053\.001\.(\d+)/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:unknown_namespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Versions v02 through v14 are supported for camt.053. Most field paths are stable across versions, but the later ones add enough new wrinkles around charges and tax details that pretending versions don't exist will eventually hurt you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Goldman Sachs Test Fixture Moment
&lt;/h2&gt;

&lt;p&gt;One of the most useful things I did was grab the sample camt.053 file from &lt;a href="https://developer.gs.com/docs/services/transaction-banking/camt-053-uk-via/" rel="noopener noreferrer"&gt;Goldman Sachs' public developer portal&lt;/a&gt;. It's a real-world example from an actual bank covering a parent DDA account and several virtual sub-accounts. Immediately I found edge cases I hadn't considered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-IBAN account IDs.&lt;/strong&gt; The Goldman sample uses &lt;code&gt;&amp;lt;Othr&amp;gt;&amp;lt;Id&amp;gt;&lt;/code&gt; rather than IBAN for account identification. Very common in the US, easy to miss entirely if you only tested against European bank samples like I had been.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proprietary account types.&lt;/strong&gt; Virtual accounts carry types like &lt;code&gt;VIRTUAL_SUSPENSE&lt;/code&gt; and &lt;code&gt;VIRTUAL_FEE&lt;/code&gt; - not ISO standard codes, just whatever strings Goldman felt like using. The parser had to handle these without choking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Six statements in one document.&lt;/strong&gt; A parser that implicitly assumed one statement per document would silently discard five of them. No error, no warning. Just missing money. Great.&lt;/p&gt;

&lt;p&gt;The lesson: use real bank-published samples as test fixtures. It is not the same as having production data, but it is a lot closer to reality than synthetic XML you wrote yourself while optimistically assuming banks follow the spec. I have thankfully been burned like this so I was used to the expectation. Though the brazenness I did not expect.&lt;/p&gt;




&lt;h2&gt;
  
  
  Expanding to camt.052
&lt;/h2&gt;

&lt;p&gt;Once camt.053 was solid, camt.052 was the obvious next step. Where camt.053 is the end-of-day statement, camt.052 is the intraday report, same basic structure, but delivered multiple times throughout the business day with pending and booked transactions mixed together.&lt;/p&gt;

&lt;p&gt;This sounds simple. It is not quite simple.&lt;/p&gt;

&lt;p&gt;The main differences that matter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transaction statuses are richer.&lt;/strong&gt; You'll see &lt;code&gt;PDNG&lt;/code&gt; (pending) alongside &lt;code&gt;BOOK&lt;/code&gt; (booked), and pending transactions may not yet have a value date — because the bank is telling you about money that might arrive, not money that has definitely arrived.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Balances are provisional.&lt;/strong&gt; The opening balance can change as the day progresses. You need to track &lt;code&gt;ITBD&lt;/code&gt; (intraday booking) balance types alongside the standard &lt;code&gt;OPBD&lt;/code&gt;/&lt;code&gt;CLBD&lt;/code&gt;, and you need to not confuse "current best guess" with "settled fact."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Messages arrive in sequence — theoretically.&lt;/strong&gt; Each camt.052 carries an &lt;code&gt;ElctrncSeqNb&lt;/code&gt; (electronic sequence number). In practice, out-of-order delivery happens, and if your system assumes messages always arrive in order you will eventually have a very bad Tuesday.&lt;/p&gt;

&lt;p&gt;The upside is that the external API looks identical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ExIso20022&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Camt053&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ExIso20022&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Camt052&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complexity is inside the library, which makes me happy.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The library currently covers camt.052 and camt.053 parsing. The roadmap I'm working toward:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pain.001&lt;/strong&gt; - the credit transfer initiation message. Where camt is about receiving information from your bank, pain.001 is about sending payment instructions to it. This unlocks the full outbound payment flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pacs.008&lt;/strong&gt; - interbank credit transfers used in SWIFT CBPR+ (the cross-border payments standard). More complex, but increasingly relevant as SWIFT's ISO 20022 migration accelerates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation against XSD schemas&lt;/strong&gt; - right now the parser is tolerant of missing optional fields. Stricter validation mode with proper error paths would be useful for testing your own message generation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generation, not just parsing&lt;/strong&gt; - building camt/pain messages, not just reading them. Useful for anyone building bank simulators or test harnesses.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The library is on &lt;a href="https://hex.pm" rel="noopener noreferrer"&gt;Hex.pm&lt;/a&gt; and &lt;a href="https://github.com/ARTARNA/ex_iso20022" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. If you're working with ISO 20022 messages in Elixir and find a message type or edge case it doesn't handle, open an issue, I'm happy to take a look.&lt;/p&gt;

&lt;p&gt;The standard is enormous and no library covers it completely. But if we can make the common cases - the bank statements, the intraday reports, the payment initiations - boring and reliable, then it feels like a genuinely useful contribution to the Elixir fintech ecosystem. It was for me at least.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ExIso20022&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Camt053&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;your_bank_xml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# That should just work.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the goal.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Art is a backend engineer building financial infrastructure. &lt;a href="https://github.com/ARTARNA/ex_iso20022" rel="noopener noreferrer"&gt;&lt;code&gt;ex_iso20022&lt;/code&gt;&lt;/a&gt; is open source under the MIT license.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>fintech</category>
      <category>banking</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
