2026Distribution / Finance

InvoiceMatch

A distributor was approving every supplier invoice on human judgement alone. Duplicates and short deliveries were being paid, and nobody could see it happening.

Three way matchPurchase orderBolt M8 x 400Washer x 200Nut M8 x 400Goods receiptBolt M8 x 380Washer x 200Nut M8 x 400InvoiceHEX BLT M8 x 400WSHR FLT x 200NUT HEX M8 x 400Ordered 400, received 380, billed 400Totals still look plausible. Only the line reveals it.Held for exception, with the specific line named
Client
Industrial parts distributor
Role
Sole engineer, end to end
Engagement
Build, shadow run, handover
Scope
Intake to ledger posting

The Problem

Three documents have to agree before a supplier is paid: what was ordered, what arrived, and what was billed.

A small team checked every invoice against a purchase order and a goods receipt by hand. At the volume arriving each month, what actually happened is that they checked the easy ones carefully and waved the rest through. Suppliers were paid twice, short deliveries were paid in full, and nobody in the business could say how much it was costing.

Money left the business unnoticed

Duplicate invoices, deliveries billed in full after arriving short, and quiet price drift between the agreed rate and the invoiced one.

Early settlement discounts lost

The approval cycle ran long enough that discounts for prompt payment expired before an invoice cleared.

No answer to a simple question

Nobody could say what invoice processing cost, how often it was wrong, or which suppliers accounted for the errors.

Constraints

The non negotiables that ruled out the obvious approach.

A wrong match pays real money

An incorrect automatic approval sends cash to a supplier for goods never received. The system is tuned to abstain, and is measured on false approvals rather than on coverage.

Finance must own the rules

Tolerances change per vendor and per category, often monthly. If changing a tolerance needed a developer, the system would be abandoned inside a quarter.

Nothing could stop shipping

Goods receipt happens on a loading dock at pace. The system observes that process and never becomes a step that can block it.

Trust had to be earned

Finance would not allow automatic payment on day one. The design assumed a long shadow period where it predicted and was scored against the humans doing the job.

How It Works

Match at the line level, keep the running state on the purchase order rather than the invoice, and make every exception arrive with a specific reason.

01

Capture from wherever it arrives

Supplier email attachments, an EDI feed from the larger vendors, and scanned paper from the smaller ones, all landing in one queue with duplicate detection at intake.

02

Extract line items, not just totals

Totals agree by coincidence more often than people expect. Every line is extracted with its own confidence, so a short delivery hidden inside a correct looking total is still caught.

03

Resolve the vendor's vocabulary

The supplier's description never matches your SKU. A per vendor mapping is built from the first corrections a buyer makes, then reused, so each vendor gets easier over time.

04

Accumulate state on the order line

Received and invoiced quantities accumulate against the purchase order line. This is what makes partial deliveries and split invoices work in any order.

05

Apply tolerances finance controls

Price and quantity tolerances per vendor and category, edited in the application by the finance team, versioned so a past decision can be explained.

06

Fail loudly and specifically

An exception names the line and the disagreement: ordered 400, received 380, billed 400. A generic failure would just move the manual work somewhere else.

Events applied to one order linePO createdordered 400Invoice Abilled 200Receipt 1received 180Receipt 2received 200Invoice Bbilled 200PO line stateordered 400received 000invoiced 200 parkedreceived 180received 380invoiced 400 overinvoiced 400 > received 380exception raised on the lineArrival order does not matter. The line holds the truth.

The Hard Part

One order becomes three deliveries and two invoices, arriving in any order

The naive model attaches a match to an invoice: find the order, find the receipt, compare. It survives about a week of real data. A single purchase order becomes several partial deliveries and several invoices, they arrive out of sequence, and one invoice routinely spans two deliveries.

Moving the state onto the purchase order line fixed it. Each line carries an ordered quantity and accumulates received and invoiced quantities as events land against it. An invoice is then not matched in isolation, it is applied to the line and either fits within what remains or does not.

That reframing made ordering irrelevant. An invoice arriving before its goods receipt is no longer an error, it is simply a line where invoiced exceeds received, which parks until the receipt lands or ages into an exception.

It also made overbilling visible for the first time. When the cumulative invoiced quantity exceeds the cumulative received quantity on a line, that is a precise, defensible finding a buyer can take to a supplier, rather than a suspicion.

Vendor text to catalogue SKUAs the vendor writes itYour SKUScoreHEX BLT M8x40 ZPBOLT-M8-40-ZN0.96Washer flat 8mmWSHR-FLT-080.93M8 nut hex gr8.8NUT-M8-HEX0.88Assy kit 8mm (set)?0.51confirmA buyer confirms once. The mapping is reused for that vendor from then on.

Technical Decisions

What was chosen, and what was not

ChoiceWhyInstead of
State on the PO linePartial deliveries and split invoices make invoice centric matching unworkable. Accumulating received and invoiced quantities per line makes arrival order irrelevant.Matching each invoice against a receipt in isolation
Append only event logEvery receipt and invoice is an event applied to a line. A disputed payment can be replayed exactly as it was decided, which is what finance asks for first.Overwriting quantities in place on the order
Tolerances as versioned dataFinance edits tolerances themselves, and old decisions still explain themselves because the version in force at the time is retained.Thresholds in configuration files owned by engineering
Shadow mode as a first class featureIt predicted while humans decided, and the disagreements were the tuning signal. It is still used whenever a vendor changes format.A pilot on a subset of low value vendors
n8n for approval routingApproval chains change with staff and delegation. Operations edits the flow visually rather than requesting a deployment.Approval logic embedded in application code

Outcome

What changed for the business.

The routine invoices clear themselves

Anything that reconciles cleanly against the order and the receipt posts without a person, so attention goes to the ones that do not.

Duplicates stopped being paid

Near duplicate detection at intake catches the repeat submissions that previously slipped through as ordinary invoices.

Short deliveries are caught before payment

Matching at the line level means a partial delivery billed in full is held with the specific line named, rather than discovered at stock count.

Finance owns the rules

Tolerances are edited in the application and versioned, so a past decision still explains itself and no change needs a deployment.

In Hindsight

What I would do differently.

01

I modelled the match against the invoice first, which is the obvious reading of the problem and is wrong. Real time was lost before moving state onto the purchase order line, and every hard case afterwards became straightforward.

02

Shadow mode was added because finance insisted, not because I planned it. It turned out to be the most valuable feature in the build, and I would now write it first on anything that touches money.

03

The SKU mapping learns from buyer corrections, but I did not show buyers that their corrections were teaching it. Once the interface said so, correction rates rose sharply and coverage improved with them.

Get in Touch

Have this problem?
Let's talk