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:

customer pays 4900 authorization hold issuer earmarks 4900 merchant pending +4900 gross authorize capture settlement — provider pays captured funds provider ledger safeguarded cash +4900 merchant ledger available +4836 bank transfer payout 4836 fee −64 · net payout po_6Fn1mS8vQ2Xc

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, net 4836 — 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:

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

ObjectWhat it sums
GET /v1/balanceYour pending and available account balances, per currency, right now.
Settlement reportOne settlement day's postings for your account, line by line, with gross + refunds + fees = net.
PayoutThe closing transfer that empties available into your bank account.
Payment fields amount_captured, amount_refundedThe postings scoped to one payment.
GET /v1/balance — the ledger's live totals
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/refunds for €15.00 against the example payment posts a debit of 1500 to your available balance and a credit to a refund-payable account, from which the original provider returns the money to the customer. The refund rf_3Vp7dK2wQx then 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, never 49.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:

EventLedger effect
payment.succeededCapture posted — pending balance credited the gross amount.
refund.succeededReversing posting — available balance debited.
dispute.created / dispute.closedDisputed amount held from available; released or made final at close.
settlement.report.readyA 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.