UETR tracking on chain: one reference that answers both the accounting and the compliance question
UETR tracking joined to a transaction hash gives one reference for payment and settlement. How it flows through pacs.008, pacs.009, pacs.002 and pacs.004.
- The UETR is a 36-character UUID that identifies the payment; the transaction hash identifies the settlement. Joined in one table they answer the accounting question, where is the money, and the compliance question, whose money is it, from a single lookup.
- The UETR is created once, in the first pacs.008 or pacs.009, and is copied unchanged into every later message: OrgnlUETR in the pacs.002 status report and in the pacs.004 return. A new UETR at any hop breaks the chain.
- The relationship between UETR and hash is many-to-many, because a retry produces a second hash for one UETR and a batched transfer produces one hash for many UETRs, so the mapping is a join table with a row per transfer event, not a column on the payment.
- Hash formats differ by chain in length, case and prefix, so the table stores the chain identifier alongside the hash and the ledger uses the exact string the chain's explorer displays.
- A compliance reviewer should be able to start from a wallet address, find the hash, find the UETR and reach the Travel Rule data and screening result, and an auditor should be able to start from a ledger entry and walk the same path in reverse.
UETR tracking on chain means joining the Unique End-to-end Transaction Reference that identifies a payment instruction to the blockchain transaction hash that identifies its settlement, so that one lookup returns both. The UETR answers the accounting question, which instruction this money belongs to and whether the ledger entry has been posted. The hash answers the compliance question, which wallet paid which wallet, when, and on which chain, with the evidence sitting on a public ledger. Neither reference can answer the other's question alone. This article explains what a UETR is, how it flows through pacs.008, pacs.009, pacs.002 and pacs.004 unchanged, why the join to the hash is many-to-many, and how to design the mapping table so both the finance team and the compliance team can use it.
What is a UETR and where is it created?
The UETR is a universally unique identifier in the version 4 format defined by RFC 4122: 36 characters of lowercase hexadecimal digits in five groups separated by hyphens, in the pattern 8-4-4-4-12. It is generated randomly by the first institution in the chain and is not derived from any business data, which is the point. Two institutions generating references independently will never collide. Swift made the UETR mandatory on cross-border customer and institutional payments over FIN from November 2018, where it travels in field 121 of the user header, block 3, of an MT103 or MT202. In ISO 20022 it is an element of its own, PmtId/UETR, inside the credit transfer transaction information of a pacs.008 or pacs.009.
The single most important rule is that the UETR is created once and never changed. The instructing agent generates it in the first message. Every intermediary copies it. If an MT103 is converted to a pacs.008 or the reverse, the value moves from field 121 to PmtId/UETR or back without alteration. An institution that generates a fresh UETR because its own system prefers its own identifiers has not created a tracking reference, it has created a second payment as far as everyone downstream is concerned. The operational check is simple: the UETR received on the inbound instruction must equal the UETR sent on the outbound instruction, and any exception should stop the payment.
How does the UETR flow through the four message types?
The four messages play four different roles and the UETR appears in each under a different element name, which is a common source of mapping errors. In the pacs.008 customer credit transfer it is CdtTrfTxInf/PmtId/UETR, and it sits beside the InstrId, which is the sending agent's own reference for the hop, and the EndToEndId, which is the reference the originating customer supplied and which will be shown to the beneficiary. In the pacs.009 financial institution credit transfer used for the interbank or cover leg it is the same PmtId/UETR, and a cover payment should carry the UETR of the underlying customer payment so the two can be tied together.
In the pacs.002 status report the element is TxInfAndSts/OrgnlUETR, because the report refers back to the original instruction rather than carrying its own. In the pacs.004 payment return the element is TxInf/OrgnlUETR for the same reason, and the return carries its own RtrId as the reference for the return movement. So a single payment that settles, is later returned, and generates status reports for both legs will show the same UETR in four message types under three element names, and the mapping layer has to recognise all three as the same key. A parser that indexes PmtId/UETR alone will lose the status reports and the return.
Why is the join to the transaction hash many-to-many?
It is tempting to add a tx_hash column to the payment record and stop there. That design fails in two ordinary situations. The first is a retry. A transaction that is broadcast but dropped from the network, or that reverts because of a fee estimation error, is re-sent with a new hash. The payment now has two hashes, one failed and one successful, and both must be kept because the failed one is evidence of what the system attempted. The second is batching. A settlement agent that pays several beneficiaries from one wallet in a single transaction, which some chains and some token contracts allow, produces one hash for many UETRs. The mapping is therefore many-to-many, and the correct structure is a transfer event table with one row per on-chain transfer, keyed by chain identifier, hash and the position of the transfer within the transaction, with the UETR as a foreign key.
The position within the transaction is the element most designs forget. On EVM-compatible chains a single transaction emits an ordered list of events, and a token transfer is one event with an index. On the XRP Ledger a transaction may affect several balances recorded in its metadata. Without the position, a batched transaction cannot be split back into its component payments by machine, and the reconciliation falls back to matching amounts, which is exactly what the UETR was meant to eliminate.
The UETR tells you which instruction the money belongs to. The hash tells you where the money went. A ledger that stores only one of them can answer only one of the two questions an examiner will ask.
How should the mapping table be designed?
The table below is the minimum a settlement agent needs. It is written as a list of columns because the exact schema depends on the ledger, but the columns themselves are not optional.
- The UETR, stored as the 36-character string exactly as received, with a uniqueness check on the payment table so that a duplicate inbound instruction is caught before it reaches the chain.
- The chain identifier and the transaction hash, stored together, with the hash in the exact form the chain's own explorer displays, because EVM chains use a 0x prefix and lowercase hexadecimal while the XRP Ledger uses uppercase hexadecimal without a prefix and some chains use base58 signatures of a different length.
- The transfer position within the transaction, the token contract address, the sending and receiving wallet addresses and the raw amount with its decimals, so that a batched transaction can be decomposed by machine.
- The block number and block timestamp, the confirmation depth at the moment the ledger entry was posted, and the status reported in the matching pacs.002, so that the accounting event and the chain event can be compared.
- A link to the return, where one exists, by storing the RtrId of the pacs.004 and the hash of the returning transfer against the original UETR rather than opening a new payment record.
- A link to the compliance record, holding the case reference for the screening result and the Travel Rule data set exchanged for the payment, so that the reviewer reaches it from the UETR in one step.
Two constraints matter for audit. The table should be append-only for transfer events, with corrections recorded as new rows rather than edits, because the tamper evident audit trail an examiner expects cannot be built on a table that permits updates. And the table should record who or what created each row: the chain watcher for observed transfers, the payment engine for attempted transfers, and a named operator for any manual link, because a manual link between a UETR and a hash is precisely the kind of entry an auditor will want to sample.
How does one reference answer both the accounting and the compliance question?
Consider the two people who will use the table most. The reconciliation analyst starts from a ledger entry, which carries the UETR. She joins to the transfer event table, finds the hash, checks the pacs.002 status against the confirmation depth recorded, and closes the item. If the join returns no row, the entry was posted without evidence and goes to the control queue. If the join returns two rows, one failed and one successful, she checks that the failed attempt was never posted. Her question, where is the money and has it been recorded correctly, is answered from the UETR outwards.
The compliance reviewer usually starts from the other end. An alert from wallet screening names an address and a hash. He joins from the hash to the transfer event, finds the UETR, and from the UETR reaches the payment record, the originator and beneficiary data carried in the pacs.008, the Travel Rule data set in IVMS101 form, and the sanctions screening result recorded at the time of the payment. His question, whose money is it and was it screened, is answered from the hash inwards. The same table serves both because both keys are in it. An examiner will test this by picking a hash from the public chain and asking for the file, then picking a UETR from the ledger and asking for the hash.
What goes wrong in practice?
The failures are predictable. A vendor system generates its own UETR on the outbound leg, so the beneficiary institution's status reports come back with a reference the originator never sent. A hash is stored in mixed case on one system and lowercase on another, and the join silently returns nothing. A batched transaction is recorded once against the first UETR and the other payments in the batch have no chain evidence at all. A return is booked as a fresh inbound payment with a new UETR, severing the link to the original outbound leg, so the net position is right and the audit trail is wrong. A manual fix links a hash to a UETR without recording who did it.
Each of these has a control. The outbound UETR must equal the inbound UETR, enforced by the payment engine. Hashes are normalised to the chain's canonical form on entry and compared in that form. The chain watcher decomposes every transaction into transfer events before matching. Returns are matched to the original UETR through OrgnlUETR before a new record can be created. Manual links require a named operator and a reason, and appear in a daily report. None of these are difficult, but all of them have to be built before go-live.
Where StableNet fits
StableNet, built by SpendTheBits, is a cross border B2B payment and settlement platform for banks, credit unions, licensed money service businesses, exchange houses and remittance fintechs. It is ISO 20022 native and tracks every payment end to end by UETR, from the pacs.008 customer credit transfer or pacs.009 interbank leg, through the pacs.002 status report, to the pacs.004 return where one is needed, all inside head.001 envelopes, and it also accepts and emits MT103. The settlement leg runs in regulated stablecoins such as USDC and USDT on public blockchains, completing in minutes with on chain auditability while customers keep custody, so the hash that joins to the UETR sits on a ledger the examiner can read directly. Travel Rule data in IVMS101 form, wallet screening and sanctions screening results are held against the same payment record, and a tamper evident audit trail records the link between reference and hash. SpendTheBits is a Bank of Canada registered payment service provider and a named finalist in the Swift Hackathon 2026 Technical Challenge.
See it on your corridors
Book a working session and we’ll map StableNet’s compliance and settlement to one of your live payment flows.