Quick Answer
Define tenant access separately from legal-entity and book ownership. Enforce balanced native-currency journals and atomic admission under concurrency. Persist logical operation identity, append corrections and recover unknown external results. Reconcile cash, obligations and provider evidence; a balanced internal journal alone does not prove a payout completed.
Key Takeaways
- Define book ownership independently of tenant authorization.
- Balance all journal legs by native currency and commit them atomically.
- Reserve against authoritative balances under concurrency.
- Distinguish external cash debit from obligation discharge.
- Deduplicate operations while preserving genuine returns and corrections.
- Reconcile source completeness and external outcomes beyond debit/credit equality.
A balance needs an owner, a currency and an explanation#
A multi-tenant double-entry ledger records financial events for multiple customers with controlled access and balanced journal entries. Each journal can have two or more legs; total debits must equal total credits in its accounting scope and currency. The useful result is an explanation of cash, obligations, fees and timing differences, rather than a balance that changes without a trace.
For a payout platform, the harder questions come after that arithmetic: who owns the book, which payee is still owed, what cash has actually moved, and who may see or change the record? This article works through a USD 200 receipt, a fee, a hold and a USD 150 payout. It then covers concurrent spending, retries, isolation and reconciliation. Implementation references were checked on October 4, 2026.
Separate tenant access from the ownership of the books#
A tenant is an application access boundary. A legal entity is an accounting owner, and a book is a defined accounting record. Those concepts can align, but they are not interchangeable. One tenant may contain several entity books; a platform-owned book may use tenant and payee dimensions to attribute obligations. Decide the ownership and consolidation model with finance before making tenant_id the entire chart of accounts.
Choose separate databases, schemas or shared tables according to operational and isolation requirements. Every choice needs enforced authorization and consistent identifiers across reads, postings, caches, search, exports and support tools. A shared-table model does not excuse weak isolation, and a separate database does not prevent a service from connecting with the wrong credentials.
For this article’s example, tenant A operates its own illustrative business book, including its cash, payee liabilities and earned fee revenue. Tenant B has a separate book. The entries do not assert that a platform may legally hold customer funds or recognize every fee this way. A real platform must use the ownership, safeguarding and revenue-recognition rules applicable to its arrangement.
Cross-entity transfers need explicit linked records in the affected books, such as appropriate due-to/due-from accounts, rather than an unauthorized journal that consumes another tenant’s cash. Authorized consolidated reporting may combine books under defined rules; it must retain entity, tenant and currency dimensions so its totals can be explained.
Define accounts and versioned posting templates#
TigerBeetle’s financial-accounting guide explains the relationship between account types and debit/credit balances. Debit does not universally mean money leaving, and credit does not universally mean money arriving. In the example below, debits increase assets and reduce liabilities; credits increase liabilities and revenue and reduce assets. Contra accounts and other account types need their own defined treatment.
| Account | Purpose in this example | Normal increase |
|---|---|---|
| Cash — USD | Confirmed cash belonging to this book | Debit |
| Outbound clearing asset — USD | Cash debited for a transfer whose required receipt evidence is outstanding | Debit |
| Payable available — USD | Earned obligation eligible for reservation, subject to controls | Credit |
| Payable held — USD | Earned obligation currently restricted from release | Credit |
| Payable in transit — USD | Obligation reserved for an unresolved payout | Credit |
| Fee revenue — USD | Fee recognized under the illustrative agreement | Credit |
Store account owner, currency, type, status and permitted use explicitly. Use fixed currency precision and bounded integer minor units or an appropriate fixed decimal representation; do not accumulate money in binary floating-point fields. Check amount bounds and rounding rules, including fees and conversions, before accepting a journal.
The diagram shows a source event selecting a versioned posting template and producing a balanced entry with provenance. Retain that template version, logical operation, originating event, effective time, recorded time, approver where required and correction links. The accounting record should explain the business fact, not merely copy an API response code.
A posted entry is immutable under the journal policy. Corrections are linked entries, not edits to old amounts or deletion of inconvenient rows. Pending drafts can follow a separate lifecycle. Access to draft mutation, posting and correction must be distinct, and journal balance must be enforced across all legs before any posted record becomes visible.
Worked example: cash movement does not always discharge the payable#
Assume tenant A receives USD 200 that creates a payee obligation. Its agreement permits a USD 10 earned fee and a USD 20 hold. It then reserves USD 150 for a payout. No FX, tax withholding, additional bank fee or opening balance is assumed. The completion policy requires verified recipient delivery before that payout discharges the payable.
| Evidence/event | Debit | Credit |
|---|---|---|
| Confirmed receipt creating the obligation | Cash 200 | Payable available 200 |
| Fee earned under the agreement | Payable available 10 | Fee revenue 10 |
| Apply a restriction to 20 still owed | Payable available 20 | Payable held 20 |
| Authorize and reserve the 150 payout | Payable available 150 | Payable in transit 150 |
| Confirmed outgoing cash debit; receipt outstanding | Outbound clearing asset 150 | Cash 150 |
| Required recipient-delivery evidence obtained | Payable in transit 150 | Outbound clearing asset 150 |
Each row balances in USD. After reservation, the total payable is still USD 190: available 20, held 20 and in transit 150. Reserving money changes availability, not ownership of the obligation. After the outgoing debit but before verified delivery, cash is 50 and the clearing asset is 150; those assets total 200 against 190 in liabilities and 10 in fee revenue. A provider’s acceptance alone does not justify removing the in-transit liability.
After the defined delivery evidence arrives, cash is 50, payables are 40 and fee revenue is 10. The remaining 40 consists of 20 available and 20 held. Holding earned funds does not make them unearned or erase the debt. If tenant B separately has cash 100 and payable 100, neither the payout nor its cached balance may consume or reveal tenant B’s records.
If the transfer fails after the cash debit but before delivery, keep the outstanding records until the relevant evidence arrives. When the USD 150 actually returns, debit cash 150 and credit outbound clearing 150. When the operation is established as failed and its reservation can safely be released, debit payable in transit 150 and credit payable available 150. The two events may occur at different times; do not invent a returned cash receipt to force them together.
A later return after a previously completed payout is a different business event. If the agreement requires reopening the obligation, its confirmed USD 150 return can debit cash and credit payable available for 150. Link it to the completed payout and its policy basis. A repeated delivery callback adds no new entry; a genuine return does. Other contracts may require different accounts and must not be forced into this illustrative treatment.
Make admission and posting atomic under concurrency#
Writing six journal legs one at a time risks leaving a partial financial record. Validate account ownership, currency, permitted status, balanced totals and operation identity, then commit the complete journal with its deduplication/admission record in one database transaction. A local transaction does not atomically commit an external bank transfer.
Balance sufficiency also needs concurrency control. With USD 100 eligible, two requests for USD 80 can both pass an unlocked preliminary read. A conditional reservation, correctly scoped locking or an appropriate serializable transaction must ensure only an admissible combination commits. Enforce the limit over the authoritative available/reserved state, not a mobile cache or a delayed analytical projection.
PostgreSQL’s isolation documentation describes serializable execution and serialization failures requiring transaction retry. If using that approach, retry the entire failed local transaction with its logical identity; do not leave earlier legs committed. Keep provider release outside blindly retried database work, with a durable intent/outbox and explicit recovery for a crash between release and result recording.
TigerBeetle’s two-phase transfers offer a different mechanism: pending transfers reserve amounts before posting, voiding or expiring. Those are ledger protocol states, not proof that a bank transfer reached its recipient. An internal reservation expiry must not free funds for a second payout while the original external result is unknown. Decide how the ledger mechanism maps to the financial facts instead of treating all systems’ pending labels alike.
Use separate identities for delivery, operation and journal#
An API request key, webhook delivery ID, payout operation ID and journal ID answer different questions. Scope request identity to the authorized tenant/book and intended action, bind it to material parameters, and persist the result. Deduplicate a repeated delivery while still recognizing a new authorized partial payment, return or correction for the same obligation.
Provider idempotency has limits. Stripe’s documentation permits key pruning after at least twenty-four hours. Keep a durable internal mapping from logical operation to provider reference and accounting effect; replaying an old key after the provider’s retention window is not enough protection. A timeout means retrieve or investigate the original, not create a replacement under a fresh key.
Use uniqueness constraints and atomic journal commits to prevent the same authorized accounting effect from posting twice. For external actions, use one release owner, a recorded intent and provider-specific recovery. Multiple worker retries must not become multiple payouts merely because their local delivery IDs differ. Unknown results remain exceptions with owners until evidence resolves them.
Distinguish occurrence time from ingestion time. An out-of-order return should be linked and resolved according to the actual operation history, not discarded because a paid callback has a later arrival timestamp. Rebuilding balance projections must not invoke live provider submission or repost already accepted journal effects.
Enforce isolation beyond the posting endpoint#
PostgreSQL row security can restrict normal row access, but superusers and BYPASSRLS roles bypass it, and table owners normally do unless constrained with FORCE ROW LEVEL SECURITY. Run application paths under appropriately restricted roles and verify policies for both reading and writing. Enabling a policy does not establish that every export or maintenance task is isolated.
Obtain tenant context from authenticated authorization rather than trusting a client-supplied tenant_id. Validate that every account and operation belongs to the authorized scope. Include the scope in lookup and cache keys, and constrain relationships so a journal cannot attach an unrelated tenant’s account accidentally.
Support access and cross-tenant reporting need explicit permission, purpose and audit evidence. Produce approved aggregates with controlled drill-down rather than letting a normal tenant query raw records globally. Check imports, exports, background jobs, error messages and backups too; a correctly filtered dashboard does not prove isolation across those paths.
Keep native currencies balanced and preserve executed FX facts#
A USD 100 debit and EUR 90 credit are not a balanced journal merely because their exchange value matches. Represent a conversion with balanced native-currency legs and explicit bridge/position accounts under the chosen accounting model. Link the journals with the quote, currencies, amounts, execution reference, rate convention, fees and rounding. Do not add mixed currencies into one wallet total without a stated reporting conversion.
Track quote expiry separately from execution. A stale quote may require a new approved price or cancellation before release; it must not silently change what the recipient agreed to receive. Record actual execution and later functional-currency valuation under finance’s policy. A reporting-rate change does not rewrite the historical USD or EUR movement.
Foreign-bank, tax or regulatory reports depend on entity, account and jurisdiction requirements. Keep the evidence the applicable reporting process needs, but do not assume every multi-tenant ledger requires a particular country’s reporting form. An operational payout subledger is also not automatically the company’s complete general ledger; define control-account mapping, export completeness and reconciliation with finance.
Reconcile obligations, cash and effects separately#
Reconcile opening balances, admitted postings and closing balances for each book/account/currency. Then match logical operations to provider records and actual cash evidence using identities, amounts and fees. A balanced journal can still use the wrong owner, account or business event. Internal arithmetic alone cannot prove external receipt or source completeness.
For the example, the completion review must explain the USD 200 receipt, USD 10 fee, USD 20 held payable, USD 150 executed payout and USD 50 remaining cash. Retain unresolved clearing and return records with responsible owners and review timing. Two exports with the same count can still have one missing item and one duplicate; compare identities as well as totals.
Batching deserves the same precision. At an illustrative USD 2.10 per transfer, fifty transfers cost USD 105 whether sent separately or inside one file, if pricing remains per transfer. Combining several obligations for the same eligible payee can reduce transfer count only when terms and rail rules permit; per-batch pricing is another possible model. Compare actual tariff inputs, settlement timing and exception handling rather than promising savings from a batch label.
For an implementation review, challenge duplicate callbacks, concurrent requests, missing legs, wrong-tenant account IDs, failed external release, late cash return, quote expiry and projection rebuilds. Preserve required recognition while an incident is investigated; do not wait indefinitely for a perfect root cause before recording known financial facts. Corrections should state their evidence and approval, including explicitly provisional treatment where policy allows.
Related guides cover payout ledger records, multi-tenant data isolation and multi-currency reconciliation. The ledger’s practical test is whether finance and support can explain the same obligation and cash movement without changing historical entries to fit a screen.
Frequently Asked Questions
What is a multi-tenant double-entry ledger in practical platform terms?
It records balanced financial journals for multiple customers with controlled access and explicit book/account ownership. A tenant access boundary is not automatically a legal entity or separate accounting book. Define currencies, obligations, posting rules and reporting scope before deriving product balances.
Why does single-entry bookkeeping fail for marketplace payouts and refunds?
A single mutable balance can hide the relationships among cash, payables, fees and returns. Double-entry makes those relationships explicit and detects unbalanced postings. It does not guarantee correct ownership, recognition, complete source capture or recipient delivery; those need separate controls and evidence.
How do idempotency keys and immutable ledger entries work together during retries?
Persist the logical operation and its accounting effect with atomic deduplication. Repeated requests or deliveries retrieve the recorded result instead of posting again; corrections append linked entries. Provider idempotency can expire, so internal durable identity and unknown-result recovery remain necessary.
When should we use batch payout windows versus per-transaction payouts?
Compare the actual fee model, cash-flow terms, allowed aggregation and recovery work. Fifty transfers at a hypothetical USD 2.10 each still cost USD 105 inside a batch file if pricing is per transfer. Savings require fewer chargeable transfers or a different tariff, not merely a batch label.
How should we structure multi-currency accounting so stale FX quotes do not corrupt balances?
Balance each native currency using the defined conversion/bridge model and retain approved quote and actual execution evidence. Revalidate expired quotes before release, including changed terms. Later reporting valuation must not rewrite executed native-currency amounts or mix currencies into an unexplained total.
What is the difference between the general ledger and derived wallet balances?
A company general ledger includes the accounting scope defined by finance; a payout system may be an operational subledger feeding its control accounts. Product balances are derived views with defined availability and reservation rules. Reconcile the views, subledger and general ledger rather than assuming they are identical.
How do we enforce tenant isolation while still supporting cross-tenant operational reporting?
Enforce authenticated scope and account ownership across queries, writes, caches, jobs and exports under restricted credentials. Use explicitly authorized reporting aggregates and controlled drill-down. Database row security has privileged-role and owner bypass behavior that must be accounted for; one filtered endpoint is not proof of complete isolation.
Try a related tool
Researched and edited by the Gruv editorial team. Gruv builds cross-border billing, payouts, and finance-operations software for global businesses.
Sources
Includes 4 external sources outside the trusted-domain allowlist.
Educational content only. Not legal, tax, or financial advice.
Related Posts

Building a Payout Ledger with Double-Entry Bookkeeping for Platform Financial Records
A usable payout ledger should be treated as an accounting system, not just a record of money moving out. This guide focuses on the mechanics your finance and ops teams need every day: record transactions in a journal, post them to the general ledger, and confirm that debits and credits stay in balance.

Building a Multi-Tenant Payment Platform with Defensible Data Isolation
A payment platform must keep one tenant’s recipients, balances, approval rules, provider credentials and exports inaccessible to another tenant. A tenant ID column organizes records; it does not authorize the user, constrain a privileged worker or prove that a callback belongs to the selected tenant.

How to Reconcile Multi-Currency Payouts Across Multiple PSPs in One Ledger
**Multi-PSP, multi-currency reconciliation means turning several provider payout views into one ledger and a clean exception process.** The goal sounds straightforward but is difficult in practice: bring multi-currency payouts from several PSP accounts into one ledger, then route every mismatch into a clear exception path instead of a spreadsheet note. Done well, your ledger becomes the place you trust for cash reporting, while Stripe, Adyen, and PayPal stay what they should be: source evidence, not competing versions of the truth.

