Ledger
Every cent that moves through Finscale is a posting in a double-entry ledger: two balanced entries per movement, append-only, in integer minor units. The Balance, settlement report, and payout objects you read in the API are all views over the same postings — which is why they always agree.
From customer to bank account
The full journey of the example payment — pay_8Q2mX4nT1cVb, €49.00, "amount": 4900 — from the customer's bank to yours:
Three things to notice:
- Authorization moves no money. The hold earmarks €49.00 at the customer's issuer; nothing posts to the ledger until capture. That's why a voided authorization costs nothing to unwind — there's nothing to reverse.
- Capture creates a claim, not cash. On capture, your pending balance is credited the gross
4900— but the cash is still at the provider. Settlement is the moment the claim becomes safeguarded cash. - Fees are deducted at settlement. The line settles as gross
4900, fee-64, net4836— the same three numbers you'll find on the settlement report. What's inside the fee is explained under fee concepts.
Double-entry, always
Each stage above is recorded as a pair of postings that sum to zero — money is never created, destroyed, or edited, only moved. The worked example as postings, in minor units:
| Step | Debit | Credit | Amount |
|---|---|---|---|
Capture pay_8Q2mX4nT1cVb |
Provider receivable (prov_eu_acq_01) |
Merchant pending balance | 4900 |
| Provider settles | Safeguarded cash | Provider receivable | 4900 |
| Fee applied | Merchant pending balance | Fee accounts | 64 |
| Balance release | Merchant pending balance | Merchant available balance | 4836 |
Payout po_6Fn1mS8vQ2Xc |
Merchant available balance | Safeguarded cash (transfer out) | 4836 |
Follow any account down the column and it nets cleanly: pending is credited 4900 and debited 64 + 4836 — back to zero. Available is credited 4836 and debited 4836 by the payout. Nothing is left dangling, and every number in the API is the sum of postings like these.
The API objects are views
There is no /v1/ledger endpoint — you never query postings directly. Instead, each money-movement object in the API is a materialized view over them:
| Object | What it sums |
|---|---|
GET /v1/balance | Your pending and available account balances, per currency, right now. |
| Settlement report | One settlement day's postings for your account, line by line, with gross + refunds + fees = net. |
| Payout | The closing transfer that empties available into your bank account. |
Payment fields amount_captured, amount_refunded | The postings scoped to one payment. |
curl https://api.finscale.dev/v1/balance \
-H "Authorization: Bearer sk_test_51FinscaleDemo…"
{
"object": "balance",
"available": [
{ "amount": 1914190, "currency": "EUR" },
{ "amount": 402100, "currency": "USD" }
],
"pending": [
{ "amount": 68400, "currency": "EUR" }
],
"livemode": false
}
Because all views derive from one set of postings, cross-checks always close: sum(net) of a settlement report equals its payout's amount, and the balance moves by exactly the postings in between. The reconciliation page turns that property into a daily routine.
Money moving backwards
Refunds and disputes are not edits to old postings — the original capture stays in the ledger forever. They are new, reversing postings:
- Refund.
POST /v1/refundsfor €15.00 against the example payment posts a debit of1500to your available balance and a credit to a refund-payable account, from which the original provider returns the money to the customer. The refundrf_3Vp7dK2wQxthen appears as a negative line on the next settlement report. - Dispute. When a dispute opens, the disputed amount is debited from available and held while the case runs. Win, and a reversing credit returns it; lose, and the hold becomes final.
- Correction. On the rare occasion a provider restates a settled line, Finscale posts an itemized adjustment — never a silent rewrite. If a number changed, there is a posting that changed it.
Invariants you can build on
- Postings balance. Every movement debits one account and credits another for the same amount. The ledger sums to zero by construction.
- Append-only. Postings are immutable. History never changes under you — yesterday's report re-downloaded today is byte-identical.
- Integer minor units. Amounts are integers in the currency's minor unit (
4900, never49.00) — no floating-point drift, anywhere. - Views agree. Balance, reports, payouts, and per-payment amounts are projections of the same postings and can never contradict each other.
One consequence worth knowing: reversing postings don't bounce. A refund or dispute debit lands even when it pushes your available balance below zero — see negative balances for what happens next.
Webhook events
The ledger itself emits no events, but every posting is triggered by an object that does:
| Event | Ledger effect |
|---|---|
payment.succeeded | Capture posted — pending balance credited the gross amount. |
refund.succeeded | Reversing posting — available balance debited. |
dispute.created / dispute.closed | Disputed amount held from available; released or made final at close. |
settlement.report.ready | A settlement day's postings published — pending moved to available, net of fees. |
Subscribe via webhooks. Deliveries are signed and retried, but ordering is not guaranteed — treat each event as a pointer to re-fetch the object, not as a ledger feed.
Related
- Balance — available vs pending, and what makes each move.
- Settlement & payouts — schedules, reserves, and fee concepts.
- Reconciliation — the daily routine that proves the views agree.
- Authorization flows — holds, captures, and voids in API terms.