Orchestrating treasury without collapsing ledger, reserve, transfer, and settlement
Treasury workflows remain explainable when accounting truth, available funds, movement execution, and external settlement keep separate records while one orchestrator coordinates their evidence.
Contents
Treasury is not one balance
A treasury platform can show a single number called “balance” and still be unable to answer a basic operational question: what can move right now, what has already been accounted for, what is in flight, and what has actually settled outside the system?
Those are different facts. Collapsing them into one mutable field makes the happy path short, but every delayed bank response, duplicated message, rejected transfer, or reconciliation difference turns into an argument over which value is true.
Treasury orchestration should coordinate financial responsibilities without allowing the coordinator to become the source of every kind of truth.
The useful boundary is not “all money logic belongs in Treasury.” It is more precise: the ledger records accounting truth, reserves control availability, transfers manage movement, settlement confirms the external outcome, and an orchestrator advances the workflow using evidence from each boundary.
Four meanings hide behind one word
When a product says “balance,” it often mixes at least four values.
Ledger balance
The ledger explains how value changed. It is derived from postings or journal entries and must preserve accounting invariants. It should answer why a position exists, not merely return the latest cached total.
Available amount
Availability is a policy result. A ledger may show funds that cannot be used because they are reserved for an approved transfer, exposed to a pending charge, held for risk review, or subject to a product limit.
Transfer position
A transfer records an intended movement and its execution lifecycle. Accepted, queued, submitted, acknowledged, failed, and reversed are workflow states. They are not interchangeable with ledger postings or external settlement.
Settled position
Settlement is the amount confirmed by an authoritative external source such as a bank statement, provider report, or network ledger. It may lag behind internal acceptance and execution.
These values are related, but equality between them is a temporary condition, not a permanent invariant. The architecture should make their differences explainable.
The orchestrator coordinates; domains decide
A treasury orchestrator is useful when one business operation crosses several boundaries. Consider a transfer that requires a limit check, a reserve, an FX quote, a provider submission, ledger postings, and later reconciliation.
The orchestrator should own:
- the operation identifier and requested outcome;
- the ordered workflow and current step;
- retry, timeout, and compensation policy;
- the evidence received from each participant;
- the final operational disposition: completed, failed, or requiring review.
It should not calculate accounting truth inside a workflow branch or modify a balance directly. Each participant owns a narrower contract:
ledger.post(journal) -> posting reference
reserve.hold(account, amount, operation) -> reservation reference
fx.lock(quote) -> executable rate reference
transfer.submit(instruction) -> provider reference
settlement.observe(reference) -> authoritative observation
This distinction keeps the coordinator replaceable. It also prevents a workflow retry from repeating a financial effect merely because the process did not record the previous response.
Make the ledger append-only in meaning
The ledger needs an immutable history even if the physical implementation supports corrections. A mistake is corrected with another posting that explains the reversal or adjustment; history is not silently rewritten.
Each journal should have:
- a stable business operation identifier;
- balanced debit and credit entries under the chosen accounting model;
- currency or asset dimensions that cannot be mixed accidentally;
- an effective time and a recorded time;
- a reference to the initiating workflow and source evidence;
- a uniqueness rule that makes replay safe.
Database transactions protect local invariants. PostgreSQL documents that Serializable isolation makes committed concurrent transactions equivalent to some serial order, while also requiring applications to retry serialization failures. That guarantee is useful for contested reservations or postings, but it does not extend across a message broker, bank API, or blockchain node. The database boundary must remain explicit.
A stored balance can be a performance projection, but it should be rebuildable from the journal or continuously checked against it. If a projection differs, the system should raise a discrepancy instead of silently editing history to fit the cached number.
Treat reserves as claims on availability
A reserve is not another ledger. It answers whether a requested amount may be committed to an operation without allowing concurrent work to spend the same capacity.
A useful reservation lifecycle is small:
requested -> active -> consumed
-> released
-> expired
Creation should be atomic with the availability check. Consumption should point to the resulting transfer or posting. Release and expiry need their own idempotent operation identifiers because retries can arrive after a timeout.
This separation matters during partial failure. If provider submission fails before any external effect, the reservation can be released according to policy. If the submission result is unknown, immediate release may enable a second transfer while the first is still possible. The correct state is not “failed”; it is “outcome unknown,” with availability still controlled until observation or manual review resolves it.
Give transfers a lifecycle, not a boolean
success: true is not an adequate transfer model. A transfer can be accepted internally, waiting for execution, submitted externally, acknowledged provisionally, settled, rejected, cancelled, reversed, or uncertain.
The transfer record should preserve three identities:
- the client or upstream idempotency key;
- the internal operation and transfer identifiers;
- the provider, bank, or chain reference.
They solve different replay and investigation problems. Reusing the provider reference as the internal identifier fails before submission; treating the internal identifier as proof of settlement fails after submission.
Retries should depend on evidence. A pre-submission validation failure may be safely retried after correction. A timeout after sending an instruction requires status lookup before another submission. A terminal business rejection may need a new intent rather than reuse of the old operation.
Publish state changes without a dual write
Treasury workflows often persist a state change and notify another service. Writing the database and publishing to Kafka as two unrelated operations creates a dual-write gap: one can succeed while the other fails.
AWS Prescriptive Guidance describes the transactional outbox pattern as a way to store the domain change and an event record in one database transaction, then relay committed events separately. It also warns that duplicate delivery remains possible and consumers must be idempotent.
That leads to a practical rule:
- local financial state and its outbox event commit together;
- relay delivery has a stable event identifier and observable attempts;
- consumers deduplicate by event identity;
- consuming an event does not imply the external financial effect has occurred;
- replay targets the existing operation rather than creating a new one.
Kafka transactions can provide exactly-once processing within a configured Kafka consume-transform-produce flow. They do not make a PostgreSQL update, a bank transfer, and a webhook one atomic transaction. The system still needs domain identities, outbox boundaries, and reconciliation.
Settlement is observation plus reconciliation
Settlement should not be inferred from the fact that a worker finished. It comes from authoritative external evidence.
Reconciliation compares at least:
- the internal transfer and expected amount;
- the external reference and observed amount;
- the relevant ledger postings;
- the reservation consumption or release;
- timing, fees, FX differences, and reversal status.
The result should be explicit: matched, timing difference, amount difference, missing internally, missing externally, duplicate, or requiring review. A scheduled job can identify the difference, but only a controlled workflow should decide whether to post an adjustment, retry a query, or escalate to an operator.
This is also where orchestration differs from pretending to have a distributed transaction. AWS's saga guidance uses a coordinator for multi-step work and calls out continuation and compensation. In treasury systems, compensation is often a new financial action, not deletion of the previous one. A reversal needs its own authorization, postings, external reference, and evidence.
Design the operator view around unresolved truth
An operations screen should not only list successful transfers. It should make uncertainty searchable.
For one operation, an operator should be able to see:
- the original request and authorization context;
- every reservation and ledger reference;
- outbox and consumer attempts;
- the provider submission and observations;
- reconciliation findings;
- manual actions, actor, reason, and resulting evidence.
The most important queues are often “outcome unknown,” “settlement overdue,” “reservation not released,” and “ledger/external mismatch.” Those states prevent automated retries from manufacturing a second effect just to make the dashboard green.
The reusable architecture
The public BEATOZ Treasury project record describes a NestJS and PostgreSQL operations platform with ledger, reserve, FX, transfer, monitoring, Kafka ingress/outbox, reconciliation, replay, and operator tooling. The reusable lesson is not that one implementation proves a universal treasury model. It is that the system becomes operable when each financial responsibility can explain its own truth and the orchestrator can show how those truths moved one operation forward.
Before calling a treasury workflow reliable, verify:
- ledger, availability, transfer, and settlement are separate records;
- every effect has a stable operation identity;
- local invariants commit in one database transaction;
- cross-boundary events use an outbox or equivalent durable handoff;
- retries consult evidence before repeating an external effect;
- reconciliation produces discrepancies instead of silent corrections;
- operators can resolve uncertainty without erasing history.
Treasury orchestration is therefore less about connecting many services than preserving the meaning of each boundary while one financial operation crosses them.
Revision note
Initial public edition based on official PostgreSQL, AWS, and Apache Kafka documentation plus the verified BEATOZ Treasury portfolio record.