A pacs.008 carries up to six identifiers for one payment, and only one of them is guaranteed to mean the same thing at every bank in the chain. The European Central Bank said so in a T2 Q&A: the UETR "is the only true, meaningful end-to-end ID", the others are point to point, and the EndToEndId "could contain NOTPROVIDED".
Most reconciliation bugs I have debugged on bank rails come from picking the wrong one as a key. This guide covers what each identifier is, who is allowed to set or change it, which rails require which, what happens to them in returns and recalls, and which ones actually come back in your statement files.
What Is a UETR in ISO 20022 Payments?
The Unique End-to-end Transaction Reference is a UUID version 4, fixed at 36 characters, carried in CdtTrfTxInf/PmtId/UETR. ISO defines it as a "universally unique identifier to provide an end-to-end reference of a payment transaction". The schema type is UUIDv4Identifier, and the pattern, printed in the EPC's 2025 SCT implementation guidelines and the Payments Canada Lynx guidelines alike, is:
[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}
Read the pattern literally and three things follow. Uppercase hex fails. A UUID v7 fails, because the version nibble must be 4. A nil UUID fails. JPMorgan's sample value 00000000-0000-4000-8000-000000000000 is the minimum shape that passes.
It predates ISO 20022 on Swift. From 18 November 2018 every Swift user, gpi member or not, had to put a UETR in field 121 of header block 3 on MT 103, 103 STP, 103 REMIT, 202, 205, 202 COV and 205 COV, or receive a NAK. The ordering institution generates it. Swift's rules say intermediaries "must not generate a new UETR", and the UETR of a cover payment "must be the same as the UETR of the underlying customer credit transfer".
Corporates can sometimes supply their own. pain.001.001.09 has an optional PmtId/UETR. Nordea's guide says it will create one if you leave it out, and Payments Canada's says "if provided, the bank passes this value on". Zürcher Kantonalbank's CBPR+ pain.001 handbook goes further and marks it mandatory.
UETR vs EndToEndId vs TxId vs InstrId: What Is the Difference?
Everything except the UETR is Max35Text. The ISO definitions tell you the intended scope, and the scope is the whole point.
| ID | Path in pacs.008 | Set by | Scope | Can it change along the chain? |
|---|---|---|---|---|
MsgId | GrpHdr/MsgId | Sender of each message | One hop | Yes, new at every hop |
InstrId | PmtId/InstrId | Each instructing party | One hop | Yes, new at every hop |
EndToEndId | PmtId/EndToEndId | The initiating customer | Customer to customer | No, "passed on, unchanged, throughout the entire end-to-end chain" |
TxId | PmtId/TxId | First instructing agent (the debtor's bank) | Interbank chain | No, "unchanged throughout the entire interbank chain" |
UETR | PmtId/UETR | Debtor agent, or the customer where allowed | End to end, across networks | No |
ClrSysRef | PmtId/ClrSysRef | The clearing system | Clearing system to member | Assigned once by the system |
Three practical caveats sit behind that tidy table.
EndToEndId is the customer's reference, and the customer often has none. MT 103 has no end-to-end field, so JPMorgan's MT-to-MX mapping guide shows a translated payment carryingNOTPROVIDED . SEPA has the same rule in writing: "In the event that no reference was given, 'NOTPROVIDED' must be used". A unique index on EndToEndId will fall over on the second such payment.
InstrId is shorter than its type suggests. CBPR+ caps it at 16 characters so that it can still translate to MT field 20, and Lynx applies the same limit "to align with CBPR+". If you generate 35-character instruction IDs because the XSD allows it, a cross-border route will reject them.
TxId is a bank's ID, not yours. In SEPA it must be "meaningful to the Originator's PSP" and unique over time. You do not choose it when you submit a pain.001. You find it out afterwards, if your bank reports it.
In a pain.001 there is one more level: PmtInf/PmtInfId identifies the batch, above the per-transaction InstrId and EndToEndId. I covered the message families themselves in the pain.001 and pacs.008 guide.
Is UETR Mandatory? Swift, Fedwire, FedNow, SEPA and T2 Rules in 2026
It depends on the rail, and the rails disagree more than you would expect.
| Rail | UETR | Other mandatory IDs | Detail worth knowing |
|---|---|---|---|
| Swift CBPR+ | Mandatory in pacs.008 and pacs.009 | InstrId (16 chars), EndToEndId | MT coexistence ended 22 November 2025 |
| Fedwire Funds | Mandatory in pacs.008, pacs.009, pacs.004 | EndToEndId | ISO 20022 since 14 July 2025; IMAD and OMAD still exist |
| T2 (Eurosystem RTGS) | Mandatory in pacs.008, pacs.009, pacs.010 | T2 generates one for screen-entered orders | |
| Lynx (Canada) | Mandatory in pacs.008 | InstrId (16 chars) | |
| SEPA SCT and SCT Inst | Optional (0..1), no usage rule | TxId, EndToEndId | 2025 rulebooks, effective 5 October 2025 |
| FedNow | Either UETR or TxId | EndToEndId | Per Lead Bank's API docs; check your own provider |
| RTP (The Clearing House) | Optional | EndToEndId, TxId | Same caveat |
| UK Faster Payments | None | ISO 8583 based; planned NPA replacement mandates UETR | |
| ACH | None | 15-digit trace number | First 8 digits are the ODFI routing number |
The Fedwire row hides the sharpest edge. The Fed's format FAQ says the service "checks the UETR for proper format but does not validate the uniqueness of it". Swift does the opposite. Nordea's guide notes that Swift "performs validations to detect recycled UETRs", and Deutsche Bank reported during Transaction Manager testing that MT 191 charge claims reusing the underlying payment's UETR were flagged as duplicates and aborted. So a UETR generator bug that repeats values will pass a Fedwire-only test suite and then break the first time a payment leaves the country.
FedNow's message ID has its own structure, quoted in Jack Henry's gap analysis: an 8-digit date (CCYYMMDD), the sender's 9-character FedNow connection party ID, then a sender reference of up to 18 characters.
Pay.UK's New Payments Architecture documents mandate UETRs "across the payment lifecycle" for the system intended to replace Faster Payments, so the UK retail gap should close eventually. Today, an FPS payment has no UETR, and the UK rails comparison explains what you get instead.
What Happens to the UETR in a pacs.004 Return or camt.056 Recall?
A return does not get a UETR of its own. pacs.004 has no PmtId/UETR; it carries TxInf/OrgnlUETR pointing at the payment being returned. Deutsche Bank's high-value formatting guide marks OrgnlUETR as 1..1 and says it "must transport the UETR of the underlying payment". Fedwire also makes the original UETR mandatory on returns, while the original InstrId, EndToEndId and TxId are optional.
| Message | Where the original UETR lives | Notes |
|---|---|---|
pacs.004 return | TxInf/OrgnlUETR | Mandatory on CBPR+, Fedwire and T2 |
camt.056 recall | Undrlyg/TxInf/OrgnlUETR | CBPR+ also requires OrgnlEndToEndId |
camt.029 recall answer | CxlDtls/TxInfAndSts/OrgnlUETR | Mandatory on CBPR+ |
pacs.002 status | TxInfAndSts/OrgnlUETR | Optional in T2 |
pacs.028 status request | TxInf/OrgnlUETR | 0..1 in SEPA |
pacs.009 COV | PmtId/UETR | Same value as the underlying pacs.008 |
Two edge cases will catch a naive "one UETR, one row" model.
A return of a return keeps pointing at the first payment. The Fed's FAQ spells it out: OrgnlMsgId becomes the incoming pacs.004's message ID, but OrgnlUETR stays the UETR of the original pacs.008 or pacs.009. One UETR can therefore own three or more money movements.
Split payments go the other way. In the Swiss SIC system, when a bank splits a payment, "a new, unique UETR must be generated for each split payment", the original UETR "may not be forwarded", the code SPLI is added, and the EndToEndId is carried over to link the parts. There, the EndToEndId is the join key and the UETR is not.
How Do You Track a Payment by UETR? gpi Status Codes
The UETR is the lookup key for Swift's gpi Tracker, and since the end of 2020 every bank has had to confirm the credit or rejection of each MT 103 to it. Most engineers never call the Tracker directly. You see it through a bank API, such as JPMorgan's track-and-trace endpoint, which returns an ISO status plus a gpi reason code:
| Status / reason | Meaning |
|---|---|
ACCC | Beneficiary credited. Final. |
ACSP / G000 | Passed to the next bank, which will keep updating the tracker |
ACSP / G001 | Passed to a bank that cannot update the tracker. Tracking stops here. |
ACSP / G002 | Credit may not be confirmed the same day |
ACSP / G003 | Waiting for documents from the beneficiary |
ACSP / G004 | Waiting for cover funds |
RJCT + reason | Rejected, with a code such as AC01, AC04, BE01, RR03 or DUPL. Final. |
G001 is the one to model carefully. It is neither success nor failure, and no further update will arrive, so a poller that waits for ACCC will wait indefinitely. Give it a terminal "untracked" state and fall back to the beneficiary's confirmation.
G003 and G004 explain most of the "where is my wire" tickets. Swift's October 2024 figures show 90% of cross-border payments reaching the destination bank within an hour, but only 43% reaching the end customer's account in that time. The delay is at the last bank, and the reason code usually tells you which kind.
Which Payment ID Should You Reconcile On in camt.053?
The honest answer is whichever your bank populates, and banks differ. camt.053 and camt.054 put references in NtryDtls/TxDtls/Refs: MsgId, PmtInfId, InstrId, EndToEndId, TxId, UETR in newer versions, and the bank's own AcctSvcrRef.
The GNU Taler project documented what it actually received over EBICS. German banks populated TxId everywhere and the UETR was "never found in practice". Swiss banks populated AcctSvcrRef and UETR and never TxId. On-us transfers carried only AcctSvcrRef. Their fix was three nullable columns with a check that at least one is present. That design is correct, and I would copy it:
create table bank_entry_ref (
entry_id bigint primary key,
uetr uuid,
tx_id varchar(35),
acct_svcr_ref varchar(35),
end_to_end_id varchar(35),
check (num_nonnulls(uetr, tx_id, acct_svcr_ref) >= 1)
);
create index on bank_entry_ref (uetr) where uetr is not null;
create index on bank_entry_ref (end_to_end_id)
where end_to_end_id <> 'NOTPROVIDED';
Note what is absent: a unique constraint on uetr. A payment, its cover, its return and its fee adjustments can all share one.
If your bank still sends MT940, none of this fits. Field 61 gives you 16 characters per reference, which holds neither a 35-character EndToEndId nor a 36-character UETR. The MT940 vs camt.053 comparison has the field-by-field detail.
Should UETR Be Your Primary Key? My Position
No. Use it as the correlation key and keep your own primary key.
The UETR identifies a payment obligation across institutions. Your ledger records money movements, and one obligation produces several. A pacs.008, the pacs.009 COV that funds it and a later pacs.004 are three postings with one UETR. On SIC a single instruction becomes several UETRs. On SEPA, FPS and ACH there may be no UETR at all.
So the shape I'd build is: an internal payment ID as the primary key, a UETR column that is always populated (generate a v4 even on rails that ignore it), and per-rail reference columns for TxId, IMAD or trace number. Deduplicate outbound submissions with your own idempotency key, because Fedwire proves a rail will not always do it for you.
My prediction: TxId fades on high-value rails. A 2019 Eurosystem design note already described the UETR replacing the transaction ID in T2. Retail schemes will take longer. SEPA's 2025 rulebooks still treat UETR as an optional element with no usage rule, and nothing public suggests that changes before the next rulebook cycle.
What This Means for Payment Engineers
1. Unit-test your UUID generator against the schema regex. Lowercase, version 4, variant [89ab]. A switch to UUID v7 for index locality is a production incident on CBPR+.
2. Generate one UETR per payment obligation and reuse it for the cover. Generate a new one for a charge claim or any separate movement.
3. Never put a unique index on EndToEndId. Treat NOTPROVIDED as null. If you control the pain.001, always set a real value of 35 characters or fewer.
4. Keep InstrId to 16 characters if a payment could ever route over Swift or Lynx.
5. Match returns on OrgnlUETR first, then OrgnlEndToEndId plus amount and settlement date. T2 itself matches recalls on that combination.
6. Store every reference the statement gives you in its own column. Ask each bank for a sample camt.053 before you design the matcher, because the populated fields vary by country.
7. Model ACSP/G001 as a terminal untracked state in any gpi status poller.
The short version of the comparison: the EndToEndId belongs to the customer, the TxId to the first bank, the InstrId to each hop, and the UETR to the payment itself. Key on the last one where it exists, and keep the others for the rails where it does not.
I'm Tom Wang, and I write about the engineering underneath payment rails. Rail specifications change with each release, so check the current implementation guide before you hard-code any rule from this page.