How it is built
Architecture overview
Interbank messaging is the postal system of finance: a bank sends a structured, authenticated message (a customer credit transfer, a confirmation, a status report) to another bank, and the network guarantees that it arrives once, unaltered, from the sender it claims to come from, and with a record of what happened. It moves instructions, not money; settlement happens elsewhere. The network is a store-and-forward system: a sender hands a message to the network, the network durably stores it, validates it, and delivers it to the receiver when the receiver is connected, with acknowledgements both ways. Around that core sit a security layer (certificates and keys in hardware modules, signatures on every message), a format layer (the older MT messages and the newer ISO 20022 XML messages, with translation between them), a tracking layer (a unique end-to-end reference so a payment can be followed across the banks that handle it) and a screening layer (names checked against sanctions lists before release). The idea that makes it work is that every message is a durable, ordered, identified event: if you can replay the log and each message has one unchanging identity, you can deliver once, track, audit and recover.
Parts:
- Access and security — participants connect over authenticated channels; keys live in hardware — Mutual TLS, X.509 certificates (no page yet), Hardware security modules, ECDSA signatures, RSA cryptosystem (no page yet), Message authentication codes, Crypto hash SHA-2 and BLAKE3, AES key wrap (no page yet), Secure memory zeroization, CSPRNG (no page yet), Dual-control approval (no page yet)
- Message formats and validation — MT field syntax, ISO 20022 XML, schemas — ISO 20022 messages (no page yet), XML streaming parser, JSON Schema validation, Protobuf wire format (no page yet), Varint / LEB128
- Translation — MT to MX and back, field by field — ISO 20022 messages (no page yet), TDL message translation (no page yet, the same pattern in another field)
- Store-and-forward core — durable ingest, ordering, delivery with retry — Partitioned commit log, Segmented log, Consumer offsets, Durable job queue, Dead-letter queue, Exponential backoff with jitter, Write-ahead log, fsync and direct I/O, Zero-copy I/O, Fast hash functions
- Exactly-once delivery and state — one effect per message across retries and crashes — Exactly-once processing (no page yet), Idempotency keys, Event deduplication, Transactional outbox, Change data capture, Two-phase commit, Optimistic concurrency control, Message sequence numbers
- Replication and failover — operating centres that survive loss of one — Primary-backup replication, Leases, Fencing tokens, Health checking
- End-to-end tracking — one identifier per payment, status from every hop — Distributed tracing, Distributed ID generation, Event sourcing, Incremental sync cursors, Telemetry sampling (no page yet), Token bucket, Columnar storage, Vectorized execution,
publish-subscribe-event-bus(no page yet) - Audit and non-repudiation — a log nobody can quietly edit — Tamper-evident audit log, Merkle tree (no page yet), Ed25519 signatures, Trusted timestamping (no page yet), CRC32C checksums
- Screening — names and text matched against sanctions lists before release — Aho-Corasick, Fuzzy string matching (no page yet), Phonetic name matching (no page yet), Name transliteration (no page yet), SIMD programming
bank A back office ─► gateway (mTLS, HSM signs) ─► validate (syntax, schema) ─► screening ─► ingest log
│ NAK │ hit: hold
▼ ▼ release by two people
reject to sender durable store (replicated)
│ assign sequence no.
translate MT ↔ MX ◄────────┤
▼
delivery queue per receiver ─► gateway ─► bank B back office
│ retry/backoff, DLQ │ ACK / status
▼ ▼
audit log (signed batches) tracker: UETR → status events
How the parts fit together
Access and security
A participant does not connect from the open internet. It runs a gateway in its own network that talks to the network over a Mutual TLS channel, so both ends prove an identity with X.509 certificates (no page yet; chains, validity and revocation matter more here than in a web service). Every business message is additionally signed by the sending institution, because the channel proves who connected, not who authored a particular message, and a signature is evidence that survives the connection. Signatures use public-key schemes such as ECDSA or RSA (RSA cryptosystem, no page yet); a shorter, symmetric authenticator (Message authentication codes) protects messages between parties that share a key. Private keys never leave a Hardware security module: the gateway sends a digest (Crypto hash SHA-2 and BLAKE3) and the module returns a signature. Keys are moved between modules wrapped under another key (AES key wrap, no page yet); any copy in process memory is overwritten after use (Secure memory zeroization); keys and nonces come from the operating system's generator (CSPRNG, no page yet). Key ceremonies and the release of a new certificate require two people (Dual-control approval, no page yet).
Message formats and validation
The classic format is a text message in numbered blocks with tagged fields of limited length and a restricted character set; the newer one is ISO 20022, a family of XML schemas for payments, cash management and securities (ISO 20022 messages, no page yet). An MX message is far richer: structured party and address data, remittance information, purpose codes. The network validates before it accepts. For MT that is field syntax against a rule table. For MX it is schema validation, done streaming (XML streaming parser) so a large batch file is checked in constant memory and the few fields the router needs (sender, receiver, type, reference) are read without building a tree. APIs next to the messaging channel (status queries, tracker updates) use JSON Schema validation on untrusted documents. Internal service calls can use a compact binary encoding (Protobuf wire format, no page yet, with Varint / LEB128 integers), but the bytes that get signed are the canonical bytes of the business message, never a re-encoding. A rejected message returns a negative acknowledgement with a reason code, and is never stored as accepted.
Translation
While banks move at different speeds, the network carries both formats and translates between them in flight. MT to MX is mostly mapping: a field becomes one or more elements. MX to MT is lossy, since the old format cannot hold what the new one can (field lengths, a smaller character set, unstructured addresses), so translation rules truncate, fold or move data to narrative fields, and the receiver may get a message with less information than the sender wrote. A sound design keeps the original message with the translated one so the loss can be audited. The same field-level mapping problem appears for bit-packed military data links (TDL message translation, no page yet): a table of source field, destination field and conversion rule, with a defined behaviour for fields that do not fit.
Store-and-forward core
The core is a replicated, partitioned, append-only log, the pattern of a Partitioned commit log: a message is appended to the partition of its receiver (a hash of the receiver key keeps order per receiver), stored in Segmented log files, and acknowledged to the sender only after it is durable on enough replicas (Write-ahead log with the flush rules of fsync and direct I/O). Each receiver is a consumer with its own position (Consumer offsets), so a receiver that was offline for hours resumes where it stopped. Delivery uses a Durable job queue: the delivery worker leases a message, sends it, and acknowledges only when the receiver's gateway confirms. If the receiver stays unreachable, the worker backs off (Exponential backoff with jitter); a message that can never be delivered (invalid receiver, repeated rejection) goes to a Dead-letter queue for an operator, not dropped. Each message carries a session sequence number so gaps and repeats are detectable (Message sequence numbers). On the data path, large files avoid copies with Zero-copy I/O, per-record integrity uses CRC32C checksums, and routing keys are hashed with a Fast hash function. The sender gets a delivery notification only when the message is accepted by the receiver's side.
Exactly-once delivery and state
A network cannot deliver "exactly once"; it can deliver at least once and make duplicates harmless, so the effect happens once. This is Exactly-once processing (no page yet) built from smaller blocks:
- The sender gives each message a unique reference, used as an Idempotency key: a retry with the same reference returns the earlier result and does not create a second message.
- The receiver side keeps a window of recent identifiers (Event deduplication) and drops repeats.
- A state change and its outgoing message are written in one local transaction with a Transactional outbox, and a relay publishes from the table; with Change data capture the relay reads the database's own log instead of polling. This avoids the lost-message and the phantom-message cases of "update, then send".
- Where two stores must commit together (for example the log and the account of a participant), use Two-phase commit; its cost is why most state is instead updated by idempotent events.
- Message state (queued, delivered, acknowledged) changes under Optimistic concurrency control with a version check, so two workers cannot both mark a message delivered.
Replication and failover
The network runs in more than one operating centre and must survive the loss of one without losing an accepted message. That is Primary-backup replication: one centre orders the log and ships it to the other, and the acknowledgement rule (the other centre has it) decides how much a failover can lose. A Lease bounds how long a node believes it is the primary, Fencing tokens make a demoted primary's late writes fail at the storage layer, and Health checking with hysteresis decides when to fail over so a slow link does not cause flapping. A participant's own gateway is usually duplicated too and the network treats the pair as one identity.
End-to-end tracking
A cross-border payment passes through several banks, each of which can wait, hold or reject it, and the sender used to
learn nothing until it was over. The tracking layer gives every payment one unique end-to-end transaction reference
(UETR), created by the originating bank and kept unchanged by every later bank. The prowide-core library documents
it as mandatory for gpi payment messages, generated as a random UUID, carried in MT block 3 field 121 (Sources). It
works like a trace identifier (Distributed tracing with Distributed ID generation):
each bank reports status events (accepted, forwarded, credited, rejected) against the reference, and the tracker joins
them into one timeline. Internally the tracker is Event sourcing: the status is a fold over the
immutable event stream, so a correction is a new event. Banks and their customers read it with
Incremental sync cursors (give me everything since this token), protected by a
Token bucket per client; analytics over the history (who is slow, which corridors fail) run on
Columnar storage with Vectorized execution. The tracker's own telemetry
is sampled deterministically by payment reference (Telemetry sampling, no page yet) so all
events of one payment are kept or dropped together.
publish-subscribe-event-bus (no page yet) distributes the status events once they are recorded: each hop's report is published on a topic keyed by the payment reference, and the tracker, the customer-facing notification service, sanctions case management and analytics subscribe independently. A slow subscriber cannot hold up the payment path, because the event store remains the source of truth and a subscriber that falls behind reads on from its own position.
Audit and non-repudiation
A dispute asks: did this bank send this message, at this time, with this content? The audit trail answers it. All messages and their lifecycle events are appended to a Tamper-evident audit log: batches are hashed into a Merkle tree (no page yet) whose root is signed with a key held in the HSM, and any entry is proven by its path to a signed root. An optional Trusted timestamping (no page yet) from a third-party authority shows the root existed at a time. For fast signature verification across many roots, Ed25519 signatures are a compact option.
Screening
Payments must not reach sanctioned parties, so names and free text in the message are matched against watch lists before release. The system must find variants: misspelling, reordering, transliteration from another script, and partial matches. The blocks are Name transliteration (no page yet) to a common script, Phonetic name matching (no page yet) and Fuzzy string matching (no page yet) for variant spellings, and Aho-Corasick to find many list entries at once in a long narrative field in one pass. Matching is data parallel and can use SIMD programming for the distance kernels. A hit does not drop the payment: it puts it on hold, and a trained analyst (with a second approver for release) decides, with the decision recorded in the audit log.
Data and control flow
One payment instruction:
- The originating bank's back office creates the message with a new UETR and passes it to its gateway.
- The gateway signs the message with a key in the HSM and sends it over mutual TLS; the network checks the certificate, the signature and the sender's authority for that message type.
- The network validates syntax or schema; a failure returns a negative acknowledgement with a reason, and nothing is stored.
- Screening runs on names and narrative; a hit holds the message, a clean message proceeds.
- The message is appended to the replicated log (with a sequence number) and the sender gets a positive acknowledgement: from this moment the network owns delivery.
- If the receiver uses the other format, the translator produces the translated message and keeps the original.
- The delivery worker leases the message, sends it to the receiver's gateway, and on the receiver's acknowledgement marks it delivered; failures back off and retry.
- Status events with the UETR go to the tracker; the lifecycle events go to the audit log. Most of the latency is the durable write and the replication, plus the screening on the slowest messages.
Recovery after a centre failure:
- Health checks mark the primary dead; the lease expires.
- The standby takes over with a new epoch (fencing token); the storage layer rejects writes with the old one.
- Gateways reconnect; each side resends from its last acknowledged sequence number.
- Deduplication drops anything that was delivered but not acknowledged; the delivery queue resumes.
- Messages that were in flight and not durable are known from the sequence gap and re-sent by the sender.
Scaling
- Message rate. The core scales by partitioning the log by receiver; add partitions and consumers. Within a partition, batch appends and flush per batch (fsync and direct I/O).
- Screening latency. The slowest stage per message. Levers: normalise and index lists once, exact-match shortcuts for repeat customers, parallel match kernels, risk-based routing to the heavy matcher.
- Peak days. Cut-offs and end-of-day windows create bursts. Levers: queue depth as a buffer, per-participant rate limits (Token bucket), priority classes for urgent messages.
- Tracker queries. Read-heavy; serve from a materialised view and use cursors instead of polling.
- Geography. Latency between centres sets the cost of synchronous replication; the lever is where the standby sits and which acknowledgement rule you pay for.
- Format migration. Running two formats doubles test cases; invest in a translation test corpus before volume.
Failure modes
| Failure | Symptom | Cause | Fix |
|---|---|---|---|
| Duplicate delivery | receiver processes a payment twice | retry without an idempotency key, or lost acknowledgement | unique reference, dedup window at the receiver |
| Lost message | sender sees ACK, receiver never gets it | acknowledged before the durable write | acknowledge only after replicated append |
| Split brain | two centres both accept messages | no lease or fencing | lease with timeout, epoch checked at storage |
| Silent truncation | data missing after MX to MT translation | field length or charset limits | translation rules with warnings, keep the original, receiver can request it |
| Replay of a captured message | old instruction accepted again | no sequence or timestamp check | per-session sequence numbers and duplicate detection of references |
| Key compromise | forged messages or loss of trust | key outside HSM, weak custody | keys only in HSM, dual control, rotation and revocation process |
| Poison message | one message blocks a queue | repeated failing delivery | attempt limit, DLQ, operator review |
| Tracker gap | a bank's status missing from the timeline | status event lost or late | outbox plus idempotent events; mark as unknown, not as failed |
| Screening false negatives | sanctioned name passes | script or spelling variant not normalised | transliteration, phonetic and fuzzy keys, test with known variants |
| Screening backlog | payments held for hours | match latency, analyst queue | tuned thresholds, risk-based routing, staffing, never auto-release |
| Audit gap | cannot prove a past message | log not signed, retention too short | signed batches, timestamping, retention policy |
Real implementations
The large networks (SWIFT FIN and ISO 20022 services, gpi) are proprietary. Open projects cover message models and sessions.
| name | what it is | repo | licence | notes |
|---|---|---|---|---|
| Prowide Core | Java model and parser for SWIFT MT (FIN) messages, builders, MT/JSON/XML conversion, BIC and IBAN validation; includes UETR support | prowide/prowide-core | Apache-2.0 | validation and translation are in the vendor's commercial offerings |
| Prowide ISO 20022 | business model and parser for ISO 20022 messages | prowide/prowide-iso20022 | Apache-2.0 | companion to Prowide Core |
| QuickFIX/J | messaging engine for FIX sessions: sequence numbers, resend and persistence | quickfix-j/quickfixj | — | the same session pattern in securities trading; see FIX protocol |
Sources
- Prowide Core source and README: github.com/prowide/prowide-core (class
UETRUtilsand the changelog describe the UETR; the README lists MT, JSON and XML conversion). - Prowide ISO 20022: github.com/prowide/prowide-iso20022.
- Bank for International Settlements, CPMI, Enhancing cross-border payments: building blocks of a global roadmap, July 2020. report page
- Bank for International Settlements, CPMI, Harmonised ISO 20022 data requirements for enhancing cross-border payments, consultative report. report page
- QuickFIX/J: github.com/quickfix-j/quickfixj.