Skip to content

ISO 20022 mapping

integrations/iso20022 maps ISO 20022 financial messages (pain, pacs, camt, admi) onto Quidnug event streams, making cross-bank trust, audit chains, and settlement-lifecycle queries first-class per-observer trust questions.

Quidnug × ISO 20022 bridge

integrations/iso20022 maps ISO 20022 financial messages (pain, pacs, camt, admi) onto Quidnug event streams, making cross-bank trust, audit chains, and settlement-lifecycle queries first-class per-observer trust questions.

Why

ISO 20022 is the global XML messaging standard for financial flows: payments (pain, pacs), cash management (camt), securities, FX, cards, trade finance. SWIFT’s MT → MX migration (completing Nov 2025) dramatically increases the volume of signed ISO 20022 messages moving between institutions.

Today the trust graph between banks is a bilateral tangle of correspondent agreements. Quidnug collapses that matrix into a lookup:

Receiving bank: "Given my observer quid, what's my transitive trust
in the originator of this pacs.008?"
→ Quidnug relational-trust query → 0.83 via BANK_B → BANK_A

What this package does

For each signed ISO 20022 message, the recorder:

  1. Extracts a small header (message id, BICs, amount, currency, etc.).
  2. Emits a Quidnug EVENT on the payment’s Quidnug Title, signed by the bank’s quid.
  3. Embeds the raw XML in the event payload so auditors can re-verify the XAdES / CAdES signature any time.

Event-type conventions

MessageFamilyevent_type
pain.001painISO20022.pain.pain.001.001.09
pacs.008pacsISO20022.pacs.pacs.008.001.08
pacs.002pacsISO20022.pacs.pacs.002.001.10
camt.053camtISO20022.camt.camt.053.001.08
admi.002admiISO20022.admi.admi.002.001.01

The canonical format is ISO20022.<family>.<variant>.

Quickstart

import (
"context"
"github.com/quidnug/quidnug/integrations/iso20022"
"github.com/quidnug/quidnug/pkg/client"
)
ctx := context.Background()
c, _ := client.New("http://quidnug.local:8080")
rec, _ := iso20022.New(iso20022.Options{
Client: c,
Domain: "bank.settlement",
})
bankQuid, _ := client.QuidFromPrivateHex(os.Getenv("BANK_KEY_HEX"))
_, err := rec.RecordMessage(ctx, bankQuid, "payment-title-id", iso20022.Message{
Family: "pacs",
Variant: "pacs.008.001.08",
Role: iso20022.RoleSettlement,
Raw: rawXML, // the verified signed XML
ParsedHeader: iso20022.Header{
MessageID: "E2E-REF-2024-001",
From: "BANKAUSXXX",
To: "BANKBEBBXXX",
Amount: 50_000.00,
Currency: "EUR",
CreatedAt: time.Now().Unix(),
},
})

Minimal header extraction

For simple flows where you just need the message id and timestamp, a lightweight header parser is provided:

header, _ := iso20022.ExtractHeader(rawXML)
// header.MessageID, header.CreatedAt, header.Reference, header.Extras

For full business-field extraction, use a proper XSD-validated ISO 20022 parser (e.g. moov-io/iso20022) and fill in ParsedHeader yourself.

Verification is the caller’s job

This package does NOT verify XAdES / CAdES signatures on incoming messages. Pair it with an XML signature verification step before calling RecordMessage, a recorded bundle should always be one that has already been validated:

// 1. Verify the XML signature
if err := xades.Verify(rawXML, bankCert); err != nil {
return fmt.Errorf("signature verify: %w", err)
}
// 2. Then record onto Quidnug
rec.RecordMessage(ctx, ...)

Query patterns

Once messages are on-stream, any Quidnug-aware app can ask:

// "Show me every settlement message on this payment."
events, _, _ := c.GetStreamEvents(ctx, paymentTitleID, "bank.settlement", 100, 0)
// "Score the originator's transitive trust from my perspective."
tr, _ := c.GetTrust(ctx, myBankQuid, originatorQuid, "bank.settlement", 5)
// "Reject anything with trust < 0.7."
if tr.TrustLevel < 0.7 {
return ErrUnknownCorrespondent
}

Example

See examples/cross_border_payment.go for a full pain.001 → pacs.008 → pacs.002 flow recorded end-to-end.

Schema mappings

The package is intentionally light on schema specifics, pair it with whichever ISO 20022 library your institution already uses. The role enum is the main editorial choice:

RoleTypical messages
initiationpain.001, pain.008 (customer-initiated)
settlementpacs.008, pacs.009 (FI-to-FI credit transfer)
statuspacs.002 (status report)
statementcamt.053, camt.054 (account / transaction statements)
rejectionadmi.002 (message rejection)
inquirycamt.027 (investigation)
genericanything not mapped above

Status

Working Go package with 4 passing tests. Companion packages for other languages will arrive as their Quidnug SDK reaches v2.x parity.

License

Apache-2.0.