Jeffrey Zalischi

← Back to all work

A case study

Ledgerline 2025

Turning a three-day spreadsheet ritual into a forty-second stream.

In one line
Real-time payment reconciliation engine
My role
Lead engineer — architecture, streaming pipeline, on-call runbook
Timeline
8 months · 2024–2025
Team
4 engineers, 1 product manager, embedded finance analyst
Built with
Go, PostgreSQL, Kafka, Kubernetes

Ledgerline replaced a manual reconciliation process — analysts diffing bank statements against an internal ledger in spreadsheets — with a streaming engine that matches every transaction as it lands. The goal was not just speed but auditability: every match, and every exception, had to be explainable to a regulator.

Where it started

The situation

Thirty thousand transactions a day were reconciled by hand each morning. A discrepancy from Monday might not surface until Wednesday, by which point the money had already settled and the fix meant a clawback. The process didn't scale, and it left no trustworthy audit trail.

The constraint

This was money. A reconciliation engine that is fast but occasionally wrong is worse than a slow one that is always right. Correctness and a complete, immutable audit log were hard requirements — throughput was the negotiable part.

How it was built

Idempotent ingest

Bank events and ledger events flow into Kafka topics keyed by a deterministic transaction fingerprint. Re-delivered events collapse onto the same key, so the pipeline is safe to replay end-to-end — essential for both recovery and backfilling history.

A matching state machine

Each transaction moves through an explicit state machine (unmatched → provisionally matched → confirmed → exception). Every transition is written to an append-only table in PostgreSQL with the rule that fired it, so any analyst can reconstruct exactly why two records were paired.

Exceptions as first-class work

Unmatched transactions after a tunable window are routed to an exceptions queue with the candidate matches ranked. Analysts stopped hunting for problems and started resolving a prioritized list — the human effort moved to where judgment was actually needed.

Observability from day one

Match latency, exception rate, and queue depth are exported as first-class metrics with alerts tied to the reconciliation SLO. The on-call runbook was written alongside the code, not after the first incident.

What it changed

Reconciliation lag
3d → 40s
Unexplained variances, year one
0
Transactions matched automatically
30k/day
Auto-match rate
98.7%

Looking back

The state machine was the decision that aged best — making every transition explicit turned 'why did this match?' from an investigation into a database query. If I did it again I'd invest earlier in a replay harness for historical data; backfilling a year of transactions taught us where the model's edges were, and I'd rather have learned that in week two than month six.