---
title: "FinTechOps"
slug: fintechops
category: industry
term_status: ambiguous
evidence_status: partially_verified
summary: "FinTechOps, as this Atlas uses the term, is the operation of money movement in a financial-technology business: every payment is recorded once in a double-entry ledger, moves over the right rail, and is reconciled against the bank and processor records every day."
audience: [Beginners, Payments and backend engineers, Payment and finance operations teams, SREs in fintech, Fintech founders and leaders]
last_verified: 2026-10-23
key_takeaways:
  - "\"FinTechOps\" is not a standard term: it is a software firm's name, a loose label for operations at fintechs, and (on this page) the work that employers call payment operations. The practices behind it are established and partly regulated."
  - "The core promise is that money moves exactly once and the books agree with the bank. Retries are safe only when an idempotency key and the ledger write commit in one database transaction and the same key or reference goes to the processor or bank."
  - "A payment is not finished when the API says \"succeeded\": cards clear later, US consumer ACH debits can be returned for up to 60 days, and instant payments are irrevocable within seconds. Design states and controls around each rail's finality."
  - "Reconciliation is the control that catches what code misses. Most UK payment and e-money firms must reconcile safeguarded funds every reconciliation day since 7 May 2026, and Synapse's collapse showed what an unreconciled ledger costs customers."
  - "Shrink PCI DSS scope by never letting card numbers reach your systems: tokenise or use hosted fields, then remember that connected systems and page scripts stay in scope."
  - "Measure exactness (duplicate rate, unmatched value, break age) as well as availability; a 100% uptime month can still lose customers' money."
related: [fincrimeops, finops, api-ops, integrationops, workflowops, complianceops, riskops, sre, retailops]
---

## What FinTechOps is

### In 30 seconds

**FinTechOps, as this Atlas uses the term, is the operation of money movement inside a financial-technology business.** It makes
sure each payment is authorised, recorded once in a double-entry ledger, sent over the right payment rail, settled, and reconciled
against the records of the processor and the bank, every day, with an owner for every difference.

In one sentence: **FinTechOps keeps the promise that money moves exactly once and the books agree with the bank.**

It is for payments and backend engineers, payment and finance operations teams, SREs who run payment systems, and founders who
must answer to a partner bank or regulator. Sources last verified **2026-10-23**.

::: warning Not legal or accounting advice
Rules on this page name their jurisdiction and the date checked. Payment law, scheme rules and accounting treatment differ by
country, licence and product, and they change.
:::

### At a glance

| Question | Answer |
|---|---|
| Goal | Every payment is correct, recorded once, settled, reconciled, and explainable to customers, auditors and regulators |
| Who does it | Payments engineers, ledger owners, payment and reconciliation operations, treasury, SRE, compliance partners |
| Scope (this page) | Payment lifecycle on cards, ACH, instant and cross-border rails; double-entry ledgers; idempotency; reconciliation and breaks; safeguarding; PCI DSS scope; open-banking APIs; availability and reporting |
| Out of scope | Money laundering, sanctions and fraud investigations ([FinCrimeOps](../fincrimeops/)); cloud cost ([FinOps](../finops/)); API lifecycle ([API Ops](../api-ops/)); audit evidence ([ComplianceOps](../complianceops/)) |
| Prerequisites | HTTP APIs and databases; basic bookkeeping helps ([API Ops](../api-ops/) is a useful first read) |
| Key outputs | Ledger, posting rules, payment state machine, daily reconciliation and break log, safeguarding records, availability statistics |
| Term status | **Ambiguous**: no standard meaning; the practice is established |

### The name: three readings, one chosen

In searches on 2026-10-23 we found no standard, professional body or certification that defines "FinTechOps". The string has at least three uses:

1. **A company name.** FintechOps is a financial-software development firm in Raleigh, North Carolina, which says it helped the
   forex broker FXCM grow "from a 15-person startup to a publicly-traded company".[^rcp][^fo-site]
2. **"Operations at a fintech"**, used loosely for DevOps or IT operations in financial-technology firms.
3. **The operations of money movement.** Employers staff this work as "Payment Operations": Wise advertised a team lead for its
   "Payment Operations Reconciliation team"[^wise-rec] and specialists who reconcile bank accounts, payments and transactions and
   escalate "reconciliation discrepancies in a timely manner".[^wise-spec]

It is also easily confused with **FinOps**, which the FinOps Foundation defines as "an operational framework and cultural practice
which maximizes the business value of technology" through collaboration between engineering, finance and business;[^finops-def]
that is cloud and technology spend, taught in [FinOps](../finops/).

::: note Editorial distinction
This page uses reading 3. Its boundaries with FinCrimeOps, FinOps, ComplianceOps and API Ops are this Atlas's choices, not an
industry standard.
:::

### What FinTechOps is not

- **Not financial crime work.** Screening, monitoring and suspicious-activity reports belong to [FinCrimeOps](../fincrimeops/).
  The two meet at payment holds and returns.
- **Not accounting close.** The ledger here is the operational record of customer balances and money in flight. The general ledger and
  financial statements are finance's; FinTechOps feeds them reconciled journals.
- **Not a start-up niche.** Banks, marketplaces and payroll firms run the same practices.

### The problem it solves

Two documented failures:

1. **The ledger and the bank disagree, and nobody can say whose money is where.** Synapse ran software between consumer fintech apps
   and partner banks. The US Consumer Financial Protection Bureau (CFPB) alleged that "as early as September 2023, Synapse's records
   did not match the records maintained by that bank", that funds sat in omnibus accounts held "for the benefit of" consumers, and
   that consumers could not reach their money "for weeks or months" while banks reconciled. The bankruptcy trustee estimated the
   shortfall at "between $60 and $90 million".[^cfpb-syn] A stipulated judgment was entered on 12 September 2025.[^cfpb-syn-page]
2. **A change disrupts banking, including payments, for months.** TSB's April 2018 platform migration disrupted branch, telephone,
   online and mobile banking until December 2018; UK regulators fined it £48.65m in 2022.[^tsb] [RiskOps](../riskops/) covers the
   programme-risk side.

Smaller failures are daily: a retry charges a customer twice; a short payout goes unnoticed for a month; a card number in a log
pulls the platform into PCI DSS scope.

**Good looks like:** every movement of money has one journal entry, one owner and one reconciled counterpart outside your systems,
and every difference has a reason and an age.

## Vocabulary, origins and neighbours

### Glossary and mental model

| Term | Meaning | Example |
|---|---|---|
| Rail | A payment system with its own rules, messages and timing | Card scheme, ACH, FedNow, UPI, SEPA Instant, Swift |
| Authorisation | The issuer approves a card payment and places a hold; no money moves yet | A EUR 50.00 hold on a hotel booking |
| Clearing (presentment) | Exchanging the final transaction details so obligations can be computed; for cards, the "first presentment"[^cko] | Merchant captures EUR 48.00 of the hold |
| Settlement | Funds actually transfer between institutions | Scheme moves funds from issuer to acquirer |
| Final settlement | "the irrevocable and unconditional transfer…"; "a legally defined moment"[^pfmi] | A FedNow credit within seconds |
| Payout | A processor pays the merchant its net funds, usually in batches | PO-77 credited to the merchant's bank |
| Return / chargeback | A reversal after settlement under the rail's rules | ACH R10, card dispute |
| Ledger | The system of record of who is owed what, built from immutable journal entries | Wallet balances |
| Double entry | Every entry has equal debits and credits; a balance is debits minus credits or the reverse, by account type[^tb-model] | Customer wallet is a liability |
| Idempotency key | A client-chosen identifier that makes a retried request have no further effect | `p2p-91c0` |
| Reconciliation | Comparing your records with an independent record and explaining every difference | Ledger vs bank statement |
| Break | A difference that reconciliation could not match | Payout EUR 10.00 short |
| Safeguarding | Protecting customer funds held by a payment or e-money firm, for example in segregated accounts | UK CASS 15 |
| PAN / CDE | Primary account number (card number) / cardholder data environment that stores, processes or transmits it | Systems in PCI DSS scope |
| FBO account | A bank account held "for the benefit of" many end users, whose shares live in someone's ledger | A fintech's pooled account |

**Mental model: three answers to "is it paid?", and two books.** Every payment has three answers: the **customer view** (what the
app shows now), the **rail view** (authorised, cleared, settled, still returnable) and the **books view** (posted, reconciled,
reported). There are always two books: **yours** (the ledger) and **theirs** (processor reports, bank statements, scheme files). Most
money incidents happen where the answers disagree, for example showing "paid" before settlement, or where your book has no matching
line in theirs.

### Origins and evolution

The practices are old: double entry, card clearing, ACH batch files. What is newer is **who runs them**: software firms that hold or
move customer money, often on a partner bank's licence. That brought retries, races and schema changes into bookkeeping, and
regulators followed ([safeguarding](#playbook-safeguarding-and-pooled-accounts)). We found no documented origin of the label.

### How FinTechOps relates to other Ops

::: figure FinTechOps and its neighbours in the money chain
```mermaid
flowchart TB
  API["API Ops<br/>API contracts and versions"]:::neutral
  FT["FinTechOps<br/>money moves once,<br/>books reconcile"]:::core
  FC["FinCrimeOps<br/>screening, monitoring,<br/>reports"]:::neutral
  CO["ComplianceOps<br/>control evidence"]:::neutral
  SRE["SRE<br/>SLOs and incidents"]:::neutral
  FIN["Finance<br/>general ledger, close"]:::neutral
  EXT["Banks, processors,<br/>schemes"]:::risk
  API -->|"payment APIs"| FT
  SRE -->|"reliability"| FT
  EXT <-->|"instructions, files"| FT
  FT -->|"payments, events"| FC
  FC -.->|"holds, returns"| FT
  FT -->|"journals"| FIN
  FT -->|"evidence"| CO
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
  classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
```
Text equivalent: API Ops supplies the payment APIs to FinTechOps. FinTechOps sends payments and events to FinCrimeOps and receives
holds and returns back (dotted). It sends reconciled journals to finance's general ledger and control evidence to ComplianceOps, takes its
reliability method from SRE, and exchanges instructions and files with external banks, processors and schemes.
:::

| Neighbour | Shared ground | FinTechOps owns | The neighbour owns |
|---|---|---|---|
| [FinCrimeOps](../fincrimeops/) | Payment data, holds | Correct movement, returns, reconciliation | Screening, monitoring, suspicious-activity reports |
| [FinOps](../finops/) | The word "Fin" | Customers' money | Cloud and technology spend |
| [API Ops](../api-ops/) | Payment APIs | Idempotency semantics, money states | API design, versioning, gateways |
| [WorkflowOps](../workflowops/) | Retries, sagas | Ledger and rail rules | Durable workflow engines ([idempotency](../workflowops/#idempotency)) |
| [IntegrationOps](../integrationops/) | Bank files and messages | What a file means for balances | Transport, mapping, queues |
| [ComplianceOps](../complianceops/) | PCI DSS, audits | Scope design and operating the controls | Control library and audit evidence |
| [SRE](../sre/) | Availability | Payment-specific SLIs (exactness, success rate) | SLO method, error budgets, postmortems |
| [RetailOps](../retailops/) | Card acceptance | Payment platforms and settlement | Tills, terminals, store procedures |
| [RiskOps](../riskops/), [DecisionOps](../decisionops/) | Limits, holds | Applying them to the right payment | Risk method (RiskOps); automated decision logic (DecisionOps) |

## Principles and operating model

### Charter and principles

::: note A proposal, not a standard
This charter is this Atlas's proposal; no body publishes a "FinTechOps charter".
:::

**Mission.** Move customers' money exactly once, know at every moment whose money is where, and prove it every day.

1. **The ledger is the system of record; everything else is a projection.** Balances and reports derive from journal entries.
2. **Entries are immutable and balanced.** Corrections are new entries that reverse or adjust, never updates or deletes.
3. **Every money-moving request carries an idempotency key**, and the key and its effect commit together.
4. **Unknown is a state, not an error.** A timeout from a bank or processor means "outcome unknown"; resolve it by enquiry or by the
   rail's own report, never by blind retry or blind failure.
5. **Reconcile daily against an independent record, and age every break.**
6. **Card numbers stay out of systems that do not need them.**
7. **Design for the rail's finality.** Release goods, credit wallets and pay out only when the rail's risk of reversal is acceptable.

**Non-goals.** Real-time perfection of every report; zero breaks (breaks are information); building your own ledger when a provider's
fits.

### Fundamentals: the rails and their finality

Rails differ in when money is final and how it can come back.

| Rail (examples) | How a payment flows | When it is final | How it comes back |
|---|---|---|---|
| Cards (Visa, Mastercard) | Authorisation and hold, then clearing ("first presentment") and settlement through the scheme; partial capture allowed[^cko] | Settled after clearing; still disputable | Reversal before clearing; disputes and chargebacks after |
| ACH (US) | Originator → ODFI → ACH Operator (the Federal Reserve or The Clearing House) → RDFI, in files[^nacha-how] | Settles in batches; consumer debits stay returnable | Returns; consumer unauthorised or error returns (R10, R11) within 60 days[^nacha-r11][^mt-r10] |
| Instant, gross settlement (FedNow) | Single message, cleared and settled between banks in seconds; a 24x7x365 real-time gross settlement service[^fednow] | Within seconds; payments "are irrevocable"[^fednow] | A payment return (pacs.004), after a return request (camt.056) or when the receiver cannot apply the funds; not a reversal[^fednow-ops] |
| Instant, net settlement (UPI) | Customer to customer "round the clock";[^npci-upi] NPCI nets each bank's position and settles it through banks' RBI accounts in cycles (four a day in 2019)[^npci-settle][^npci-oc65] | Customer credit in seconds; interbank funds at the cycle's settlement | A new payment back; failed credits reversed by RBI deadlines[^rbi-tat] |
| Cross-border (Swift, correspondent banks) | ISO 20022 pacs messages through correspondents; MT payment instructions no longer supported FI-to-FI since 22 November 2025[^swift] | When each leg settles | Return payments, investigations |

Two operational facts follow. **Instant rails remove the safety net**: checks must happen before sending. Under the EU's Instant
Payments Regulation, euro-area providers offer verification of payee (VoP) from 9 October 2025 for standard and instant euro credit
transfers, returning "match", "close match", "no match" or "other"; euro-area payment and e-money institutions must send and receive
instant payments by 9 April 2027.[^ecb-ipr] **Card holds expire**: Stripe documents authorisation windows of five to seven days
for online card payments, by brand and by who initiated the payment (October 2026), so a capture after the window can
fail.[^stripe-hold]

**ISO 20022 in one table.** The message prefix says who talks to whom, which tells reconciliation which file to read:[^bny-iso]

| Family | Between | Examples (replaces) |
|---|---|---|
| `pain` (payment initiation) | Customer → bank | `pain.001` credit transfer initiation (MT 101) |
| `pacs` (clearing and settlement) | Bank → bank | `pacs.008` customer transfer (MT 103); `pacs.004` return |
| `camt` (cash management) | Bank → customer, investigations | `camt.053` statement (MT 940); `camt.054` notification |

### Roles, ownership and RACI

| Role | Owns | Typical skills |
|---|---|---|
| Payments engineering | Rail integrations, payment state machine, idempotency | Distributed systems, ISO 20022 and scheme specifications |
| Ledger owner (product + engineering) | Chart of accounts, posting rules, invariants | Bookkeeping, database transactions |
| Payment / reconciliation operations | Daily reconciliation, breaks, returns, investigations | Bank files, processor reports, judgement |
| Treasury | Liquidity, prefunding, safeguarding accounts, top-ups | Cash management, bank relationships |
| SRE | Availability, incident response, capacity | SLOs, on-call ([SRE](../sre/)) |
| Compliance / safeguarding officer | Regulatory duties, returns, regulator contact | Payment law in each jurisdiction |
| Finance | General ledger, month-end close, audit | Accounting standards |

**This RACI is a proposal** (R = responsible, A = accountable, C = consulted, I = informed).

| Activity | Payments eng | Ledger owner | Recon ops | Treasury | Compliance |
|---|---|---|---|---|---|
| New posting rule or account | C | A/R | C | C | I |
| Daily reconciliation and break triage | I | C | A/R | C | I |
| Safeguarding shortfall top-up | I | C | R (detect) | A/R (fund) | C (notify if required) |
| Unknown-outcome payment resolution | R | C | A | I | I |
| New rail or processor launch (A: product owner) | R | R | C | C | C (sign-off gate) |
| Manual ledger adjustment | – | A | R (four-eyes) | C | I |

### The payment lifecycle

Each payment goes through **initiate → authorise or validate → submit to the rail → settle → reconcile → (possibly) return**. The
platform has its own Day 0/1/2:

- **Day 0 (design):** rails and processors; chart of accounts and posting rules; customer-visible states; external records to
  reconcile against.
- **Day 1 (launch):** simulator tests, then small live amounts; reconciliation from the first live day; compliance sign-off.
- **Day 2 (run):** daily close, breaks, returns and disputes, rail changes, safeguarding records and regulatory returns.

### Operating model and governance

- **Every business day:** the [daily close](#reference-implementation-operating-artefacts): files, reconciliations, breaks,
  safeguarding position, unknown-outcome payments.
- **Weekly:** break ageing with owners; returns and disputes trends; capacity for peaks (paydays, sales, month-end).
- **Monthly:** close with finance; regulatory returns (the UK safeguarding return is due within 15 business days of month
  end);[^sup1614a] review of manual adjustments.
- **Per change:** posting-rule changes reviewed like schema migrations, with replay tests on yesterday's events.
- **Escalation:** a break above a threshold, any safeguarding shortfall or any duplicate payout pages the on-call owner.

## Architecture and workflow

### Reference architecture: from API call to reconciled books

::: figure Reference architecture of a money-movement platform
```mermaid
flowchart TB
  C["Client apps<br/>with idempotency keys"]:::neutral
  API["Payments API<br/>validation, auth"]:::core
  SM["Payment state<br/>machine"]:::core
  L["Double-entry<br/>ledger"]:::core
  OB["Outbox and<br/>rail adapters"]:::core
  EXT["Processors, banks,<br/>schemes"]:::risk
  FILES["Reports, statements,<br/>webhooks"]:::neutral
  REC["Reconciliation<br/>engine"]:::core
  BRK["Break queue<br/>owners, ageing"]:::decision
  RPT["Safeguarding, finance<br/>and regulatory reports"]:::neutral
  C --> API
  API --> SM
  SM -->|"journal entries"| L
  SM --> OB
  OB -->|"instructions"| EXT
  EXT --> FILES
  FILES -->|"status updates"| SM
  FILES --> REC
  L --> REC
  REC --> BRK
  REC --> RPT
  BRK -. "adjusting entries" .-> L
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
  classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
  classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
```
Text equivalent: Client apps call the payments API with idempotency keys. The state machine writes journal entries to the ledger
and queues instructions in an outbox that sends them to processors, banks and schemes. Their reports and webhooks update payment
states and feed the reconciliation engine, which compares them with the ledger, sends unmatched items to a break queue (resolved by
adjusting entries, dotted) and produces safeguarding, finance and regulatory reports.
:::

Design choices that matter more than vendor choice:

| Choice | Option A | Option B | What decides it |
|---|---|---|---|
| Ledger | Build on your database | Ledger service or database (e.g. TigerBeetle, Blnk) | Volume, team skills, need for custom posting rules |
| Sending to rails | Call the processor inside the request | Outbox table written in the same transaction, sent by a worker | Whether a crash between "recorded" and "sent" is acceptable (it rarely is) |
| Balances | Computed from entries on read | Stored running balances updated in the same transaction | Read volume; always keep entries as the source |
| Holds | Debit immediately, refund on failure | Two-phase: pending, then post, void or expire[^tb-2p] | Whether users may see money disappear and return |
| Reconciliation | End of day | Continuous, plus end-of-day sign-off | Rail speed; regulatory minimum is often daily |

### End-to-end workflow: the payment state machine

Most incidents start in the states between "sent" and "known". The state machine below makes them explicit.

::: figure Payment states, including the unknown outcome
```mermaid
stateDiagram-v2
  state answer <<choice>>
  [*] --> Created
  Created --> Pending: validated, ledger hold
  Pending --> Submitted: outbox sends
  Submitted --> answer: rail replies
  Submitted --> Unknown: timeout or no reply
  Unknown --> answer: enquiry or rail report
  answer --> Succeeded: confirmed
  answer --> Failed: rejected
  Failed --> [*]: hold released
  Succeeded --> Settled: settlement file
  Settled --> Returned: early return
  Settled --> Reconciled: matched
  Reconciled --> Returned: late return or dispute
  Reconciled --> Final: return windows closed
  Returned --> Final: reversal posted, matched
  Final --> [*]
```
Text equivalent: Created, then pending (validated, ledger hold), then submitted. The rail's answer (a reply, or for an unknown
outcome a status enquiry or the rail's own report) leads to succeeded or to failed (hold released). Succeeded payments settle and
reconcile when matched. A return can arrive before matching, or with a dispute after reconciliation, even weeks later; its reversal
is posted and matched as a new event. A payment is final only when its return and dispute windows have closed.
:::

**Happy path.** A wallet top-up by card: the API validates the request and key; the ledger records a receivable from the processor
and a liability to the customer; the processor confirms; the next day its settlement report and the bank credit match the ledger.

**Exception paths:**

- **Timeout after submission (Unknown).** Do not resend a new instruction. Query the status with the same reference, or wait for
  the rail's report. In India, the RBI's turn-around-time framework requires a UPI transfer debited without beneficiary credit to be
  reversed by the beneficiary bank by T+1, with ₹100 per day compensation beyond that (circular of 20 September 2019; check for later
  amendments).[^rbi-tat]
- **Rejected after hold.** Release the hold with a reversing entry and tell the customer the reason code in plain words.
- **Settled, then returned.** Post the return as a new entry; if the funds were already paid out, the shortfall becomes a receivable
  from the customer or a loss.
- **Late presentment.** Checkout.com's issuing documentation says a presentment arriving after an issuer reversal is honoured, and the
  issuer may then raise an "Authorization-Related Chargeback"; ledgers must accept a capture with no live hold.[^cko]

### The card payment lifecycle in messages

::: figure A card payment from authorisation to payout
```mermaid
sequenceDiagram
  participant M as Merchant
  participant P as Processor
  participant S as Scheme
  participant I as Issuer
  M->>P: Authorise 50.00
  P->>S: Auth request
  S->>I: Auth request
  I->>S: Approved, hold
  S->>P: Approved
  P->>M: Approved
  M->>P: Capture 48.00
  P->>S: Clearing record
  S->>I: Presentment
  I->>S: Settle funds
  S->>P: Settle funds
  P->>M: Net payout and report
```
Text equivalent: An authorisation for 50.00 passes from merchant to processor, scheme and issuer, which approves and places a hold.
Later the merchant captures 48.00, sent as a clearing record and presented to the issuer. Funds settle from issuer through scheme to
processor, which pays the merchant a net payout with a report.
:::

The merchant sees three amounts: authorisation (50.00, today), capture (48.00, at shipping) and payout (48.00 minus fees, days
later). Reconciliation must link all three.

## Practices, frameworks and tools

### Core practices and playbooks

The [charter](#charter-and-principles) says what must stay true; these playbooks cover the work that most often goes wrong.

#### Playbook: idempotent money movement

1. **The client generates the key and stores it before sending.** TigerBeetle's guidance is that the app or browser, not your API,
   generates the transfer ID, persists it locally and reuses it on every retry.[^tb-submit] A key per *business intent* ("pay invoice
   841 once"), not per HTTP attempt.
2. **Record the key and the effect in one transaction.** The AWS Builders' Library warns that recording the token and doing the
   mutating work must be one atomic (ACID) operation, or you can record the token and fail to act, or act and fail to record
   it.[^aws-idem] In a ledger, the unique key and the journal entry commit together (Lab 1 and Lab 3).
3. **Compare the payload on replay:** same request, stored result; different request, error. Stripe errors if parameters differ
   from the original, and its v1 API returns the saved response on retries, "including 500 errors" (as of October 2026).[^stripe-idem]
   The IETF Idempotency-Key header draft, still an Internet-Draft (revision 08, 22 October 2026), suggests HTTP 422
   for a reused key with a different payload and 409 Conflict for a retry that arrives while the original is still running.[^ietf-idem]
4. **Pass the key downstream.** Send the same key or reference to the processor or bank, so their side de-duplicates too. Your
   idempotency protects your database, not the rail. Providers keep keys only for a time: Stripe v1 keys may be pruned after 24
   hours, after which a reused key makes a new request,[^stripe-idem] so a late retry first queries by your own reference.
5. **Keep your keys longer than any retry can arrive** (this Atlas's rule), including offline mobile clients and batch replays; never
   put personal data in a key.[^stripe-idem]

**Accept when** a replay test (same request sent 2, 10 and 100 times, including concurrently) produces one journal entry and one
rail instruction.

#### Playbook: posting rules for a double-entry ledger

1. **Name accounts by owner and purpose** (`wallet:<customer>`, `receivable:psp`, `bank:safeguarding`, `expense:psp_fees`) and give
   each a normal side (debit or credit).
2. **Write one posting rule per business event** before writing code, and review it with finance.
3. **Enforce invariants in the database:** entries balance; amounts are positive integers in minor units; currency per entry; no
   updates or deletes on postings.
4. **Model money in flight explicitly.** A card top-up creates a receivable from the processor until the payout arrives; a pending
   transfer reserves funds until posted, voided or expired.[^tb-2p]
5. **Keep one currency per entry**; foreign exchange is two balanced legs through an FX account, with the rate stored.

| Business event | Debit | Credit | Notes |
|---|---|---|---|
| Card top-up approved (100.00) | `receivable:psp` 100.00 | `wallet:alice` 100.00 | Customer can spend; money is still at the processor |
| Processor payout (fee 1.80) | `bank:safeguarding` 98.20; `expense:psp_fees` 1.80 | `receivable:psp` 100.00 | Fee is the firm's cost, not the customer's |
| Own funds cover the fee | `bank:safeguarding` 1.80 | `bank:operating` 1.80 | Transfer from the firm's own account (Lab 1) |
| Wallet-to-wallet transfer (25.00) | `wallet:alice` 25.00 | `wallet:bob` 25.00 | No external movement |
| Card chargeback (40.00) | `wallet:alice` 40.00 (or `receivable:customer`) | `receivable:psp` 40.00 | If the wallet is empty, a receivable from the customer |

#### Playbook: daily reconciliation and break management

1. **List every independent record:** processor settlement reports, payout notifications, bank statements (for example ISO 20022
   camt notifications; FedNow sends camt.054 debit and credit notifications),[^fednow] scheme files, partner-bank balances.
2. **Reconcile in legs.** Ledger ↔ processor (each payment), processor ↔ bank (each payout batch, net of fees and adjustments),
   ledger ↔ bank (balances). Lab 2 runs all three. On net-settled rails such as UPI the bank movement is one net amount per
   settlement cycle, so reconcile per cycle.[^npci-settle]
3. **Classify each break:** timing (expected later, often from cut-offs, bank holidays or time zones), amount mismatch, missing on one side, duplicate, unbooked adjustment (chargeback,
   reserve, fee change).
4. **Give every break an owner, a reason code and an age.** Timing items need an expected date; once past it, they become breaks.
5. **Fix the cause, not only the item.** A break class that recurs is a bug or a missing posting rule.
6. **Sign off the day:** record time, actions and outcome. UK safeguarding rules require exactly that for each safeguarding
   reconciliation.[^cass15]

**Accept when** the day is signed off before the funding deadline, every open break has an owner and expected resolution date, and
breaks older than the agreed age are escalated.

#### Playbook: safeguarding and pooled accounts

Customer money held by a non-bank usually sits in pooled accounts at banks; the firm's ledger says who owns which share. The ledger
is then the only record of each customer's entitlement, which makes reconciliation a customer-protection control. Rules as of
October 2026:

- **UK safeguarding institutions (authorised payment and e-money firms, with exceptions; FCA CASS 15, in force 7 May
  2026):** internal and external safeguarding reconciliations "no less than once each reconciliation day"; the internal one uses the
  firm's own records "rather than" bank statements; a shortfall is paid in "by the end of the day on which the reconciliation is
  performed"; record time, date, actions and outcome.[^cass15][^ps2512] A monthly return goes to the FCA.[^sup1614a]
- **US bank–fintech arrangements:** US regulators noted in 2024 that third parties may maintain "the deposit and transaction system of
  record", and that a bank lacking access to it may be unable to determine its deposit obligations, delaying end users' access.[^baas]
  The FDIC proposed in 2024 that banks reconcile custodial beneficial ownership records "no less frequently than as of the close of
  business daily"; we found no final rule as of 23 October 2026.[^fdic-cust] New interagency third-party risk guidance is open for
  comment until 16 November 2026.[^tprm26]

**The control:** daily, independent, signed-off reconciliation at the beneficial-owner level.

#### Playbook: reducing PCI DSS scope

PCI DSS v4.0.1 has been the active version since PCI DSS v4.0 was retired on 31 December 2024; its future-dated requirements took
effect on 31 March 2025 (check the PCI SSC document library for later revisions).[^pci-401][^pci-saqa]

1. **Keep card numbers out.** Use the processor's hosted payment page, hosted fields or a tokenisation service, so your systems hold
   only tokens. The PCI SSC's tokenisation guidance says that if the merchant can retrieve the card number (PAN), its environment is in
   scope; the tokenisation system is part of its operator's cardholder data environment.[^pci-tok] If a provider operates it, you
   manage that provider.
2. **Know whether you are a service provider.** Storing, processing or transmitting card data "on behalf of another entity" makes
   you a PCI DSS service provider, with its own validation path.[^pci-sp]
3. **Map connected systems.** Systems with connectivity to the CDE are "connected to" systems and in scope; attackers often enter
   through systems an entity considered out of scope.[^pci-scope]
4. **Protect payment pages.** Since 31 March 2025, merchants validating with SAQ A must confirm their site is "not susceptible to
   attacks from scripts that could affect the merchant's e-commerce system(s)"; the payment-page script requirements 6.4.3 and 11.6.1
   left SAQ A only, and the PCI SSC says the change does "not remove or diminish the underlying requirements".[^pci-saqa]
5. **Search logs and support tools for card numbers** on a schedule; a PAN in a ticket or log line expands scope.

Audit evidence and the control calendar belong to [ComplianceOps](../complianceops/).

#### Playbook: operating open-banking APIs

- **Security profile.** The OpenID Foundation's FAPI 2.0 Security Profile (final, 22 February 2025) allows only confidential clients
  and sender-constrained access tokens, bound with mutual TLS or DPoP.[^fapi2]
- **Availability you can prove.** UK Open Banking's operational guidelines (v4.0.1, March 2026) set availability and performance KPIs
  for banks' dedicated interfaces, following the EBA Guidelines, and require published statistics.[^ob-avail] Third parties should
  measure the same KPIs from their side.
- **Regulation in motion (October 2026).** The EU's PSD3 and Payment Services Regulation (provisional agreement 27 November 2025, ECON
  approval 5 May 2026) were "close to adoption" on the European Parliament's tracker in September 2026.[^ep-psr] The US CFPB's personal financial data rights rule (published 18 November 2024) has had its compliance
  dates stayed by a court since 29 October 2025 while the CFPB reconsiders it.[^cfpb-1033]

### Methods, standards and frameworks

| Source | Status (October 2026) | What FinTechOps takes from it |
|---|---|---|
| Rail and scheme rules (Nacha Operating Rules, card scheme rules, Swift/ISO 20022 usage guidelines)[^nacha-r11][^swift] | Binding by contract on participants | Messages, cut-offs, returns, disputes |
| PFMI (CPSS-IOSCO, 2012)[^pfmi] | International standard for financial market infrastructures, not fintech apps | Settlement finality; resume critical IT within two hours and settle by end of day, a useful benchmark |
| PCI DSS v4.0.1[^pci-401] | Contractual standard via card brands and acquirers | Card data protection, scoping, payment-page security |
| UK CASS 15, SUP 16.14A[^cass15][^sup1614a] | UK rules for payment and e-money firms, from 7 May 2026 | Daily safeguarding reconciliation, records, monthly return |
| EU Instant Payments Regulation (2024/886)[^ecb-ipr] | EU law, phased 2025–2028 | Instant sending, verification of payee |
| RBI failed-transaction TAT framework (2019)[^rbi-tat] | Indian regulatory circular | Reversal deadlines and compensation |
| FAPI 2.0 Security Profile[^fapi2] | OpenID Foundation final specification | Open-banking API security |
| Operational resilience: UK FCA (in force 31 March 2022; transition ended 31 March 2025),[^fca-opres] EU DORA (from 17 January 2025),[^dora] US incident notification (36 hours)[^cfr53] | Law and rules, by jurisdiction | Important services, impact tolerances, incident reporting |
| IETF Idempotency-Key draft[^ietf-idem] | Internet-Draft, not a standard | Common semantics for retries |

### Tooling landscape

Examples verified on 2026-10-23, not recommendations.

| Category | Job | Examples (verified) | Selection criteria | Watch-outs |
|---|---|---|---|---|
| Ledger | Double-entry records, balances, holds | TigerBeetle (Apache-2.0),[^gh-tb] Blnk (Apache-2.0),[^gh-blnk] your own relational database | Invariants enforced in the store; idempotent writes; audit trail | A "ledger" that allows balance updates |
| Core banking | Accounts, products, interest | Apache Fineract (Apache-2.0)[^gh-fineract] | Product fit, regulatory reporting | Scope much wider than a ledger |
| Rail file tooling | Build and validate files | Moov ACH reader, writer and validator (Apache-2.0)[^gh-moovach] | Spec coverage, maintenance | Bank-specific file variants |
| Processor / PSP | Card and local payment acceptance | Commercial processors | Reports with fees and adjustments per payment, idempotent APIs, webhooks | Net payouts you cannot break down |
| Reconciliation | Matching, break queues | Commercial tools or in-house (Lab 2) | Rules per break type, ageing, audit trail | Auto-matching with tolerances that hide errors |
| Observability | SLIs for success rate and latency | General tools ([SRE](../sre/)) | Per-rail and per-bank views | Averages that hide one failing bank |

### Automation and AI

Automate matching, break classification, status enquiries and customer messages. Automation is riskiest where it **moves money or
posts adjustments without a second person**: keep four-eyes approval above a threshold. Language models can summarise break
histories for an analyst; they should not decide postings. Fraud and AML models belong to [FinCrimeOps](../fincrimeops/).

## FinTechOps in practice

### Reference implementation: operating artefacts

A minimal set for a small wallet or marketplace business (this Atlas's proposals; adapt names to your rails and licence).

**1. Break record**, one per unmatched item, in the reconciliation tool.

_Example status: syntax-checked (YAML), not executed; fictional data._

```yaml
break_id: BRK-2026-11-03-0007
reconciliation: processor_to_bank   # or ledger_to_processor, ledger_to_bank
detected_at: 2026-11-03T07:40:00Z
kind: unbooked_adjustment           # timing, amount_mismatch, missing_*, duplicate
external_ref: CB-551
amount_minor: -1000
currency: EUR
explanation: Chargeback on P-0950 deducted from payout PO-77; no ledger entry yet
owner: recon-ops-2
expected_resolution: 2026-11-03
resolution:
  action: post_entry                # or chase_counterparty, write_off, no_action
  journal_ref: J-2026-11-03-1182         # adjusting entry, four-eyes approved
  approved_by: [recon-ops-2, ledger-owner-1]
root_cause: chargeback webhook not mapped to a posting rule
prevention: posting rule CHARGEBACK_V1 added; replay test passed
```

**2. Daily close SOP** (one page; times are an example for a UK firm).

```text
DAILY MONEY CLOSE - owner: reconciliation lead; backup: treasury analyst
07:00  Files in? Processor settlement reports, payout notices, bank statements.
       Missing file at 07:30 -> chase counterparty, open incident if past cut-off.
07:30  Run reconciliations: ledger-processor, processor-bank, ledger-bank.
08:30  Triage new breaks: owner, reason code, expected date. Escalate > threshold.
09:00  Unknown-outcome payments older than 1 hour: status enquiry or rail report.
10:00  Safeguarding position: requirement vs resource. Shortfall -> treasury tops
       up from own funds today; record time, actions, outcome.
11:00  Sign-off by reconciliation lead; summary to finance and compliance.
```

**3. Unknown-outcome policy**, kept in configuration so engineers and operations read the same rules.

_Example status: syntax-checked (JSON), not executed._

```json
{
  "policy": "unknown_outcome_v2",
  "applies_to": ["card_capture", "instant_credit", "ach_origination"],
  "on_timeout": "mark_unknown_and_keep_hold",
  "never": ["resend_with_new_reference", "release_hold_without_rail_answer"],
  "status_enquiry": {
    "first_after_seconds": 30,
    "backoff": "exponential",
    "max_attempts": 8
  },
  "escalate_to_ops_after_minutes": 60,
  "customer_message": "Your payment is being confirmed. You have not been charged twice."
}
```

The [posting-rule table](#playbook-posting-rules-for-a-double-entry-ledger) and the [daily checklist](#checklists) complete the set.

### Hands-on labs

Three labs on **synthetic data**: Python 3.8 or later with the standard library (SQLite is built in); no cost, no network, no real
accounts. Expected outputs are copied from runs on 2026-10-23 (Python 3.8.10, Ubuntu).

::: warning Never practise on real payment data
Use invented data only. Card numbers, bank details and customer balances are regulated personal and financial data.
:::

#### Lab 1 (beginner): a double-entry ledger with idempotent posting

**Objective.** See balanced entries, safe retries, conflict detection and a safeguarding-style check in one small ledger.
**Prerequisites.** Python 3.8+. **Time.** 25 minutes.

1. Create a folder: `mkdir -p ~/fintech-labs && cd ~/fintech-labs`.
2. Save this as `ledger.py`.

_Example status: verified by execution on 2026-10-23 (Python 3.8.10)._

```python
"""Lab 1: a double-entry ledger with idempotent posting (SQLite, stdlib only).
Amounts are integer minor units (cents). All data is invented. Python 3.8+."""
import hashlib
import json
import sqlite3
import sys

SCHEMA = """
CREATE TABLE IF NOT EXISTS account (
  id TEXT PRIMARY KEY,
  normal TEXT NOT NULL CHECK (normal IN ('debit', 'credit')));
CREATE TABLE IF NOT EXISTS journal (
  id INTEGER PRIMARY KEY,
  idem_key TEXT NOT NULL UNIQUE,      -- one business intent, one journal
  request_hash TEXT NOT NULL,
  memo TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS posting (
  journal_id INTEGER NOT NULL REFERENCES journal(id),
  account_id TEXT NOT NULL REFERENCES account(id),
  debit INTEGER NOT NULL DEFAULT 0 CHECK (debit >= 0),
  credit INTEGER NOT NULL DEFAULT 0 CHECK (credit >= 0));
"""

ACCOUNTS = [("bank:safeguarding", "debit"), ("bank:operating", "debit"),
            ("receivable:psp", "debit"), ("expense:psp_fees", "debit"),
            ("wallet:alice", "credit"), ("wallet:bob", "credit"),
            ("equity:own_funds", "credit")]


class Conflict(Exception):
    """Same idempotency key, different request: refuse (HTTP 422 style)."""


def connect(path=":memory:"):
    db = sqlite3.connect(path, isolation_level=None)  # explicit transactions
    db.executescript(SCHEMA)
    db.executemany("INSERT OR IGNORE INTO account VALUES (?, ?)", ACCOUNTS)
    return db


def post(db, idem_key, memo, lines):
    """lines: [(account, debit, credit)]. Returns (journal_id, replayed)."""
    if sum(d for _, d, _ in lines) != sum(c for _, _, c in lines):
        raise ValueError(f"unbalanced entry: {memo}")
    if any(d < 0 or c < 0 or (d == 0) == (c == 0) for _, d, c in lines):
        raise ValueError("each line needs exactly one positive side")
    req = hashlib.sha256(json.dumps([memo, lines]).encode()).hexdigest()
    db.execute("BEGIN IMMEDIATE")
    try:
        row = db.execute("SELECT id, request_hash FROM journal WHERE idem_key = ?",
                         (idem_key,)).fetchone()
        if row:
            db.execute("ROLLBACK")
            if row[1] != req:
                raise Conflict(f"key {idem_key} reused with a different request")
            return row[0], True
        jid = db.execute("INSERT INTO journal (idem_key, request_hash, memo) "
                         "VALUES (?, ?, ?)", (idem_key, req, memo)).lastrowid
        db.executemany("INSERT INTO posting VALUES (?, ?, ?, ?)",
                       [(jid, a, d, c) for a, d, c in lines])
        db.execute("COMMIT")
        return jid, False
    except sqlite3.Error:
        db.execute("ROLLBACK")
        raise


def balances(db):
    q = """SELECT a.id, a.normal, COALESCE(SUM(p.debit), 0), COALESCE(SUM(p.credit), 0)
           FROM account a LEFT JOIN posting p ON p.account_id = a.id
           GROUP BY a.id ORDER BY a.id"""
    for acc, normal, dr, cr in db.execute(q):
        yield acc, (dr - cr) if normal == "debit" else (cr - dr)


def show(db):
    dr, cr = db.execute("SELECT SUM(debit), SUM(credit) FROM posting").fetchone()
    print(f"trial balance: debits={dr} credits={cr} balanced={dr == cr}")
    for acc, bal in balances(db):
        print(f"  {acc:<18} {bal / 100:>9.2f}")


if __name__ == "__main__":
    db = connect()
    print(post(db, "capital-2026-11-01", "Owners fund the operating account",
               [("bank:operating", 50000, 0), ("equity:own_funds", 0, 50000)]))
    print(post(db, "topup-7f3a", "Alice tops up 100.00 by card",
               [("receivable:psp", 10000, 0), ("wallet:alice", 0, 10000)]))
    print(post(db, "settle-2026-11-02", "PSP settles net of 1.80 fee",
               [("bank:safeguarding", 9820, 0), ("expense:psp_fees", 180, 0),
                ("receivable:psp", 0, 10000)]))
    xfer = [("wallet:alice", 2500, 0), ("wallet:bob", 0, 2500)]
    print(post(db, "p2p-91c0", "Alice pays Bob 25.00", xfer))
    print(post(db, "p2p-91c0", "Alice pays Bob 25.00", xfer), "<- client retry")
    try:
        post(db, "p2p-91c0", "Alice pays Bob 25.00",
             [("wallet:alice", 5000, 0), ("wallet:bob", 0, 5000)])
    except Conflict as e:
        print("conflict:", e)
    try:
        post(db, "bad-1", "typo", [("wallet:alice", 100, 0), ("wallet:bob", 0, 10)])
    except ValueError as e:
        print("rejected:", e)
    if "--top-up" in sys.argv:  # treasury moves the fee from the firm's own account
        post(db, "topup-own-2026-11-02", "Own funds cover PSP fee",
             [("bank:safeguarding", 180, 0), ("bank:operating", 0, 180)])
    show(db)
    owed = sum(b for a, b in balances(db) if a.startswith("wallet:"))
    held = dict(balances(db))["bank:safeguarding"]
    print(f"customer money owed {owed / 100:.2f}; held {held / 100:.2f}; "
          f"shortfall {(owed - held) / 100:.2f}")
```

3. Run it, then run it with the own-funds top-up.

_Example status: verified by execution on 2026-10-23._

```bash
python3 ledger.py
python3 ledger.py --top-up | grep -E "bank:|owed"
```

**Expected output** (first run in full, then the filtered lines of the second run):

```text
(1, False)
(2, False)
(3, False)
(4, False)
(4, True) <- client retry
conflict: key p2p-91c0 reused with a different request
rejected: unbalanced entry: typo
trial balance: debits=72500 credits=72500 balanced=True
  bank:operating        500.00
  bank:safeguarding      98.20
  equity:own_funds      500.00
  expense:psp_fees        1.80
  receivable:psp          0.00
  wallet:alice           75.00
  wallet:bob             25.00
customer money owed 100.00; held 98.20; shortfall 1.80
```

```text
  bank:operating        498.20
  bank:safeguarding     100.00
customer money owed 100.00; held 100.00; shortfall 0.00
```

**Validation.** Journal 1 is the owners' opening capital in the firm's operating account. The retry returns journal 4 again with
`True` (replayed), so Alice is charged once. Reusing the key with 50.00 is
refused, and an unbalanced entry never reaches the database. Debits equal credits, yet customer money owed (100.00) exceeds money held
(98.20): the processor fee was taken from customer funds. A transfer from the operating account closes the gap, as a UK safeguarding
shortfall must be closed the same day.[^cass15]

**Troubleshooting.** An uncaught `Conflict` means the two `p2p-91c0` calls no longer send identical lines (one was edited).
Different balances mean the posting lines changed.

**Teardown.** Nothing is stored (the database is in memory). Lab 3 removes the folder.

#### Lab 2 (intermediate): three-way reconciliation

**Objective.** Reconcile a ledger, a processor settlement report and a bank statement; separate timing from real breaks.
**Prerequisites.** Lab 1 folder. **Time.** 30 minutes.

1. Save this as `recon.py`.

_Example status: verified by execution on 2026-10-23 (Python 3.8.10)._

```python
"""Lab 2: three-way reconciliation of ledger, PSP settlement report and bank
statement for one business day. Synthetic data; stdlib only (Python 3.8+)."""
import csv
import io
from collections import Counter
from datetime import date

AS_OF = date(2026, 11, 3)

LEDGER = """ref,amount,captured_on
P-1001,5000,2026-11-02
P-1002,12000,2026-11-02
P-1003,2599,2026-11-02
P-1004,7500,2026-11-02
P-1005,4000,2026-11-02
P-1005,4000,2026-11-02
P-1006,9900,2026-11-03
"""
# PSP report: gross, fee, net and the batch the net will be paid in.
PSP = """ref,gross,fee,net,payout_id
P-1001,5000,90,4910,PO-77
P-1002,12000,150,11850,PO-77
P-1003,2599,61,2538,PO-77
P-1004,7000,135,6865,PO-77
P-1006,9900,124,9776,PO-78
P-1007,3000,72,2928,PO-77
"""
# Adjustments deducted from payouts (chargebacks, reserves), per payout batch.
ADJUST = """adj_id,payout_id,amount,reason
CB-551,PO-77,-1000,chargeback on P-0950
"""
PAYOUT_DATES = {"PO-77": date(2026, 11, 3), "PO-78": date(2026, 11, 4)}
BANK = """bank_ref,amount,value_date
PO-77,28091,2026-11-03
"""
BOOKED_ADJUSTMENTS = set()  # adj_ids already posted in the ledger


def rows(text):
    return list(csv.DictReader(io.StringIO(text)))


def reconcile():
    breaks = []
    ledger = rows(LEDGER)
    counts = Counter(r["ref"] for r in ledger)
    for ref, n in counts.items():
        if n > 1:
            breaks.append(("duplicate_in_ledger", ref, f"{n} postings"))
    led = {r["ref"]: int(r["amount"]) for r in ledger}
    psp = {r["ref"]: r for r in rows(PSP)}
    for ref, amt in led.items():
        p = psp.get(ref)
        if p is None:
            breaks.append(("missing_at_psp", ref, f"ledger {amt}"))
        elif int(p["gross"]) != amt:
            breaks.append(("amount_mismatch", ref, f"ledger {amt} psp {p['gross']}"))
    for ref in psp.keys() - led.keys():
        breaks.append(("missing_in_ledger", ref, f"psp gross {psp[ref]['gross']}"))
    # Leg 2: each PSP payout batch against the bank statement.
    payouts = Counter()
    for p in psp.values():
        payouts[p["payout_id"]] += int(p["net"])
    for a in rows(ADJUST):
        payouts[a["payout_id"]] += int(a["amount"])
        if a["adj_id"] not in BOOKED_ADJUSTMENTS:
            breaks.append(("unbooked_adjustment", a["adj_id"], a["reason"]))
    bank = {r["bank_ref"]: int(r["amount"]) for r in rows(BANK)}
    for po, expected in sorted(payouts.items()):
        got = bank.get(po)
        if got is None and PAYOUT_DATES[po] > AS_OF:
            due = PAYOUT_DATES[po]
            breaks.append(("in_transit", po, f"expected {expected} on {due}"))
        elif got is None:
            breaks.append(("payout_not_in_bank", po, f"expected {expected}"))
        elif got != expected:
            breaks.append(("payout_amount_diff", po, f"expected {expected} bank {got}"))
    matched = sum(1 for ref in led if ref in psp and int(psp[ref]["gross"]) == led[ref]
                  and counts[ref] == 1)
    return breaks, matched, len(led)


if __name__ == "__main__":
    breaks, matched, total = reconcile()
    real = [b for b in breaks if b[0] != "in_transit"]
    print(f"as of {AS_OF}: {matched}/{total} ledger refs matched "
          f"({matched / total:.0%}); {len(real)} breaks, "
          f"{len(breaks) - len(real)} in transit")
    for kind, ref, detail in sorted(breaks):
        print(f"  {kind:<20} {ref:<7} {detail}")
```

2. Run it: `python3 recon.py`.

**Expected output:**

```text
as of 2026-11-03: 4/6 ledger refs matched (67%); 5 breaks, 1 in transit
  amount_mismatch      P-1004  ledger 7500 psp 7000
  duplicate_in_ledger  P-1005  2 postings
  in_transit           PO-78   expected 9776 on 2026-11-04
  missing_at_psp       P-1005  ledger 4000
  missing_in_ledger    P-1007  psp gross 3000
  unbooked_adjustment  CB-551  chargeback on P-0950
```

**Validation and interpretation.**

- **P-1004:** ledger 75.00, processor 70.00: an unrecorded partial capture or a wrong amount.
- **P-1005:** posted twice and unknown to the processor: a retry without idempotency, possibly never captured. Investigate before
  telling the customer anything.
- **P-1007:** a processor payment the ledger lacks (lost webhook?); the customer may be owed a credit.
- **CB-551:** PO-77 matches the bank only after the 10.00 chargeback, which the ledger has not booked.
- **PO-78:** due tomorrow; timing, not a break, until its date passes.

**Troubleshooting.** A `KeyError` in `PAYOUT_DATES` means a payout ID was added to the data without a date.

**Teardown.** Lab 3 removes the folder.

#### Lab 3 (advanced): the race that pays twice

**Objective.** Show why "check the key, then insert" double-pays under concurrency, and how a database unique constraint fixes it.
**Prerequisites.** Lab 1 folder. **Time.** 20 minutes.

1. Save this as `race.py`. Two connections replay a fixed interleaving: both read before either writes.

_Example status: verified by execution on 2026-10-23 (Python 3.8.10)._

```python
"""Lab 3: two retries of one payment race through check-then-insert.
Two SQLite connections replay a fixed interleaving. Python 3.8+, stdlib."""
import os
import sqlite3
import tempfile


def setup(path, unique):
    db = sqlite3.connect(path, isolation_level=None)
    key = "UNIQUE" if unique else ""
    db.executescript(f"""
      DROP TABLE IF EXISTS payout;
      CREATE TABLE payout (id INTEGER PRIMARY KEY, idem_key TEXT NOT NULL {key},
                           amount INTEGER NOT NULL);""")
    db.close()


def check(db, key):
    """Step 1 of the naive handler: 'have we seen this key?'"""
    return db.execute("SELECT id FROM payout WHERE idem_key = ?", (key,)).fetchone()


def insert(db, key, amount):
    """Step 2: record the payout (and, in real life, call the bank)."""
    try:
        db.execute("BEGIN IMMEDIATE")
        db.execute("INSERT INTO payout (idem_key, amount) VALUES (?, ?)", (key, amount))
        db.execute("COMMIT")
        return "sent payout"
    except sqlite3.IntegrityError:
        db.execute("ROLLBACK")
        return "duplicate refused; return the stored result"


def run(unique):
    path = os.path.join(tempfile.mkdtemp(prefix="race-"), "race.db")
    setup(path, unique)
    a = sqlite3.connect(path, isolation_level=None)
    b = sqlite3.connect(path, isolation_level=None)
    key = "payout-2026-11-03-0042"
    seen_a, seen_b = check(a, key), check(b, key)  # both read before either writes
    print(f"  A sees key: {seen_a is not None}; B sees key: {seen_b is not None}")
    print("  A:", insert(a, key, 50000) if not seen_a else "replay")
    print("  B:", insert(b, key, 50000) if not seen_b else "replay")
    n, total = a.execute("SELECT COUNT(*), SUM(amount) FROM payout").fetchone()
    print(f"  payouts recorded: {n}; money sent: {total / 100:.2f}")
    a.close()
    b.close()
    os.remove(path); os.rmdir(os.path.dirname(path))


if __name__ == "__main__":
    print("naive check-then-insert, no unique constraint:")
    run(unique=False)
    print("same code, UNIQUE(idem_key) enforced by the database:")
    run(unique=True)
```

2. Run it: `python3 race.py`.

**Expected output:**

```text
naive check-then-insert, no unique constraint:
  A sees key: False; B sees key: False
  A: sent payout
  B: sent payout
  payouts recorded: 2; money sent: 1000.00
same code, UNIQUE(idem_key) enforced by the database:
  A sees key: False; B sees key: False
  A: sent payout
  B: duplicate refused; return the stored result
  payouts recorded: 1; money sent: 500.00
```

**Validation.** Without the constraint both retries pass the check and two payouts are sent. With `UNIQUE(idem_key)` the database
refuses the second insert, and the handler returns the stored result. In production the rail instruction must be sent only after the
insert commits (for example from an outbox), carrying the same key.[^aws-idem]

**Troubleshooting.** `database is locked` means another program holds the file; the script uses a fresh temporary directory per run.

**Teardown.** `rm -r ~/fintech-labs` (the script removes its own temporary database).

### Use cases and scenarios

The first case applies the sourced Synapse facts; the other two are illustrative. Methods are this Atlas's proposals.

**1. A fintech app on a partner bank's pooled account (BaaS).** *Context:* customer funds sit in a "for the benefit of" account at a
partner bank; the fintech or a middleware provider keeps the per-customer ledger.[^cfpb-syn] *Method:* one
beneficial-owner ledger the bank can read; daily reconciliation of customer balances to the bank balance, signed by both sides; a
contract that gives the bank the system of record if the provider fails.[^baas] *Success:* zero unexplained difference at each
close. *Pitfall:* a middleware provider moving funds between several banks with no per-bank reconciliation.

**2. Marketplace payouts through a processor (illustrative).** *Context:* a marketplace pays sellers weekly, net of commission.
*Method:* posting rules for gross, fees, commission and seller payable; [Lab 2](#lab-2-intermediate-three-way-reconciliation)'s
reconciliation; a payout reserve sized to the dispute window. *Success:* payout batches matched on the value date; no seller paid
twice. *Pitfall:* paying sellers on the processor's "succeeded" event, then absorbing chargebacks.

**3. Instant payments around the clock (illustrative).** *Context:* a firm starts sending irrevocable instant credit transfers.
*Method:* verification of payee where offered;[^ecb-ipr] pre-send fraud checks with [FinCrimeOps](../fincrimeops/); per-bank
success-rate monitoring; an unknown-outcome policy and customer messages before launch. *Success:* per-bank success rate and
unknown-state age within target, every hour. *Pitfall:* letting the customer resend after a timeout.

## Measure, govern, pay

### Metrics and KPIs

Uptime hides partial degradation. NPCI reports UPI at 100% for most months to August 2025, and April 2025 at 99.72%: about 121
minutes of a 30-day month, while its downtime table lists 300 minutes marked "Partial & Intermittent", at 50% and 80% success
rates.[^npci-uptime] Measure **exactness** and **success** as well as uptime. Formulas are this Atlas's.

| KPI | Formula | Type | Pitfall |
|---|---|---|---|
| Payment success rate | successful ÷ attempted, per rail and counterparty bank | Outcome (real-time) | Customer declines (no funds) mixed with technical failures |
| Duplicate rate | duplicate journal entries or payouts ÷ payments | Exactness | Found only if reconciliation looks for duplicates |
| Auto-match rate | items matched without a human ÷ items reconciled | Efficiency | Wide tolerances inflate it |
| Unmatched value | absolute value of open breaks at close | Exactness | Netting positives against negatives hides breaks |
| Break age | open breaks older than N days ÷ open breaks | Operational | Averages hide the oldest breaks |
| Unknown-state age | payments in Unknown longer than 1 hour | Leading | Not measured until a customer complains |
| Safeguarding gap | requirement − resource at reconciliation point | Compliance | A same-day top-up rule in the UK[^cass15] |
| Close timeliness | days signed off before deadline ÷ business days | Compliance | "Signed off" with open items unowned |

::: example Worked calculation for one day (illustrative numbers)
A wallet business attempts 120,000 payments; 117,600 succeed; of the 2,400 failures, 1,500 are customer declines (no funds) and 900
technical. Reconciliation covers 118,000 items; 116,820 match automatically. Twelve duplicates were found. Open breaks: +3,400.00 and
−2,950.00. Customer balances total 4,210,000.00; the safeguarding accounts hold 4,206,500.00.

- Success rate = 117,600 ÷ 120,000 = **98.0%**; technical failure rate = 900 ÷ 120,000 = **0.75%**, the part engineering owns.
- Auto-match rate = 116,820 ÷ 118,000 = **99.0%**: 1,180 items for people to work.
- Duplicate rate = 12 ÷ 120,000 = **0.01%**: 12 customers charged twice; any value above zero is an incident.
- Unmatched value = 3,400.00 + 2,950.00 = **6,350.00**, not the netted 450.00.
- Safeguarding gap = 4,210,000.00 − 4,206,500.00 = **3,500.00** short: top up from own funds today and find the cause.
:::

Review exactness daily, success rates continuously, trends weekly; set targets from your own baseline.

### Quality, security, privacy and compliance

- **Card data:** PCI DSS scope reduction ([playbook](#playbook-reducing-pci-dss-scope)); no card numbers in logs, tickets or analytics.
- **Least privilege on money:** separate roles for initiating, approving and posting manual adjustments and payouts; log every change
  to posting rules and bank details.
- **Incident duties (as of October 2026):** US banks notify their regulator "no later than 36 hours" after determining a notification
  incident occurred, and their service providers must tell affected banks "as soon as possible" about disruptions of four or more
  hours;[^cfr53] the UK FCA's operational resilience rules apply to payment and e-money firms;[^fca-opres] EU DORA applies to financial
  entities from 17 January 2025, including reporting of major ICT incidents.[^dora]
- **Privacy:** ledgers hold personal data for years; retention and access rules come from [PrivacyOps](../privacyops/) and national law.

### Cost, capacity and sustainability

The main costs are processor and scheme fees, people working breaks, and losses (duplicates, unrecovered returns, chargebacks).
Plan capacity for **peaks and cut-offs**: paydays, sale days, month-end, and the fixed windows of batch rails. Illustrative model:
1,180 manual items a day at 4 minutes each is about 79 hours, roughly ten analysts; raising auto-match from 99.0% to 99.5% halves
that. Fixing recurring break classes is the cheaper lever.

### Assurance of the operating model

Monthly second-line review of sampled sign-offs; internal audit of posting-rule changes and manual entries; hourly canary payments
(small real payments on each rail); alerts on missing, duplicated or out-of-sequence files; and a quarterly replay of a historic day
through current posting rules.

## When things go wrong

### Common failures and troubleshooting

| Symptom | Likely cause | Diagnosis | Remediation | Prevention |
|---|---|---|---|---|
| Customer charged twice | Retry without idempotency, or check-then-insert race | Search journals by customer, amount, minute; compare keys | Refund one; post reversal | Unique key with the entry; concurrency test (Lab 3) |
| Payout short | Unbooked fee, chargeback or reserve | Processor adjustment report per payout | Post adjustments | Posting rule per adjustment type |
| Payment stuck "processing" | Timeout treated as unknown with no enquiry | List Unknown older than 1 hour | Status enquiry; customer message | Unknown-outcome policy with escalation |
| Ledger total ≠ bank balance | Missing file, duplicate import, manual edit | Reconcile per day and per account; find first divergent day | Correct with entries; top up if short | File sequence checks; no balance updates |
| Card numbers found in logs | Debug logging of request bodies | Scan logs and tickets | Purge per policy; assess scope | Hosted fields; log redaction tests |
| Success rate falls for one bank only | Counterparty outage or rejected format | Success rate by bank and error code | Route around or pause; inform customers | Per-bank alerts; canary payments |

### Worked runbook: a processor timeout causes duplicate payouts

::: example Illustrative scenario
Friday 16:05. A marketplace's payout job times out calling its processor during a processor incident. The job's retry logic creates
new payout requests with new keys. At 17:30 three sellers report receiving their weekly payout twice.
:::

1. **Stop the bleeding (minute 0–15).** Pause the payout job and its retries; declare an incident; name a lead and a ledger owner.
2. **Bound the damage (15–60).** Compare payouts created since 16:00 with the processor's payout list; classify each as sent once,
   sent twice or unknown.
3. **Resolve the unknowns (1–3 hours).** Query the processor by your reference; resend nothing until every item is known.
4. **Correct the books (same day).** Post duplicates as receivables from the sellers (four-eyes). If a safeguarding account paid
   them, top it up from own funds and record it.[^cass15]
5. **Recover (days 1–5).** Ask sellers to return funds or net them against the next payout under your terms; log every contact.
6. **Prevent (week 1).** Root cause: the retry generated a new key. Fix: one key per payout intent, stored before the first call
   and sent to the processor; a timeout replay test; an alert on two payouts per seller per cycle.

**Exit criteria:** every payout in the window classified and reconciled; books and safeguarding position correct; root-cause fix
tested; postmortem done using the [IncidentOps](../incidentops/) process.

::: figure Break handling: from detection to prevention
```mermaid
flowchart TB
  A["Daily reconciliation"]:::core --> B{"Matched?"}:::decision
  B -->|"yes"| Z["Sign off the day"]:::core
  B -->|"no"| C{"Timing item<br/>before due date?"}:::decision
  C -->|"yes"| W["Track with<br/>expected date"]:::neutral
  W --> A
  C -->|"no"| D["Break record:<br/>owner, reason, age"]:::risk
  D --> E{"Money missing or<br/>customer harmed?"}:::decision
  E -->|"yes"| F["Incident; top up or<br/>refund today"]:::risk
  E -->|"no"| G["Investigate with<br/>counterparty"]:::neutral
  F --> H["Adjusting entry,<br/>four-eyes"]:::core
  G --> H
  H --> I["Root cause and<br/>posting-rule fix"]:::core
  I --> A
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
  classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
  classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
```
Text equivalent: Matched items lead to sign-off; timing items before their due date are tracked. Other items become break records
with owner, reason and age. Missing money or customer harm means an incident with a same-day top-up or refund; otherwise the
counterparty is asked. Both end in a four-eyes adjusting entry and a posting-rule fix.
:::

### Anti-patterns and myths

- **"We can fix the balance with an UPDATE."** It destroys the audit trail; post an entry instead.
- **"Retries are safe; the processor is idempotent."** Only if you send the same key; a new key is a new payment.
- **"Reconciliation is finance's month-end job."** Daily differences become unexplainable monthly ones.

## Deciding and adopting

### When to use and when not to use

Run these practices as a named function when you **hold or move other people's money**: wallets, marketplaces, payroll, lending
disbursement, card issuing, or any product on a partner bank's licence. Signals: more than one rail or processor, pooled customer
accounts, or a regulator or partner bank asking for daily reconciliation.

**Do not** build your own ledger and rail integrations when a provider's hosted product covers your flows with per-payment reports;
operate theirs well. And do not start with tooling when the problem is ownership: a named owner of the daily close comes first.

### Decision framework

::: figure Build a ledger, or rely on your provider's?
```mermaid
flowchart TB
  A{"Do you hold customer<br/>balances or pooled funds?"}:::decision
  A -->|"no: accept<br/>payments only"| B["Use processor<br/>reports"]:::neutral
  A -->|"yes"| C{"Provider ledger with<br/>per-customer records<br/>you and bank can read?"}:::decision
  C -->|"yes"| D["Operate the provider's ledger;<br/>reconcile it daily"]:::core
  C -->|"no"| E{"Team able to run<br/>ledger invariants<br/>and on-call?"}:::decision
  E -->|"no"| F["Change provider or<br/>hire before launch"]:::risk
  E -->|"yes"| G["Own the ledger;<br/>buy rails and matching"]:::core
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
  classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
  classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
```
Text equivalent: Hold no balances: use processor reports. Hold balances and a provider ledger gives per-customer records you and
the bank can read: operate it and reconcile daily. Otherwise own the ledger and buy rails and matching if your team can run ledger
invariants and on-call; if not, change provider or hire before launch.
:::

This is this Atlas's simplification; licensing and safeguarding duties are legal questions for each jurisdiction.

### Maturity model

This is **this Atlas's model**, not an industry standard.

| Level | Observable behaviour | Exit criteria to the next level |
|---|---|---|
| 1. Spreadsheet | Balances in the app database; monthly reconciliation by finance | One ledger with balanced, immutable entries |
| 2. Ledgered | Posting rules; idempotency keys; some daily matching | Every external record reconciled daily with owners and ageing |
| 3. Reconciled | Daily three-way reconciliation; unknown-outcome policy; safeguarding records | Duplicate rate and break age measured and on target for a quarter |
| 4. Engineered | Outbox, concurrency tests, canary payments, per-bank SLIs | Break classes falling; replay of historic days in CI |
| 5. Continuous | Near-real-time matching; automatic status resolution; audit-ready by default | Sustained, evidenced by audits and partner-bank reviews |

### Adoption roadmap: 30/60/90 days

For a new head of payment operations at a growing fintech:

- **Days 1–30: see the money.** Inventory rails, processors, bank accounts and files; draw the [three views](#glossary-and-mental-model)
  for each product; find every place a balance can be edited. *Quick win:* a daily ledger-to-bank balance check with an owner.
- **Days 31–60: make it exact.** Posting rules reviewed with finance; idempotency keys stored with entries; unknown-outcome policy;
  break records with reason codes. *Milestone:* no unowned breaks.
- **Days 61–90: make it provable.** Three-way reconciliation signed off daily; safeguarding records and returns; concurrency and
  timeout tests in CI; per-bank success-rate alerts. *Milestone:* first month with the duplicate rate and break age on target.

## Toolkit

### Checklists

**Launch readiness (new rail, processor or product)**
- [ ] Posting rules for every event, including returns, disputes, fees and reversals, reviewed by finance
- [ ] Idempotency keys end to end, including the processor call; concurrency and timeout tests pass
- [ ] Reconciliation runs from day one; reports give per-payment fees and adjustments
- [ ] Unknown-outcome policy and customer messages approved; card numbers never reach your systems

**Daily close**
- [ ] All expected files received, in sequence, once
- [ ] Three-way reconciliation run; new breaks owned; timing items dated
- [ ] Unknown-outcome payments resolved or escalated; safeguarding gap closed and recorded

**Monthly governance**
- [ ] Manual adjustments reviewed by a second line; posting-rule changes audited
- [ ] Regulatory returns filed; PCI DSS scope re-checked (logs, tickets, page scripts)

### Templates

**Posting-rule change request** (lines ≤ 90 characters):

```text
RULE: CHARGEBACK_V1            OWNER: ledger owner        STATUS: proposed
EVENT: processor adjustment, type=chargeback
ENTRY: Dr wallet:<customer> (or receivable:customer if wallet < amount)
       Cr receivable:psp
INVARIANTS: balanced; one entry per adjustment id (idempotency key = adj_id)
TESTS: replay 2026-11-03 events; duplicate adjustment ignored; empty wallet case
APPROVALS: finance, reconciliation lead      ROLLBACK: disable rule, re-run day
```

**Break reason codes:** T01 timing; A01 amount mismatch; M01 missing in ledger; M02 missing externally; D01 duplicate; J01 unbooked
adjustment; F01 fee difference. "Other" above 5% of breaks means the list is wrong.

### Deep comparisons

Operating models (this Atlas's comparison):

| Model | Strength | Weakness | Fits when |
|---|---|---|---|
| Processor-led | Little to build; fast launch | Limited records; dependence on one provider | Accepting payments only |
| Provider ledger (BaaS) | Bank-grade records if the bank can read them | Middleware failure risk (Synapse) | Early wallets and cards |
| Own ledger, bought rails | Control, exactness, multi-provider | Needs ledger engineering and on-call | Several rails, pooled funds, scale |

### FAQ

**Do we need a ledger if our processor keeps balances?** If you hold customer balances or pooled funds, yes, or a provider ledger
that you and your bank can read; otherwise the processor's reports may be enough ([decision](#decision-framework)).

**Can we retry a payment that timed out?** Only with the same idempotency key and reference; better, ask the rail for its status first.

**Does tokenisation remove PCI DSS duties?** It reduces scope; duties remain for connected systems, payment pages and, if you
operate it, the tokenisation system; if a provider operates it, you manage that provider.[^pci-tok][^pci-scope]

**How often must we reconcile?** It depends on licence and jurisdiction; UK safeguarding firms at least once each reconciliation
day;[^cass15] this Atlas recommends daily for anyone holding customer money.

**Who owns a break caused by the partner bank?** You do, until it is closed: the customer sees your app.

## Going further

### Advanced topics and emerging direction

- **ISO 20022 everywhere (established).** Cross-border payment instructions moved to ISO 20022 in November 2025,[^swift] and FedNow uses
  pacs messages;[^fednow] richer structured data makes automatic matching easier.
- **Ledger databases (active engineering).** Purpose-built stores record each transfer as one debit and one credit, with the transfer
  ID as its idempotency key.[^tb-model]
- **Exceptions as messages (established, FedNow).** A return request "should be sent within 60 calendar days" of settlement, and the
  same messages report fraud (codes FRAD, FR01).[^fednow-ops] Post them as ledger events.

### Learning path

- **Prerequisites:** [API Ops](../api-ops/) (APIs, transactions); [WorkflowOps](../workflowops/) (retries, sagas).
- **Day 1:** glossary; Lab 1. **Week 1:** your processor's settlement report; Labs 2 and 3; your rail's return codes.
- **Month 1:** shadow a daily close; write posting rules for one product; run a timeout game day.
- **Specialise:** ledgers, rail integrations or reconciliation. *Portfolio:* a ledger with replay tests and a synthetic three-way
  reconciliation.

## Wrap-up

### Executive recap and cheat sheet

| Topic | Remember |
|---|---|
| Definition | Operating money movement: ledger, rails, reconciliation, safeguarding |
| Invariants | Balanced immutable entries; one key per intent; unknown is a state |
| Daily | Three-way reconciliation, break ageing, safeguarding position |
| Metrics | Duplicate rate, unmatched value, break age, success rate per bank |
| First moves | Daily ledger-to-bank check; idempotency with entries; unknown-outcome policy |

### A judgement call: launch instant payouts now, or after the ledger work?

::: decision The situation (illustrative)
Sellers want instant payouts. The ledger has no outbox and idempotency is per HTTP attempt. Marketing has a launch date in three weeks.
:::

**Option A: launch on the date.** Revenue sooner, but instant credits are irrevocable,[^fednow] so every duplicate is a loss to
recover from a seller.

**Option B: first add one key per payout intent, an outbox and timeout tests (about four weeks).** A week late; duplicates prevented.

**Assumptions that decide it:** how often timeouts happen; the average payout; whether your terms let you recover from sellers.
**Reasoned choice:** B, or A limited to small payouts with a daily cap.
Uncertainty: if timeouts are truly rare, A's expected loss may be small, so measure first.

### Learning objectives and mastery check

You can now explain the label's status, design posting rules, make money movement idempotent, reconcile in three legs, and choose
controls by rail finality.

::: answer 1. A retry with the same idempotency key arrives with a different amount. What should the API do?
Refuse it as a conflict (the IETF draft suggests 422) and keep the original result; never apply the new amount under the old key.
:::

::: answer 2. Why does Lab 3's naive handler pay twice, and what fixes it?
Both requests read "key not seen" before either writes. A unique constraint on the key, in the same transaction as the entry, makes
the database refuse the second.
:::

::: answer 3. Breaks are +3,400.00 and −2,950.00. What is the unmatched value?
6,350.00. Netting to 450.00 hides two errors.
:::

::: answer 4. A UK e-money firm finds a 3,500.00 safeguarding shortfall at its morning reconciliation. What must happen?
Find the cause and pay the shortfall into the safeguarding account by the end of that day, recording time, actions and outcome
(CASS 15.8.50R, 15.8.9R).
:::

::: answer 5. Your instant payment timed out. The customer taps "send again". What should the system do?
Keep the first payment Unknown, block a new payment for the same intent, and query its status; instant payments are irrevocable.
:::

### Conclusion

"FinTechOps" is not a discipline anyone standardised, but the work behind it is old and exact: double entry, settlement finality and
reconciliation, now run by software teams at API speed. Synapse and TSB show two ways it fails: books that drift from the bank, and
changes that break banking services. Hold the cheat sheet's invariants, and have someone sign the daily reconciliation.

## Sources and further reading

All sources opened 2026-10-23; "archived copy" = Internet Archive copy, read because the live site blocked automated access.

[^rcp]: [FintechOps](https://rcpmag.com/partners/al/f/fintechops.aspx) — Redmond Channel Partner directory, undated. Checked 2026-10-23.
[^fo-site]: [FintechOps: Full-Cycle Financial Software Development Agency](https://fintechops.com/) — FintechOps. Checked 2026-10-23.
[^wise-rec]: [Payment Operations Reconciliation Team Lead](https://wise.jobs/job/payment-operations-reconciliation-team-lead-in-tallinn-jid-2683) — Wise careers, undated (expired). Checked 2026-10-23.
[^wise-spec]: [Payment Operations Reconciliation Senior Specialist](https://builtin.com/job/payment-operations-reconciliation-senior-specialist/7422702) — Built In (Wise), removed 2025-11-07. Checked 2026-10-23.
[^finops-def]: [What is FinOps?](https://www.finops.org/introduction/what-is-finops/) — FinOps Foundation, updated March 2026. Checked 2026-10-23.
[^cfpb-syn]: [Complaint, CFPB v. Synapse Financial Technologies (PDF)](https://files.consumerfinance.gov/f/documents/cfpb_synapse-financial-technologies_complaint_2025-08.pdf) — CFPB, 2025-08-21, paras 2–9 and 50. Checked 2026-10-23.
[^cfpb-syn-page]: [Synapse Financial Technologies, Inc.](https://www.consumerfinance.gov/enforcement/actions/synapse-financial-technologies-inc/) — CFPB enforcement action. Checked 2026-10-23.
[^tsb]: [TSB fined £48.65m for operational resilience failings](https://www.fca.org.uk/news/press-releases/tsb-fined-48m-operational-resilience-failings) — FCA, 2022-12-20. Checked 2026-10-23.
[^cko]: [Transaction lifecycle](https://www.checkout.com/docs/card-issuing/manage-issuing-transactions/transaction-lifecycle) — Checkout.com docs, updated 2026-03-04. Checked 2026-10-23.
[^pfmi]: [Principles for financial market infrastructures (PDF)](https://www.bis.org/publications/principles-financial-market-infrastructures.pdf) — CPSS-IOSCO, April 2012; Principles 8 and 17, glossary. Checked 2026-10-23.
[^tb-model]: [Data Modeling](https://docs.tigerbeetle.com/coding/data-modeling/) — TigerBeetle docs. Checked 2026-10-23.
[^tb-2p]: [Two-Phase Transfers](https://docs.tigerbeetle.com/coding/two-phase-transfers/) — TigerBeetle docs. Checked 2026-10-23.
[^tb-submit]: [Reliable Transaction Submission](https://docs.tigerbeetle.com/coding/reliable-transaction-submission/) — TigerBeetle docs. Checked 2026-10-23.
[^nacha-how]: [How ACH Payments Work](https://www.nacha.org/content/how-ach-payments-work) — Nacha. Checked 2026-10-23.
[^nacha-r11]: [Differentiating Unauthorized Return Reasons](https://www.nacha.org/rules/differentiating-unauthorized-return-reasons) — Nacha, effective 2020–2021. Checked 2026-10-23.
[^fednow]: [The FedNow Service Technical Overview and Planning Guide (PDF)](https://explore.fednow.org/resources/technical-overview-guide.pdf) — Federal Reserve Banks, 2025-07-17. Checked 2026-10-23.
[^fednow-ops]: [FedNow Service Operating Procedures (PDF)](https://www.frbservices.org/wp-content/uploads/101326-fednow-service-operating-procedures.pdf) — Federal Reserve Banks, October 2026 (v3.7), §15.2. Checked 2026-10-23.
[^swift]: [ISO 20022: MT to ISO 20022 Conversion](https://www.swift.com/standards/iso-20022/iso-20022-faqs/mt-iso-20022-conversion) — Swift (archived copy, 2026-06-14). Checked 2026-10-23.
[^npci-upi]: [Unified Payments Interface: product overview](https://www.npci.org.in/what-we-do/upi/product-overview) — NPCI (archived copy, 2025-03-01). Checked 2026-10-23.
[^npci-settle]: [UPI Settlement Process (PDF)](https://www.npci.org.in/PDF/npci/others/UPI-Settlement-Process.pdf) — NPCI (archived copy, 2025-02-07). Checked 2026-10-23.
[^npci-oc65]: [UPI OC 65: Change in cutover cycles in UPI (PDF)](https://www.npci.org.in/PDF/npci/upi/circular/2019/UPI%20OC%2065.pdf) — NPCI, 2019-01-24 (archived copy). Checked 2026-10-23.
[^bny-iso]: [Demystifying ISO 20022, Module 1 (PDF)](https://www.bny.com/content/dam/bnymellon/documents/pdf/iso-20022/presentation-module-1.pdf) — BNY, September 2020. Checked 2026-10-23.
[^npci-uptime]: [Uptime for UPI](https://www.npci.org.in/what-we-do/upi/uptime-upi-month-wise) — NPCI (archived copy, 2025-09-07). Checked 2026-10-23.
[^rbi-tat]: [Harmonisation of Turn Around Time and customer compensation for failed transactions](https://www.rbi.org.in/Scripts/NotificationUser.aspx?Id=11693) — Reserve Bank of India, 2019-09-20. Checked 2026-10-23.
[^ecb-ipr]: [Instant Payments Regulation](https://www.ecb.europa.eu/paym/retail/instant_payments/html/instant_payments_regulation.en.html) — European Central Bank. Checked 2026-10-23.
[^mt-r10]: [R10 return code](https://moderntreasury.com/ach-return-codes/r10) — Modern Treasury (vendor reference; Nacha's rulebook text not opened). Checked 2026-10-23.
[^stripe-hold]: [Place a hold on a payment method](https://docs.stripe.com/payments/place-a-hold-on-a-payment-method) — Stripe docs. Checked 2026-10-23.
[^stripe-idem]: [Idempotent requests](https://docs.stripe.com/api/idempotent_requests) — Stripe API reference. Checked 2026-10-23.
[^ietf-idem]: [The Idempotency-Key HTTP Header Field, draft 08](https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/) — IETF HTTPAPI WG, 2026-10-22 (Internet-Draft). Checked 2026-10-23.
[^aws-idem]: [Making retries safe with idempotent APIs](https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/) — Malcolm Featonby, Amazon Builders' Library. Checked 2026-10-23.
[^ps2512]: [PS25/12: Changes to the safeguarding regime for payments and e-money firms](https://www.fca.org.uk/publications/policy-statements/ps25-12-changes-safeguarding-regime-payments-and-e-money-firms) — FCA, 2025-08-07. Checked 2026-10-23.
[^cass15]: [CASS 15 Payment services and electronic money: relevant funds](https://handbook.fca.org.uk/handbook/CASS/15/) — FCA Handbook, from 2026-05-07; CASS 15.8. Checked 2026-10-23.
[^sup1614a]: [SUP 16.14A Safeguarding returns](https://handbook.fca.org.uk/handbook/SUP/16/14A.html) — FCA Handbook, from 2026-05-07. Checked 2026-10-23.
[^baas]: [Joint Statement on Banks' Arrangements with Third Parties to Deliver Bank Deposit Products and Services (PDF)](https://www.federalreserve.gov/newsevents/pressreleases/files/bcreg20240725c1.pdf) — Federal Reserve, FDIC and OCC, 2024-07-25. Checked 2026-10-23.
[^fdic-cust]: [Recordkeeping for Custodial Accounts (proposed rule)](https://www.federalregister.gov/documents/2024/10/02/2024-22565/recordkeeping-for-custodial-accounts) — FDIC, Federal Register, 2024-10-02. Checked 2026-10-23.
[^tprm26]: [Proposed Third-Party Risk Management Guidance](https://www.federalregister.gov/documents/2026/09/15/2026-18859/proposed-third-party-risk-management-guidance) — OCC, Fed, FDIC, NCUA, 2026-09-15. Checked 2026-10-23.
[^pci-401]: [Just Published: PCI DSS v4.0.1](https://blog.pcisecuritystandards.org/just-published-pci-dss-v4-0-1) — PCI SSC, 2024-06-11. Checked 2026-10-23.
[^pci-saqa]: [Important Updates Announced for Merchants Validating to SAQ A](https://blog.pcisecuritystandards.org/important-updates-announced-for-merchants-validating-to-self-assessment-questionnaire-a) — PCI SSC, January 2025. Checked 2026-10-23.
[^pci-tok]: [PCI DSS Tokenization Guidelines (PDF)](https://listings.pcisecuritystandards.org/documents/Tokenization_Guidelines_Info_Supplement.pdf) — PCI SSC, August 2011. Checked 2026-10-23.
[^pci-sp]: [Glossary: Service Provider](https://www.pcisecuritystandards.org/glossary/service-provider/) — PCI SSC. Checked 2026-10-23.
[^pci-scope]: [Guidance for PCI DSS Scoping and Network Segmentation (PDF)](https://listings.pcisecuritystandards.org/documents/Guidance-PCI-DSS-Scoping-and-Segmentation_v1.pdf) — PCI SSC, December 2016. Checked 2026-10-23.
[^fapi2]: [FAPI 2.0 Security Profile](https://openid.net/specs/fapi-security-profile-2_0-final.html) — OpenID Foundation, 2025-02-22. Checked 2026-10-23.
[^ob-avail]: [Availability and performance (v4.0.1)](https://standards.openbanking.org.uk/operational-guidelines/availability-and-performance/latest/) — Open Banking Limited, 2026-03-18. Checked 2026-10-23.
[^ep-psr]: [Revision of EU rules on payment services](https://www.europarl.europa.eu/legislative-train/theme-an-economy-that-works-for-people/file-revision-of-eu-rules-on-payment-services) — European Parliament Legislative Train, updated 2026-09-20. Checked 2026-10-23.
[^cfpb-1033]: [Personal Financial Data Rights](https://www.consumerfinance.gov/compliance/compliance-resources/other-applicable-requirements/personal-financial-data-rights/) — CFPB, modified 2026-01-06. Checked 2026-10-23.
[^cfr53]: [12 CFR Part 53, sections 53.3 and 53.4](https://www.ecfr.gov/current/title-12/chapter-I/part-53) — eCFR (OCC rule), text as of 2026-10-01. Checked 2026-10-23.
[^fca-opres]: [Operational resilience](https://www.fca.org.uk/firms/operational-resilience) — FCA. Checked 2026-10-23.
[^dora]: [Digital Operational Resilience Act (DORA)](https://www.eiopa.europa.eu/digital-operational-resilience-act-dora_en) — EIOPA. Checked 2026-10-23.
[^gh-tb]: [tigerbeetle/tigerbeetle](https://github.com/tigerbeetle/tigerbeetle) — GitHub (Apache-2.0). Checked 2026-10-23.
[^gh-blnk]: [blnkfinance/blnk](https://github.com/blnkfinance/blnk) — GitHub (Apache-2.0). Checked 2026-10-23.
[^gh-fineract]: [apache/fineract](https://github.com/apache/fineract) — GitHub (Apache-2.0). Checked 2026-10-23.
[^gh-moovach]: [moov-io/ach](https://github.com/moov-io/ach) — GitHub (Apache-2.0). Checked 2026-10-23.

**Changelog**
- 2026-10-23, r0: first draft (research, evidence ledger, three executed labs, six diagrams). Not yet independently fact-checked.
- 2026-10-23, r1: after independent fact check: UPI shown as net-settled; state machine allows late returns after reconciliation;
  VoP, CASS scope, late presentment, PCI service providers, key pruning, ISO 20022 families corrected or added; Lab 1 re-run.
- 2026-10-23, r2: FedNow returns sourced; FCA operational-resilience dates; TigerBeetle wording; one advanced topic;
  repeats trimmed.
- 2026-10-23, post-score polish: FedNow return routes; second R10 source; Stripe windows by initiator; early-return edge
  in the state machine; repeats trimmed.
