The Ops Atlas xOps School

Industry & Organisation Ambiguous / not standardised Partly verified

FinTechOps

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.

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.

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.

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); cloud cost (FinOps); API lifecycle (API Ops); audit evidence (ComplianceOps)
Prerequisites HTTP APIs and databases; basic bookkeeping helps (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

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”.[1][2]
  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”[3] and specialists who reconcile bank accounts, payments and transactions and escalate “reconciliation discrepancies in a timely manner”.[4]

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;[5] that is cloud and technology spend, taught in FinOps.

  • Not financial crime work. Screening, monitoring and suspicious-activity reports belong to 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.

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”.[6] A stipulated judgment was entered on 12 September 2025.[7]
  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.[8] 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.

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”[9] 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”[10] 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[11] 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.

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). We found no documented origin of the label.

Figure 1. FinTechOps and its neighbours in the money chain
payment APIsreliabilityinstructions, filespayments, eventsholds, returnsjournalsevidenceAPI OpsAPI contracts and versionsFinTechOpsmoney moves once,books reconcileFinCrimeOpsscreening, monitoring,reportsComplianceOpscontrol evidenceSRESLOs and incidentsFinancegeneral ledger, closeBanks, processors,schemes
Diagram source (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 Payment data, holds Correct movement, returns, reconciliation Screening, monitoring, suspicious-activity reports
FinOps The word “Fin” Customers’ money Cloud and technology spend
API Ops Payment APIs Idempotency semantics, money states API design, versioning, gateways
WorkflowOps Retries, sagas Ledger and rail rules Durable workflow engines (idempotency)
IntegrationOps Bank files and messages What a file means for balances Transport, mapping, queues
ComplianceOps PCI DSS, audits Scope design and operating the controls Control library and audit evidence
SRE Availability Payment-specific SLIs (exactness, success rate) SLO method, error budgets, postmortems
RetailOps Card acceptance Payment platforms and settlement Tills, terminals, store procedures
RiskOps, DecisionOps Limits, holds Applying them to the right payment Risk method (RiskOps); automated decision logic (DecisionOps)

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

Link to section “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[9] 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[12] Settles in batches; consumer debits stay returnable Returns; consumer unauthorised or error returns (R10, R11) within 60 days[13][14]
Instant, gross settlement (FedNow) Single message, cleared and settled between banks in seconds; a 24x7x365 real-time gross settlement service[15] Within seconds; payments “are irrevocable”[15] A payment return (pacs.004), after a return request (camt.056) or when the receiver cannot apply the funds; not a reversal[16]
Instant, net settlement (UPI) Customer to customer “round the clock”;[17] NPCI nets each bank’s position and settles it through banks’ RBI accounts in cycles (four a day in 2019)[18][19] Customer credit in seconds; interbank funds at the cycle’s settlement A new payment back; failed credits reversed by RBI deadlines[20]
Cross-border (Swift, correspondent banks) ISO 20022 pacs messages through correspondents; MT payment instructions no longer supported FI-to-FI since 22 November 2025[21] 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.[22] 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.[23]

ISO 20022 in one table. The message prefix says who talks to whom, which tells reconciliation which file to read:[24]

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
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)
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

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.
  • Every business day: the daily close: 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);[25] 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.

Reference architecture: from API call to reconciled books

Link to section “Reference architecture: from API call to reconciled books”
Figure 2. Reference architecture of a money-movement platform
journal entriesinstructionsstatus updatesadjusting entriesClient appswith idempotency keysPayments APIvalidation, authPayment statemachineDouble-entryledgerOutbox andrail adaptersProcessors, banks,schemesReports, statements,webhooksReconciliationengineBreak queueowners, ageingSafeguarding, financeand regulatory reports
Diagram source (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[26] 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

Link to section “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 3. Payment states, including the unknown outcome
validated, ledger holdoutbox sendsrail repliestimeout or no replyenquiry or rail reportconfirmedrejectedhold releasedsettlement fileearly returnmatchedlate return or disputereturn windows closedreversal posted, matchedCreatedPendingSubmittedUnknownSucceededFailedSettledReturnedReconciledFinal
Diagram source (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).[20]
  • 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.[9]
Figure 4. A card payment from authorisation to payout
IssuerSchemeProcessorMerchantAuthorise 50.00Auth requestAuth requestApproved, holdApprovedApprovedCapture 48.00Clearing recordPresentmentSettle fundsSettle fundsNet payout and report
Diagram source (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.

The charter says what must stay true; these playbooks cover the work that most often goes wrong.

  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.[27] 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.[28] 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).[29] 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.[30]
  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,[29] 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.[29]

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

Link to section “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.[26]
  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

Link to section “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),[15] 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.[18]
  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.[31]

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

Link to section “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.[31][32] A monthly return goes to the FCA.[25]
  • 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.[33] 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.[34] New interagency third-party risk guidance is open for comment until 16 November 2026.[35]

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

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).[36][37]

  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.[38] 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.[39]
  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.[40]
  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”.[37]
  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.

  • 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.[41]
  • 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.[42] 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.[43] 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.[44]
Source Status (October 2026) What FinTechOps takes from it
Rail and scheme rules (Nacha Operating Rules, card scheme rules, Swift/ISO 20022 usage guidelines)[13][21] Binding by contract on participants Messages, cut-offs, returns, disputes
PFMI (CPSS-IOSCO, 2012)[10] 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[36] Contractual standard via card brands and acquirers Card data protection, scoping, payment-page security
UK CASS 15, SUP 16.14A[31][25] UK rules for payment and e-money firms, from 7 May 2026 Daily safeguarding reconciliation, records, monthly return
EU Instant Payments Regulation (2024/886)[22] EU law, phased 2025–2028 Instant sending, verification of payee
RBI failed-transaction TAT framework (2019)[20] Indian regulatory circular Reversal deadlines and compensation
FAPI 2.0 Security Profile[41] OpenID Foundation final specification Open-banking API security
Operational resilience: UK FCA (in force 31 March 2022; transition ended 31 March 2025),[45] EU DORA (from 17 January 2025),[46] US incident notification (36 hours)[47] Law and rules, by jurisdiction Important services, impact tolerances, incident reporting
IETF Idempotency-Key draft[30] Internet-Draft, not a standard Common semantics for retries

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),[48] Blnk (Apache-2.0),[49] 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)[50] 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)[51] 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) Per-rail and per-bank views Averages that hide one failing bank

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.

Reference implementation: operating artefacts

Link to section “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.

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).

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.

{
  "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 and the daily checklist complete the set.

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).

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

Link to section “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).

"""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}")
  1. Run it, then run it with the own-funds top-up.

Example status: verified by execution on 2026-10-23.

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):

(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
  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.[31]

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

Link to section “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).

"""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}")
  1. Run it: python3 recon.py.

Expected output:

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

Link to section “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).

"""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)
  1. Run it: python3 race.py.

Expected output:

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.[28]

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).

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.[6] 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.[33] 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’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;[22] pre-send fraud checks with 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.

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.[52] 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[31]
Close timeliness days signed off before deadline ÷ business days Compliance “Signed off” with open items unowned

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

  • Card data: PCI DSS scope reduction (playbook); 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;[47] the UK FCA’s operational resilience rules apply to payment and e-money firms;[45] EU DORA applies to financial entities from 17 January 2025, including reporting of major ICT incidents.[46]
  • Privacy: ledgers hold personal data for years; retention and access rules come from PrivacyOps and national law.

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.

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.

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

Link to section “Worked runbook: a processor timeout causes duplicate payouts”
  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.[31]
  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 process.

Figure 5. Break handling: from detection to prevention
yesnoyesnoyesnoDaily reconciliationMatched?Sign off the dayTiming itembefore due date?Track withexpected dateBreak record:owner, reason, ageMoney missing orcustomer harmed?Incident; top up orrefund todayInvestigate withcounterpartyAdjusting entry,four-eyesRoot cause andposting-rule fix
Diagram source (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.

  • “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.

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.

Figure 6. Build a ledger, or rely on your provider’s?
no: acceptpayments onlyyesyesnonoyesDo you hold customerbalances or pooled funds?Use processorreportsProvider ledger withper-customer recordsyou and bank can read?Operate the provider's ledger;reconcile it dailyTeam able to runledger invariantsand on-call?Change provider orhire before launchOwn the ledger;buy rails and matching
Diagram source (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.

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

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 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.

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)

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

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.

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

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).

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.[38][40]

How often must we reconcile? It depends on licence and jurisdiction; UK safeguarding firms at least once each reconciliation day;[31] 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.

  • ISO 20022 everywhere (established). Cross-border payment instructions moved to ISO 20022 in November 2025,[21] and FedNow uses pacs messages;[15] 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.[11]
  • 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).[16] Post them as ledger events.
  • Prerequisites: API Ops (APIs, transactions); 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.
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?

Link to section “A judgement call: launch instant payouts now, or after the ledger work?”

Option A: launch on the date. Revenue sooner, but instant credits are irrevocable,[15] 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.

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.

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.

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.

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.

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).

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.

“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.

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

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.

  1. FintechOps — Redmond Channel Partner directory, undated. Checked 2026-10-23. ↩︎

  2. FintechOps: Full-Cycle Financial Software Development Agency — FintechOps. Checked 2026-10-23. ↩︎

  3. Payment Operations Reconciliation Team Lead — Wise careers, undated (expired). Checked 2026-10-23. ↩︎

  4. Payment Operations Reconciliation Senior Specialist — Built In (Wise), removed 2025-11-07. Checked 2026-10-23. ↩︎

  5. What is FinOps? — FinOps Foundation, updated March 2026. Checked 2026-10-23. ↩︎

  6. Complaint, CFPB v. Synapse Financial Technologies (PDF) — CFPB, 2025-08-21, paras 2–9 and 50. Checked 2026-10-23. ↩︎ ↩︎

  7. Synapse Financial Technologies, Inc. — CFPB enforcement action. Checked 2026-10-23. ↩︎

  8. TSB fined £48.65m for operational resilience failings — FCA, 2022-12-20. Checked 2026-10-23. ↩︎

  9. Transaction lifecycle — Checkout.com docs, updated 2026-03-04. Checked 2026-10-23. ↩︎ ↩︎ ↩︎

  10. Principles for financial market infrastructures (PDF) — CPSS-IOSCO, April 2012; Principles 8 and 17, glossary. Checked 2026-10-23. ↩︎ ↩︎

  11. Data Modeling — TigerBeetle docs. Checked 2026-10-23. ↩︎ ↩︎

  12. How ACH Payments Work — Nacha. Checked 2026-10-23. ↩︎

  13. Differentiating Unauthorized Return Reasons — Nacha, effective 2020–2021. Checked 2026-10-23. ↩︎ ↩︎

  14. R10 return code — Modern Treasury (vendor reference; Nacha’s rulebook text not opened). Checked 2026-10-23. ↩︎

  15. The FedNow Service Technical Overview and Planning Guide (PDF) — Federal Reserve Banks, 2025-07-17. Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  16. FedNow Service Operating Procedures (PDF) — Federal Reserve Banks, October 2026 (v3.7), §15.2. Checked 2026-10-23. ↩︎ ↩︎

  17. Unified Payments Interface: product overview — NPCI (archived copy, 2025-03-01). Checked 2026-10-23. ↩︎

  18. UPI Settlement Process (PDF) — NPCI (archived copy, 2025-02-07). Checked 2026-10-23. ↩︎ ↩︎

  19. UPI OC 65: Change in cutover cycles in UPI (PDF) — NPCI, 2019-01-24 (archived copy). Checked 2026-10-23. ↩︎

  20. Harmonisation of Turn Around Time and customer compensation for failed transactions — Reserve Bank of India, 2019-09-20. Checked 2026-10-23. ↩︎ ↩︎ ↩︎

  21. ISO 20022: MT to ISO 20022 Conversion — Swift (archived copy, 2026-06-14). Checked 2026-10-23. ↩︎ ↩︎ ↩︎

  22. Instant Payments Regulation — European Central Bank. Checked 2026-10-23. ↩︎ ↩︎ ↩︎

  23. Place a hold on a payment method — Stripe docs. Checked 2026-10-23. ↩︎

  24. Demystifying ISO 20022, Module 1 (PDF) — BNY, September 2020. Checked 2026-10-23. ↩︎

  25. SUP 16.14A Safeguarding returns — FCA Handbook, from 2026-05-07. Checked 2026-10-23. ↩︎ ↩︎ ↩︎

  26. Two-Phase Transfers — TigerBeetle docs. Checked 2026-10-23. ↩︎ ↩︎

  27. Reliable Transaction Submission — TigerBeetle docs. Checked 2026-10-23. ↩︎

  28. Making retries safe with idempotent APIs — Malcolm Featonby, Amazon Builders’ Library. Checked 2026-10-23. ↩︎ ↩︎

  29. Idempotent requests — Stripe API reference. Checked 2026-10-23. ↩︎ ↩︎ ↩︎

  30. The Idempotency-Key HTTP Header Field, draft 08 — IETF HTTPAPI WG, 2026-10-22 (Internet-Draft). Checked 2026-10-23. ↩︎ ↩︎

  31. CASS 15 Payment services and electronic money: relevant funds — FCA Handbook, from 2026-05-07; CASS 15.8. Checked 2026-10-23. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  32. PS25/12: Changes to the safeguarding regime for payments and e-money firms — FCA, 2025-08-07. Checked 2026-10-23. ↩︎

  33. Joint Statement on Banks’ Arrangements with Third Parties to Deliver Bank Deposit Products and Services (PDF) — Federal Reserve, FDIC and OCC, 2024-07-25. Checked 2026-10-23. ↩︎ ↩︎

  34. Recordkeeping for Custodial Accounts (proposed rule) — FDIC, Federal Register, 2024-10-02. Checked 2026-10-23. ↩︎

  35. Proposed Third-Party Risk Management Guidance — OCC, Fed, FDIC, NCUA, 2026-09-15. Checked 2026-10-23. ↩︎

  36. Just Published: PCI DSS v4.0.1 — PCI SSC, 2024-06-11. Checked 2026-10-23. ↩︎ ↩︎

  37. Important Updates Announced for Merchants Validating to SAQ A — PCI SSC, January 2025. Checked 2026-10-23. ↩︎ ↩︎

  38. PCI DSS Tokenization Guidelines (PDF) — PCI SSC, August 2011. Checked 2026-10-23. ↩︎ ↩︎

  39. Glossary: Service Provider — PCI SSC. Checked 2026-10-23. ↩︎

  40. Guidance for PCI DSS Scoping and Network Segmentation (PDF) — PCI SSC, December 2016. Checked 2026-10-23. ↩︎ ↩︎

  41. FAPI 2.0 Security Profile — OpenID Foundation, 2025-02-22. Checked 2026-10-23. ↩︎ ↩︎

  42. Availability and performance (v4.0.1) — Open Banking Limited, 2026-03-18. Checked 2026-10-23. ↩︎

  43. Revision of EU rules on payment services — European Parliament Legislative Train, updated 2026-09-20. Checked 2026-10-23. ↩︎

  44. Personal Financial Data Rights — CFPB, modified 2026-01-06. Checked 2026-10-23. ↩︎

  45. Operational resilience — FCA. Checked 2026-10-23. ↩︎ ↩︎

  46. Digital Operational Resilience Act (DORA) — EIOPA. Checked 2026-10-23. ↩︎ ↩︎

  47. 12 CFR Part 53, sections 53.3 and 53.4 — eCFR (OCC rule), text as of 2026-10-01. Checked 2026-10-23. ↩︎ ↩︎

  48. tigerbeetle/tigerbeetle — GitHub (Apache-2.0). Checked 2026-10-23. ↩︎

  49. blnkfinance/blnk — GitHub (Apache-2.0). Checked 2026-10-23. ↩︎

  50. apache/fineract — GitHub (Apache-2.0). Checked 2026-10-23. ↩︎

  51. moov-io/ach — GitHub (Apache-2.0). Checked 2026-10-23. ↩︎

  52. Uptime for UPI — NPCI (archived copy, 2025-09-07). Checked 2026-10-23. ↩︎

Related Ops

FinCrimeOps

FinCrimeOps (financial crime operations) is the team and workflow that turn anti-money-laundering, sanctions and fraud obligations into daily decisions: check customers, screen names, work alerts, investigate, report suspicion to the authorities on time, and prove every decision later.

FinOps

FinOps is the operating model for variable technology spend: engineering, finance, product, procurement and leadership share timely cost data and clear decision rights, so that cloud, SaaS, data-platform and AI spend buys measurable business value.

API Ops

API Ops, usually written APIOps, applies GitOps and DevOps practice to APIs: contracts and gateway configuration live in Git, and a pipeline lints, diffs and tests each change before promoting it to the gateways.

IntegrationOps

IntegrationOps is the work of running system-to-system integrations as owned production flows: messages that may arrive twice, late or broken are deduplicated, parked, replayed and reconciled so that no business record is silently lost.

WorkflowOps

WorkflowOps is not a standard term. This Atlas uses it for running workflow engines well: keeping long-running, multi-step processes (DAGs, state machines, durable executions) correct through retries, crashes, partial failure and code changes.

ComplianceOps

ComplianceOps runs regulatory and contractual compliance as a continuous operation: one library of controls mapped to every framework in scope, evidence collected and tested on a schedule, exceptions with expiry dates, and audits served from records that already exist.

RiskOps

RiskOps, as this Atlas uses it, is the day-to-day operation of risk decisions: appetite and tolerances, a live risk register, quantification where it pays, and acceptance, treatment or transfer by the owner with the authority, with an expiry. The label has no standard meaning.

SRE (Site Reliability Engineering)

Site Reliability Engineering (SRE) runs production services as a software-engineering problem: it sets explicit reliability targets (SLOs) from users' experience and uses the remaining error budget to decide how fast to ship and when to stop and fix.

Practise and get certified

From xOps School

Practise FinTechOps and get certified

The FinTechOps track on xOps School (part of the Coffee Pass) has study notes, a 25-question practice quiz with explanations, and a verifiable certificate when you score 70% or more. Sign in with Google to start.

For teams

Instructor-led training

Live batches for your team, with completion certificates and reports for L&D/HR.

Source: master.md (the editorial source of this page) · built 2026-10-23 · diagrams rendered with @mermaid-js/mermaid-cli 11.17.0 (mermaid 11.17.2)