How the pacs.002 status report should track an on-chain settlement from submission to finality
pacs.002 status report messages mirror on-chain states: submitted, pending, confirmed and failed. Which codes to use, how many to send, and how to reconcile.
- The four on-chain states a settlement passes through, submitted, pending, confirmed and failed, map cleanly onto the ISO 20022 transaction status codes ACTC or ACSP, ACSP, ACSC or ACCC, and RJCT. PDNG is reserved for a human hold, not for a transaction waiting on confirmations.
- A single payment should normally generate two pacs.002 messages, one on acceptance and one on completion, with a third only if it fails or is held. One message per block confirmation is a design error.
- The transaction hash does not fit in a 35-character reference field, so it belongs in the status reason additional information element or in supplementary data, and the choice must be agreed with the counterparty in writing.
- Every pacs.002 must carry the original UETR in OrgnlUETR, and reconciliation is a join on that field between the status log, the chain watcher and the general ledger, not a search on amounts.
- The reason code on a rejection should say why in a way the originator can act on: an unusable destination address, insufficient balance in the operating wallet, a compliance hold and a chain-level failure are different problems with different codes.
A pacs.002 status report is the ISO 20022 message an agent sends back to tell the sender what happened to a payment instruction. When the settlement leg runs on a public blockchain, the report should mirror the states the transaction actually passes through: submitted to the network, pending confirmation, confirmed to the agreed depth, or failed. Each of those states maps to an existing transaction status code, and a well-designed flow sends two or three reports per payment rather than one per block. This article sets out the mapping, the codes and reason codes to use, the elements that must be populated, how many messages a single payment should generate, and how a treasury reconciles the ledger from them.
What does a pacs.002 actually report, and to whom?
The message is formally the FI to FI payment status report. It is sent by the agent that received a pacs.008 customer credit transfer or a pacs.009 financial institution credit transfer, back to the agent that sent it, to state whether the instruction was accepted, is being processed, has settled or was rejected. It refers to the original instruction through a group of original references: OrgnlMsgId and OrgnlMsgNmId identify the original message and its type, and within TxInfAndSts the elements OrgnlInstrId, OrgnlEndToEndId, OrgnlTxId and OrgnlUETR identify the individual transaction. TxSts carries the status code. StsRsnInf carries a reason code and optional additional information. AccptncDtTm records when the instruction was accepted.
On the Swift network under the CBPR+ guidelines the pacs.002 is used mainly for rejections and for confirmations of processing, with gpi confirmations carrying much of the tracking load. When the settlement leg is on chain there is no gpi tracker for that leg, so the pacs.002 becomes the only structured way for the settlement agent to tell the instructing agent that the token transfer happened. That raises its importance, and it means the originating institution's back office must be able to consume a pacs.002 with a status it may never have received from a correspondent before, namely a completion status with a chain reference attached.
How do on-chain states map to transaction status codes?
A token transfer passes through four states that matter to the instructing party. It has been validated and broadcast to the network. It is in the network's pending pool or in a block that has not yet reached the agreed confirmation depth. It has reached that depth and the settlement agent treats it as final. Or it has failed, either because the network rejected it or because the transaction executed and reverted. The ISO 20022 external transaction status code list already contains codes that describe those states without stretching their definitions.
- Submitted maps to ACTC, accepted technical validation, when the agent has validated the instruction and signed the transaction but not yet broadcast it, or to ACSP, accepted settlement in process, once the transaction has been broadcast.
- Pending confirmation maps to ACSP, accepted settlement in process, and should not generate a new message for each block, because the state has not changed from the instructing party's point of view.
- Confirmed maps to ACSC, accepted settlement completed, when the transaction has reached the agreed confirmation depth, or to ACCC, accepted credit settlement completed, when the settlement agent has also credited the beneficiary customer.
- Failed maps to RJCT, rejected, with a reason code that distinguishes a network rejection, a reverted execution, a compliance decision and an insufficient balance.
- Held for a human decision maps to PDNG, pending, and is the only case where a status should be followed later by either ACSP or RJCT depending on the outcome.
The distinction between ACSC and ACCC deserves a rule of its own. ACSC says the interbank or inter-institution leg is done. ACCC says the end beneficiary has been credited. When the settlement agent is also the beneficiary's institution, both may be true at once. When a further last-mile leg exists, for example a local currency payout after the on-chain credit, ACSC should be sent at confirmation depth and ACCC only when the payout has been made. Reporting ACCC too early is the most common way a status log ends up contradicting a customer statement.
Which reason codes belong on a rejection or a hold?
The external status reason code list was written for account-based payments, so the mapping to on-chain failures is a matter of judgement, and it should be agreed bilaterally and documented rather than left to each developer. A reasonable mapping treats a malformed or unsupported destination address as an incorrect account number, AC01, since the address is the account identifier on the chain. Insufficient token balance in the operating wallet, or insufficient native gas to pay the network fee, maps to AM04, insufficient funds. A compliance hold or refusal after sanctions or wallet screening maps to RR04, regulatory reason, with no further detail in the message itself. A duplicate instruction detected by UETR maps to DUPL. A submission after the agent's own control cut-off maps to TM01. A network failure with no cleaner explanation uses MS03, reason not specified by the agent, with narrative in the additional information element.
Two practical points. First, the AddtlInf element inside StsRsnInf is limited in length per occurrence, 105 characters in the base schema, and network guidelines may restrict the number of occurrences, so it should carry a short, structured phrase rather than a paragraph. Second, a compliance rejection should never carry the screening detail. RR04 plus a case reference the compliance team can look up is the correct disclosure; naming the list or the match in a payment message is not.
A status report is a promise about what the sender may now do. ACSP means wait. ACSC means post the entry. RJCT means the money is still yours. Anything that blurs those three is worse than silence.
How many pacs.002 messages should one payment generate?
Two in the normal case, three in the exceptional case, and never one per block. The first report goes out when the instruction is accepted and broadcast, carrying ACSP. The second goes out when confirmation depth is reached, carrying ACSC or ACCC, and it is the one that carries the chain reference. If the payment fails at any point, the RJCT report replaces whichever of those two has not yet been sent. If the payment is held, a PDNG report is sent when the hold begins, and the flow then resumes with ACSP or ends with RJCT. A status log for a single payment therefore contains at most three entries, each with a different TxSts, and a treasury can validate its log with that rule alone.
Some teams are tempted to send a status per confirmation on chains where finality is probabilistic, on the reasoning that more information is better. It is not. The instructing party cannot act on the difference between three confirmations and four, and the extra messages create ambiguity about which one is authoritative. The confirmation depth is a policy of the settlement agent; the instructing party receives its outcome, not its progress. Where the chain has deterministic finality the second report simply follows the finality event.
Where does the transaction hash go in the message?
Nowhere comfortable, which is why it has to be agreed. A transaction hash on an EVM-compatible chain is 64 hexadecimal characters, 66 with the 0x prefix. Signatures on some other chains are longer. The identification elements in pacs.002 are mostly Max35Text, so the hash does not fit in ClrSysRef or in any of the original reference fields. The two workable options are the AddtlInf element within StsRsnInf, which accepts 105 characters and can carry a chain identifier plus the hash in one occurrence, and the SplmtryData block, which can carry a structured envelope with chain, hash, block number and confirmation depth as separate elements. The supplementary data route is cleaner for machines and worse for humans reading a message in a viewer; the additional information route is the reverse.
Whichever route is chosen, the format must be fixed. A workable convention is chain identifier, a colon, then the hash exactly as the chain's own explorer displays it, so that a person can paste the string into the explorer and see the transfer. The same string should appear in the settlement agent's audit trail and in the instructing party's ledger, character for character. A mismatch in case or prefix between systems is a reconciliation break waiting to happen, and it is easier to prevent with a convention than to fix with a normalisation routine later.
How does a treasury reconcile from the status reports?
Reconciliation starts from the UETR, not from the amount. Every pacs.002 carries the original UETR in OrgnlUETR, so the status log can be joined to the outbound instruction log on that key alone. The chain watcher, the component that reads the blockchain and detects the settlement agent's transfers, records each transfer with its hash and the UETR it was sent for. The general ledger posts the settlement entry against the UETR when the ACSC or ACCC report arrives. Three sources, one key. The reconciliation job then asks three questions for each UETR: does an ACSP exist without a later ACSC, ACCC or RJCT after the maximum expected confirmation time; does an ACSC exist whose hash the chain watcher has not seen; and does a ledger entry exist without a matching ACSC or ACCC.
Each mismatch has an owner. An ACSP that has aged out goes to the technical on-call engineer, who checks whether the transaction is still pending or was dropped and needs re-broadcasting. An ACSC whose hash is unknown to the chain watcher is a control failure and goes to the operations lead, because it means a completion was reported without evidence. A ledger entry without a completion status goes to finance, and the usual cause is a manual posting that bypassed the message flow. The examiner's version of this is simpler: pick ten UETRs from the ledger and ask for the pacs.002 with the hash for each, then ask to see the hash on the chain.
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, so a payment instructed by pacs.008 or pacs.009 and settled in regulated stablecoins such as USDC and USDT on public blockchains is reported back through pacs.002 status reports inside head.001 envelopes, tracked end to end by the same UETR, with pacs.004 available for returns. Settlement completes in minutes with on chain auditability, which gives the completion status something to point at, and customers keep custody of their own wallets. StableNet also accepts and emits MT103, so the status flow can sit alongside a legacy instruction format during a migration. A tamper evident audit trail records every status transition against the UETR, which is the record an examiner asks for when testing the reconciliation. 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.