Reconciliation

Match transactions, fees, and payouts to your ledger.

View as MarkdownInstall skills

Reconciliation is how you prove that the money VINR collected, the fees it charged, and the cash that landed in your bank all agree with your own ledger. This page explains the data VINR gives you to do that and how to automate the match so close-of-books is a report, not an investigation.

The reconciliation modelAsk

Three layers have to line up, and each is one step removed from the next:

  1. Gross activity — individual payment and refund objects, each with the amount the customer paid or got back.
  2. Fees — what VINR deducts per transaction (processing, currency conversion, dispute fees).
  3. Net cashpayout objects: the batched bank transfers VINR sends you, grouped under a settlement that lists exactly which transactions it contains.

The golden rule: sum(gross activity) - sum(fees) = sum(net payouts) over any closed period. Reconciliation is finding and explaining every line where that equation does not hold yet (e.g. funds still in transit, held for dispute, or pending payout).

payments + refunds  ──(minus)──>  fees  ──(equals)──>  payout (net)
   pay_, re_                       per-transaction        po_ / setl_

Transaction-level dataAsk

Every balance-affecting event is a balance transaction. List them with the SDK, filtered to a window, and join on the source object's ID to your own records.

import { Vinr } from '@vinr/sdk';
const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });

// All balance transactions that settled in May 2026.
const txns = await vinr.balanceTransactions.list({
  created: { gte: 1746057600, lt: 1748736000 }, // unix seconds
  limit: 100,
});

for (const t of txns.data) {
  console.log(t.id, t.type, t.source, t.gross, t.fee, t.net, t.payout);
  // e.g. "btx_9f2 payment pay_8a1 1000 -34 966 po_2kd"
}

Each row carries the source (pay_, re_, dp_, …), the gross amount, the fee deducted, the net that hit your VINR balance, and the payout it was paid out in (null while still in transit). Amounts are integers in minor units — 966 is EUR 9.66.

Reconcile on net and payout, not on the original charge amount. A EUR 10.00 payment never arrives as EUR 10.00 in your bank — fees come out first, which is exactly what the fees breakdown below explains.

Fees breakdownAsk

The fee on a balance transaction is a total. To audit it, expand the fee_details array, which itemises each component:

{
  "id": "btx_9f2",
  "source": "pay_8a1",
  "gross": 1000,
  "fee": 34,
  "net": 966,
  "currency": "EUR",
  "fee_details": [
    { "type": "processing", "amount": 29, "description": "1.4% + 0.15" },
    { "type": "fx",         "amount": 5,  "description": "USD→EUR conversion" }
  ]
}
Fee typeWhen it applies
processingEvery successful payment.
fxCharge currency differs from your settlement currency.
disputeA dp_ is opened; reversed if you win.
payoutSome bank rails charge per transfer (often zero in EUR/SEPA).

Refunds return the gross but not the original processing fee, so a fully refunded payment leaves a small negative net — book it as a cost, not as a balancing error.

Reconciling to payoutsAsk

A payout is the bank transfer; its parent settlement is the report that closes the loop. Fetch a settlement and you get every transaction that funded that exact deposit, so the amount on your bank statement maps to a single VINR object.

const settlement = await vinr.settlements.retrieve('setl_2kd9');

console.log(settlement.payout);        // "po_2kd" — matches the bank reference
console.log(settlement.gross);         // 482000
console.log(settlement.fees);          // -16388
console.log(settlement.net);           // 465612  → equals the deposit
console.log(settlement.status);        // "paid"

// The exact transactions inside this payout:
const lines = await vinr.settlements.listTransactions('setl_2kd9');

Match the deposit

Find the bank credit, then look up the payout whose net equals it. The payout's bank_reference appears on most statements.

Open the settlement

Pull the parent settlement. Its net must equal the deposit to the cent.

Drill into transactions

List the settlement's transactions and tick each one off against your sales ledger. Anything in your ledger but not the settlement is in transit (will appear in a later payout) or held.

Explain the residue

In-transit, held-for-dispute, and reserve amounts are timing differences, not errors. Record them so next period's opening balance is correct.

AutomationAsk

Manual matching does not scale. Two production patterns:

Event-driven (incremental). Subscribe to payout.paid and pull that settlement's transactions as each deposit lands, writing them straight to your ledger.

// In your webhook handler.
const event = vinr.webhooks.verify(payload, req.headers['x-vinr-signature']);

if (event.type === 'payout.paid') {
  const lines = await vinr.settlements.listTransactions(event.data.settlement);
  await ledger.recordSettlement(event.data.id, lines.data); // your code
}

Scheduled export (bulk). For monthly close, request a reconciliation report covering the period — it returns one row per balance transaction with source, fee details, and payout, ready to load into your accounting system.

curl https://api.vinr.com/v1/reports/reconciliation \
  -H "X-Api-Key: $VINR_SECRET_KEY" \
  -d "period_start=1746057600" \
  -d "period_end=1748736000" \
  -d "columns=source,gross,fee,net,payout,fee_type"

Always reconcile on a closed period and key your records on the immutable btx_ and setl_ IDs, not on amounts or timestamps. Re-running an export over an open window will pick up newly settled transactions and appear to "change" prior totals.

Next stepsAsk

Was this page helpful?
Edit on GitHub

Last updated on

On this page