ISO 20022 files (camt.053, pain.001)
In short
Section titled “In short”Most core banking systems, and Ghana’s interbank systems (GhIPSS Instant Pay and ACH), can produce ISO 20022 XML files. A file drop can load two of them as transactions:
| Message | What it is | Versions read | Each transaction is |
|---|---|---|---|
| camt.053 | Bank-to-customer statement: what was booked on an account | camt.053.001.02 to .001.13 |
One transaction detail (TxDtls) of an entry, or the entry itself when it has no details |
| pain.001 | Customer credit transfer initiation: payments a customer asked the bank to make | pain.001.001.03 to .001.12 |
One credit transfer instruction (CdtTrfTxInf), as an outgoing, pending transfer |
Element names are matched without their namespace, so all those versions read the same way. Only
statements (BkToCstmrStmt/Stmt) are read: camt.052 reports and camt.054 notifications are not.
Files are read as a stream, one entry or instruction at a time, so a statement with a million entries needs no more memory than one with ten. Each entry or instruction that can’t be mapped is rejected on its own, with the line where it starts; the rest of the file still loads.
Each entry (Ntry) or instruction (CdtTrfTxInf) may be up to 8 MiB. A larger one is skipped
while it is read, never held in memory, and reported on its line as entry 12 is larger than 8 MiB; skipped (or instruction 12 …); the rest of the file still loads. Any other single piece of the
file larger than that, such as one huge header or text node, means the file can’t be read: it
fails with … an XML element or text larger than …, and nothing from it is loaded.
Before you send both
Section titled “Before you send both”A payment can appear in a pain.001 (when the customer asks for it) and again in a camt.053
(when it is booked). SpeculaPro gives both the same external_id, its payment id:
- The payment’s UETR, when the message carries one. It is unique for every payment.
- Otherwise, for an outgoing payment,
e2e/<account>/<date>/<EndToEndId>: the end-to-end id, qualified with the paying account (the pain.001’s debtor account, the camt.053’s statement account) and the day (the pain.001’s requested execution date; the camt.053 entry’s value date, else its booking date).
So a payment loaded from both files is recorded once. The second copy differs from the first
(pending against booked), so it is reported as external_id was already used for a different transaction in that file’s results, not counted again.
camt.053: statement entries
Section titled “camt.053: statement entries”Paths are relative to BkToCstmrStmt/Stmt. Tx means Ntry/NtryDtls/TxDtls, or the entry
itself when it has no transaction details. A single-detail entry is one whose NtryDtls holds at
most one TxDtls.
| Transaction field | Taken from |
|---|---|
external_id |
The first that is present: the payment id (Tx/Refs/UETR; for an outgoing DBIT detail, the qualified EndToEndId); Tx/Refs/AcctSvcrRef (the bank’s own reference); for a single-detail entry, Ntry/AcctSvcrRef; Tx/Refs/TxId; the qualified EndToEndId (e2e/<account>/<date>/<EndToEndId>); then Ntry/AcctSvcrRef or Ntry/NtryRef, followed by /<n> (the detail’s position) in a batch entry; finally Stmt/Id + /<entry number> |
occurred_at |
Tx/RltdDts/TxDtTm, else Tx/RltdDts/AccptncDtTm, else Ntry/BookgDt (DtTm or Dt), else Ntry/ValDt. A date or time without a zone is read in your workspace’s time zone; a bare date is midnight |
customer_external_id |
Acct/Ownr/Id: OrgId/Othr/Id, else its LEI or BIC; or PrvtId/Othr/Id. Without an owner id, the account id |
customer_type |
business when the owner id is an OrgId, individual when it is a PrvtId |
account_external_id |
Acct/Id/IBAN, else Acct/Id/Othr/Id |
account_type |
bank |
direction |
CdtDbtInd of the detail, else of the entry: CRDT → in, DBIT → out |
amount_minor, currency |
Tx/AmtDtls/TxAmt/Amt, else Tx/Amt, else (single-detail entries only) Ntry/Amt, with its Ccy attribute |
status |
Ntry/Sts (Cd, or the plain text in older versions): BOOK → completed; PDNG, INFO, FUTR or any other code → pending |
type, channel |
From the bank transaction code, Tx/BkTxCd, else Ntry/BkTxCd: see Bank transaction codes. RvslInd = true makes the type reversal |
counterparty |
Money in: RltdPties/Dbtr (or UltmtDbtr when it has no name), DbtrAcct and RltdAgts/DbtrAgt. Money out: RltdPties/Cdtr (or UltmtCdtr), CdtrAcct and RltdAgts/CdtrAgt |
counterparty.name |
Nm (or Pty/Nm), up to 200 characters |
counterparty.identifier |
The counterparty account’s IBAN (identifier_type iban), else its Othr/Id (account_number) |
counterparty.provider |
The agent’s FinInstnId: BICFI or BIC, else ClrSysMmbId/MmbId (such as a GhIPSS bank sort code), else its other id or name |
counterparty.country, counterparty_country |
PstlAdr/Ctry, else CtryOfRes, else the first two letters of the counterparty’s IBAN |
counterparty.type |
business for an OrgId, individual for a PrvtId |
reference |
Tx/Refs/EndToEndId, else RmtInf/Strd/CdtrRefInf/Ref (140 characters) |
description |
The RmtInf/Ustrd lines joined, else AddtlTxInf, else Ntry/AddtlNtryInf (500 characters) |
attributes |
iso20022_message (camt.053), statement_id, bank_transaction_code, proprietary_code, end_to_end_id, uetr, account_servicer_reference, purpose, value_date, when present |
The placeholder NOTPROVIDED, which ISO 20022 uses for a missing end-to-end id, counts as absent
everywhere.
An entry is rejected when it has no account id, no amount or currency, a CdtDbtInd other than
CRDT or DBIT, no booking or value date, or no reference at all to build an external_id from.
Bank transaction codes
Section titled “Bank transaction codes”The ISO bank transaction code (domain / family / sub-family, from the ISO 20022 external code list)
decides the transaction’s type and channel:
| Code | type |
channel |
|---|---|---|
Any sub-family CHRG, FEES, COMM or INTR (charges, fees, commission, interest) |
fee |
other |
PMNT/RCDT, PMNT/ICDT (credit transfers) |
transfer |
bank_transfer; instant_payment for sub-family INST, or a proprietary code containing GIP |
PMNT/RDDT, PMNT/IDDT (direct debits) |
payment |
bank_transfer |
PMNT/CCRD, PMNT/MCRD (cards) |
payment; sub-family CWDL: withdrawal |
card; CWDL: atm; POSD, POSP, POSC: pos |
PMNT/CNTR (counter transactions) |
CDPT: deposit; CWDL: withdrawal; else other |
branch |
PMNT/ICHQ, PMNT/RCHQ, PMNT/DRFT (cheques and drafts) |
payment |
cheque |
Any other PMNT family |
transfer |
bank_transfer |
LDAS (loans and deposits) |
DDWN: loan_disbursement; RPMT, PPAY: loan_repayment; else other |
bank_transfer |
CAMT (cash management) |
transfer |
bank_transfer |
Only a proprietary code, containing GIP (GhIPSS Instant Pay) or INST |
transfer |
instant_payment |
| Only a proprietary code, anything else | other |
other |
| Any other domain | other |
other |
| No code at all | transfer |
bank_transfer |
The code itself is kept in the bank_transaction_code attribute (for example PMNT/RCDT/INST), and
a proprietary code in proprietary_code, so you can see what the bank sent.
pain.001: credit transfer instructions
Section titled “pain.001: credit transfer instructions”Paths are relative to CstmrCdtTrfInitn. Tx means PmtInf/CdtTrfTxInf. A pain.001 is an
instruction to pay, not a settled payment, so every transaction is an outgoing transfer with
status pending.
| Transaction field | Taken from |
|---|---|
external_id |
The payment id: Tx/PmtId/UETR, else e2e/<debtor account>/<ReqdExctnDt>/<EndToEndId>. Without either, GrpHdr/MsgId + / + Tx/PmtId/InstrId (else the instruction’s position in the file) |
occurred_at |
GrpHdr/CreDtTm, when the instruction was created. A time without a zone is read in your workspace’s time zone |
customer_external_id |
PmtInf/Dbtr/Id: OrgId/Othr/Id, else its LEI or BIC; or PrvtId/Othr/Id. Without one, the debtor account id |
customer_type |
business for an OrgId, individual for a PrvtId |
account_external_id |
PmtInf/DbtrAcct/Id/IBAN, else DbtrAcct/Id/Othr/Id |
account_type |
bank |
direction, type, status |
out, transfer, pending |
channel |
instant_payment when the service level or local instrument (of the instruction, else of PmtInf) is INST or contains GIP; else bank_transfer |
amount_minor, currency |
Tx/Amt/InstdAmt with its Ccy, else Tx/Amt/EqvtAmt/Amt |
counterparty |
Tx/Cdtr (or UltmtCdtr when it has no name), Tx/CdtrAcct and Tx/CdtrAgt, read as for camt.053 |
counterparty_country |
Tx/Cdtr/PstlAdr/Ctry, else CtryOfRes, else the creditor IBAN’s country |
reference |
Tx/PmtId/EndToEndId, else RmtInf/Strd/CdtrRefInf/Ref (140 characters) |
description |
The Tx/RmtInf/Ustrd lines joined (500 characters) |
attributes |
iso20022_message (pain.001), message_id, payment_information_id, instruction_id, end_to_end_id, uetr, purpose, category_purpose, requested_execution_date, service_level, local_instrument, when present |
The requested execution date (ReqdExctnDt) may lie in the future, so it is kept as the
requested_execution_date attribute rather than used as the transaction’s time.
An instruction is rejected when the file has no GrpHdr/MsgId (and the instruction has no payment id)
or no GrpHdr/CreDtTm, when its payment has no debtor account, or when it has no amount or
currency.
After loading
Section titled “After loading”Every transaction then goes through the same checks as one sent to the API: allowed values, required fields, the duplicate check, conversion to your base currency and encryption of identifiers. Check what loaded in the data source’s File history and in the results file.