Reconciliation
Every settlement day produces a batch you can prove: a report whose lines sum to its payout, with your payment id and order reference on every row. This page covers the batch objects, the report columns, the id chain from bank statement down to payment, and the daily workflow that closes the books.
The id chain
Reconciliation is following ids downward — from the money that arrived to the orders that earned it. Every link in the chain is a field in the API:
| Id | Prefix / form | Issued by | Where you see it |
|---|---|---|---|
| Payment id | pay_ | Finscale | The API, every webhook, and the payment_id column of every report row. |
| Merchant reference | yours (ord_9f21_0716) | You | Echoed onto the payment and every report row — the join key to your orders table. |
| Settlement report id | stl_ | Finscale | Names the batch; linked from the payout's settlement_report field. |
| Payout id | po_ | Finscale | GET /v1/payouts, and on the bank transfer that lands in your account. |
| Provider id | prov_ | Finscale | The provider column and field — opaque, stable, never a company name. |
| Provider reference | provider-specific | Provider | Not an API field. Finscale stores it per attempt; it surfaces in dispute evidence and support lookups. |
| Network reference | ARN / RRN | Card network | Not an API field. Used when an issuer or bank quotes one — Finscale resolves it to a pay_ id for you. |
The practical rule: everything you automate joins on payment_id or reference. The provider and network references exist so that when an outside party quotes theirs — an issuer in a dispute, a bank tracing a transfer — the chain still resolves to one payment. You never store them yourself.
Settlement batches
A settlement report is the batch object: it covers a settlement day, carries normalized totals, and flips to ready when its CSV is generated. Reports are produced per provider per settlement day — plus a consolidated report with "provider": null that spans all of them. Whatever the provider, the shape is identical:
{
"id": "stl_2026_07_16_eu01",
"object": "settlement_report",
"period_start": "2026-07-15",
"period_end": "2026-07-16",
"provider": "prov_eu_acq_01",
"currency": "EUR",
"status": "ready",
"totals": {
"gross": 2041800,
"refunds": -96400,
"fees": -31210,
"net": 1914190
},
"transaction_count": 412,
"payout": "po_6Fn1mS8vQ2Xc",
"livemode": false,
"created_at": "2026-07-16T04:00:00Z"
}
Three properties make batches provable:
- The totals identity.
gross + refunds + fees = net, always — refunds and fees are negative. Here:2041800 − 96400 − 31210 = 1914190. - The payout link.
payoutnames the payout whoseamountequalstotals.net. Money in the report and money on the bank statement are the same number. - Immutability. A
readyreport never changes. Corrections arrive as itemized lines in a later report, per the ledger's append-only rule.
List batches with GET /v1/settlement_reports (newest first, cursor pagination); filter to one provider with ?provider=prov_eu_acq_01. A report in status: generating has no CSV yet — wait for the settlement.report.ready event rather than polling.
Report columns
Fetch the transaction-level export with Accept: text/csv. One row per payment, refund, and fee line; amounts in minor units:
| Column | Contents |
|---|---|
payment_id | The payment behind this line — always present. Refund lines carry the refunded payment's id. |
reference | Your order reference, echoed from the payment. The join key to your side. |
method | Method code (ideal, card, …). |
provider | Opaque provider id that processed the transaction. |
gross | Minor units. Negative on refund lines. |
fee | Fee for this line, minor units — always negative. What it's made of: fee concepts. |
net | gross + fee. The column that sums to the payout amount. |
currency | ISO 4217. |
settled_at | When the line settled (ISO 8601, UTC). |
curl https://api.finscale.dev/v1/settlement_reports/stl_2026_07_16_eu01 \
-H "Authorization: Bearer sk_test_51FinscaleDemo…" \
-H "Accept: text/csv"
payment_id,reference,method,provider,gross,fee,net,currency,settled_at
pay_8Q2mX4nT1cVb,ord_9f21_0716,ideal,prov_eu_acq_01,4900,-64,4836,EUR,2026-07-16T04:00:00Z
pay_3Xw9cR5pLm,ord_9f22_0716,card,prov_eu_acq_01,12900,-206,12694,EUR,2026-07-16T04:00:00Z
pay_8Q2mX4nT1cVb,ord_9f21_0716,ideal,prov_eu_acq_01,-1500,-25,-1525,EUR,2026-07-16T04:00:00Z
# … 409 more rows. sum(net) = totals.net = payout amount = 1914190
Row three is a refund line: negative gross, the refunded payment's payment_id, and the original reference — a partial refund of €15.00 against the €49.00 order. The CSV is idempotent: re-download it any time and you get the same bytes.
The daily workflow
Five steps, one per morning, fully automatable:
-
Receive the batch
Subscribe to
settlement.report.readyvia webhooks — it fires when a report's CSV is generated, shortly after the settlement day closes. (Fallback: pollGET /v1/settlement_reportsand pick up reports withstatus: readyyou haven't processed.) -
Pull the CSV
GET /v1/settlement_reports/{id}withAccept: text/csv. Store it — it's your audit artifact for the day. -
Prove the batch
Assert the three-way tie:
sum(net)over the rows =totals.net= the linked payout'samount. If this holds, the batch is internally consistent before you look at a single order. -
Join to your orders
Match rows on
reference(orpayment_idif you stored it at creation — you should). Mark matched orders as settled with theirsettled_atand net amount. Refund rows net against the same order. -
Tie the bank statement
When the payout lands (its
arrival_date), match the single credit of €19,141.90 on your bank statement topo_6Fn1mS8vQ2Xc. One transfer, one number, already proven equal to the batch.
{
"id": "evt_7k1M3nQwB8Xd",
"object": "event",
"type": "settlement.report.ready",
"data": {
"object": {
"id": "stl_2026_07_16_eu01",
"object": "settlement_report",
"status": "ready",
"currency": "EUR",
"totals": { "gross": 2041800, "refunds": -96400, "fees": -31210, "net": 1914190 },
"transaction_count": 412,
"payout": "po_6Fn1mS8vQ2Xc"
}
},
"created_at": "2026-07-16T04:00:02Z",
"livemode": false
}
When a line doesn't match
A healthy recon run classifies its exceptions instead of staring at them:
| Symptom | Almost always | Do |
|---|---|---|
| Order captured yesterday, not in today's report | Timing — the line hasn't settled yet. Slower rails (e.g. direct debit) settle later than cards. | Check it's still in pending on GET /v1/balance; expect it in a later batch. Alert only past the method's normal window. |
Row with negative gross you didn't expect |
A refund — possibly issued by support outside your order flow. | Join on payment_id; list the payment's refunds with GET /v1/refunds?payment=…. |
| Balance moved but no report line explains it | A dispute — disputed amounts are held from available when the dispute opens, itemized on the dispute object rather than as a report row. | Reconcile disputes from GET /v1/disputes and dispute.* events, separately from the settlement batch. |
| Row matches no order at all | A payment created outside your order system — a retried checkout, a manual charge, a stale reference. |
Fetch the payment by id; its metadata and provider_attempts usually identify the source. |
sum(net) ≠ payout amount |
Should not happen — the identity is enforced by the ledger. | Re-download the CSV (partial file?), re-check your parser handles negative values. If it genuinely fails, contact your Finscale representative with the report id. |
Related
- Settlement & payouts — schedules, reserves, and what's inside the fee column.
- Ledger — why the totals identity can't drift.
- Balance — where unsettled lines live in the meantime.
- Webhooks — signatures, retries, and ordering.