Skip to content

Case study

Reconciling three payment sources into one ledger

Three payment sources, three spreadsheets and a six-hour weekly reconciliation, replaced by a scheduled job that only asks for a human on exceptions.

Client
Logistics company
Sector
Logistics
Duration
PLACEHOLDER — confirm engagement length
Stack
Node.js, PostgreSQL, Stripe API

Published with the client’s permission. Their name, product names and any identifying figures have been removed or generalised.

The engagement

The situation

Payments arrived through a card processor, a bank feed and a marketplace payout, each with its own identifiers, timing and fee structure. Reconciling them against invoices was a manual weekly exercise in a spreadsheet, and errors were usually found a month later during the accounting close.

What we did

We modelled the ledger first and treated each payment source as an untrusted feed into it, rather than trying to make three systems agree directly with each other. Matching runs in passes — exact reference, then amount and date window, then fuzzy — with anything unmatched surfaced for review rather than guessed at.

The outcome

The weekly reconciliation became a scheduled job with an exceptions queue. Unmatched items are surfaced the next morning instead of at month end, which is the change that actually mattered: errors are now found while the context is still fresh.

Manual reconciliation
PLACEHOLDER
Client-reported; confirm the figure and its baseline before publishing.
Payment sources unified
3
Card processor, bank feed, marketplace payout.
DecisionsWhat we chose, and what it cost

Every choice traded something away.

One canonical ledger, not three-way matching

A single internal ledger that each source feeds into, with source-specific adapters.

Trade-off More upfront modelling, and an extra hop for every payment. In exchange, adding a fourth payment source later is an adapter rather than a rewrite of the matching logic.

Matching in passes, with a confidence threshold

Exact match first, then progressively looser passes, with anything below the threshold queued for a person.

Trade-off Some items still need human review. Automating those too would have meant accepting silent mis-matches, which in a financial context is worse than the manual work.

Deliberately not real-time

A scheduled overnight run rather than event-driven reconciliation.

Trade-off Figures are a day behind. That was acceptable here and removed an entire class of ordering and partial-failure problems that real-time would have introduced.

Detail

What we found

Discovery turned up something the brief had not mentioned: the marketplace payout was already net of fees, while the card processor reported gross with a separate fee line. The spreadsheet had been silently absorbing that difference for over a year, which meant the historical figures everyone trusted were slightly wrong in a consistent direction.

That changed the job. Before building anything we agreed a position on historical data: the backfill would restate it rather than preserve the existing numbers, and the difference would be reported rather than quietly corrected.

What we built

A ledger service with one adapter per payment source, a matching engine that runs in passes, and an exceptions queue with enough context attached that a non-technical user can resolve an item without opening another system.

The backfill was written to be resumable from the start, which proved necessary — the first run was interrupted twice.

What we would do differently

We built the exceptions queue after the matching engine. It should have come first — the queue is what made the system trustworthy, and for the first two weeks without it the client had no way to see what the matcher was unsure about.

—Start a project

Start a project like this.

If something here resembles your situation, describe yours. We will tell you how close it actually is.

  1. 01

    Send a short brief

    Five minutes. What you are building, or what is broken.

  2. 02

    We reply within one business day

    From an engineer, with a real question or observation.

  3. 03

    A 30-minute technical call

    Free, no deck, no obligation.

Or read how we work first →