Ledger — reconcile against your own books
If you keep your own accounts, this is the chapter that lets you prove ours agree with them.
The invariant
Every transaction writes a balanced set of ledger entries — debits equal credits, always. Entries are append-only: a correction is a new compensating entry, never an edit. So the ledger is replayable, and any balance is derivable from it.
GET /ledger/health returns that invariant as a boolean over your tenant's books. It is cheap.
Call it nightly; alert if it is ever false.
Reading entries
GET /ledger/entries?page=0&size=100
Each entry carries its wallet, fund, direction (DEBIT/CREDIT), amount, the transaction that
produced it, and its timestamp.
Reconciling by your own reference
The endpoint built for exactly this:
GET /transactions/by-reference?referenceId=trip-88213
It returns a page, not a single row — deliberately. One reference legitimately spans several transactions: a posting and its reversal, a top-up and the credit it produced. Code for the list.
A nightly job that works:
- For each of yesterday's business events,
GET /transactions/by-referencewith your id. - Assert exactly one non-reversed transaction,
COMPLETED, for the amount you expect. - Fetch
GET /transactions/{id}/detailfor anything that disagrees — it includes the gateway block for top-ups and withdrawals, which is usually where the answer is. - Finish with
GET /ledger/health.
Anything unmatched is a real discrepancy worth a human.
GET /transactions behaves differently depending on the tokenWith your machine token it returns transactions across your whole tenant.
With a wallet-user token — the same endpoint, called from your app — the server rewrites the query to that user's own activity. The clamp is applied on our side; you cannot widen it by passing different parameters.
Same URL, two scopes. If you proxy this endpoint from your app, know which token you are forwarding.
What is true afterwards
What each movement records about its authorisation
Alongside the amounts, every transaction and every withdrawal stores how it came to be authorised: which credential asked, through which surface, and what we could prove about the customer at that moment.
That last part is the one to understand, because it decides who answers for a disputed movement. A payment your backend instructs on a machine credential records that no end user was identified. A movement made with a delegated token records that you asserted the user rather than that we authenticated them. A customer who logged in to us directly records that we did.
The distinction is stamped when the row is written and never recalculated, because the question a dispute asks is what was known at the time, not what today's rules would say.
These fields are not returned by the read APIs yet. They are described here because they affect how you should think about delegated sessions — not because you can query them today.
You can produce, for any business event you recorded, the GoHubPay transaction id and the balanced
ledger pair behind it — and ledger/health agrees. That is the state an auditor will ask you to
demonstrate.
Next: Top-up.