Zafarin
Closing the daily reconciliation break queue at Zafarin
Zafarin's daily safeguarding reconciliation produced a queue of breaks that finance operations cleared by hand every evening, and the queue grew with transaction volume. We rebuilt the partner feeds, the ledger and the break workbench underneath it.
Outcomes
What changed, and how we know
- 01
End-of-day breaks left for manual clearing
From a nightly hand-cleared queue to an exception
- Before
- A queue finance operations worked through by hand every evening, growing with transaction volume
- After
- Most runs clearing without manual intervention, and a residual queue one analyst can hold
- Window
- Measured over the twelve weeks after cutover
- Method
- Break counts from the client's existing reconciliation run log, attributed by the rules agreed during the baseline
- 02
Daily safeguarding reconciliation finished within the working day
Held through two month-end peaks
- Before
- Slipping past the end of the working day on the busiest days of the month
- After
- Complete inside the working day on every run day in the measurement window
- Window
- Measured over the twelve weeks after cutover
- Method
- Run completion timestamps from the client's own reconciliation run log, not from our deployment records
- 03
Statement formats reaching the matching engine
Four parsing paths removed from the matching engine
- Before
- Four partner formats, each with its own parsing path inside the matcher
- After
- One canonical statement model, normalised at the ingestion boundary
- Window
- At cutover
- Method
- Parsing paths counted in the shipped matching engine, against the four dialects it carried before ingestion was normalised
Challenge
Zafarin runs multi-currency business accounts on top of four partner banks, holding customer funds in safeguarded accounts and reconciling its internal ledger against partner statements every business day.
Each daily run left a queue of unexplained differences that finance operations cleared by hand before the safeguarding attestation could be signed, and the queue grew faster than the team could hire against it.
Constraints
- Safeguarding reconciliation is a daily regulatory obligation, so no run could be skipped, batched or replayed late while the work was in flight
- Ledger entries had to remain immutable for the statutory retention period, with corrections posted as compensating entries rather than edits
- The annual safeguarding audit was already in fieldwork, so the evidence the auditors were sampling had to keep the same shape throughout
- Customer money records had to stay inside the EEA, which ruled out two of the managed matching services the client had been quoted
Approach
- Team
- Two senior backend engineers, a data engineer full time through the baseline and half time after it, and a tech lead at 40 percent
- Engagement model
- Outcome-based
- Duration
- Eight months, including the baseline period
A six-week joint baseline against the client's existing reconciliation run log before any code changed, then two-week iterations with the break queue reviewed alongside finance operations at each boundary.
Architecture
We put a normalising ingestion API in front of the four partner statement feeds, made the internal ledger append-only with an idempotency key derived from the partner's own transaction reference, and gave finance operations a workbench where every manual decision is filed as a compensating entry with its reason and author attached.
- TypeScript
- Node.js
- PostgreSQL
- Temporal
- Amazon S3
- Grafana
Key decisions
- Normalise every partner feed into one canonical statement model at ingestion
- The matching engine understood four dialects, so a rounding, currency or value-date rule could differ in four places, and every difference eventually surfaced as a break somebody cleared by hand.
- Derive the ledger idempotency key from the partner's own transaction reference
- The largest single class of breaks was duplicate postings from retried webhooks. A key the partner also owns makes the duplicate impossible to post rather than merely detectable afterwards.
- Replace the matching engine's inputs before touching the engine itself
- The matching rules were the evidence the auditors were sampling mid-fieldwork. Moving the rules and the feeds in the same release would have left no way to explain why a number changed.
- Corrections as compensating entries in a workbench, not adjustments in a spreadsheet
- The attestation needs to show who decided what and why. Spreadsheet adjustments carried none of that back into the ledger, so the audit trail was reconstructed by email each year.
Why we were willing to price this on the outcome
An outcome contract only works when the number is not arguable. Zafarin had one: the count of end-of-day breaks left for manual clearing, emitted by the existing run log every business day and already the figure finance operations signed the safeguarding attestation against. Nobody had to build instrumentation to make it exist, no team had an incentive to redefine it, and the breaks in scope came from Zafarin's own posting and matching logic rather than from a partner bank. That combination is rare enough that we do not offer this model often.
We spent the first six weeks measuring and classifying rather than building. Without an agreed baseline an outcome contract becomes a dispute about what "before" meant, and the baseline did a second job here: it drew the boundary of what we were accountable for. Breaks originating in a partner's own statement — a file delivered late, a value date restated after booking — were excluded and reported separately, because we could not fix them and should not have been paid for them moving.
What we would do differently
We drew that attribution boundary from a sample of breaks rather than classifying every break in the baseline window. One class sat close enough to the line that both sides could argue it in good faith, and the first month after cutover went on settling the argument instead of shipping. Classifying the full window by hand would have taken a week and removed the question entirely.

