This website uses cookies

Read our Privacy policy and Terms of use for more information.

In 2022, OpenEnvoy audited half a billion dollars of its customers' invoices and found nearly one in ten were duplicates: the same invoice arriving twice, on its way to being paid twice. That was $42 million flagged before it left the building, which is a lot of money to ride on the absence of an if-statement. APQC's benchmark puts typical duplicate rates well below that — the vendor sells duplicate detection, after all — but "lower" still means real money paid twice, quietly, by automations doing exactly what they were built to do: process invoices, not wonder whether they'd seen this one before.

Today: three checks that make your billing automation suspicious in the way a good bookkeeper is suspicious.

What's actually going on

Duplicates don't happen because anyone is careless. They happen because invoices travel redundantly: a vendor emails the invoice, then uploads it to your portal to be safe. Someone forwards the email thread, and the intake automation dutifully ingests the forward. Your own workflow times out mid-run, retries, and processes the attachment twice. Every one of these is a system working exactly as designed.

The problem is that "as designed" includes no memory. A billing automation checks that the invoice is well-formed — the right fields and a plausible amount. It does not check that the invoice is new, because nobody told it that "new" was a requirement. A human bookkeeper gets an itchy feeling seeing the same €4,300 from the same vendor twice in one week. Your workflow feels like nothing. It wasn't built to.

And this is before ordinary errors: manual invoice processing runs a roughly 3.6% data-entry error rate, per IOFM — wrong amount, wrong quantity, wrong recipient. So the range on an unchecked billing automation runs from a few percent to, in that vendor's worst-case sample, one in ten: some real fraction of what arrives shouldn't be paid as-is, and the workflow will correctly pay all of it. That's the whole problem — correctly is doing a lot of work in that sentence.

Check 1: Give it memory

Before your workflow pays (or approves), it looks up the invoice in a ledger of everything it has already seen. The ledger can be a Google Sheet, an Airtable base, or n8n's built-in Data Table — anything with a "search rows" action.

The fingerprint to store and search: vendor name + invoice number + amount.

  • Seen before → stop. Route to a human with both copies attached.

  • New → add it to the ledger, then proceed.

In Zapier, this is a "Lookup Spreadsheet Row" step before the payment step; in n8n, it's a Data Table "Get row(s)" step. Ten minutes of work, and the single highest-ROI change I know of in billing automation.

Two details will bite you if you skip them. Normalize before you fingerprint — vendors are creative (INV-001, INV 001, inv1), and un-normalized dedup is dedup theater. And mind the race: two copies of the same invoice arriving in one batch will both pass a lookup before either writes. Sheets and Airtable have no unique-column constraint, so in n8n, use the Data Table's upsert keyed on the fingerprint and dedupe each batch before inserting; once real money moves through this, graduate to an actual database with a unique index. What you're building is an idempotency key in a spreadsheet-shaped costume. While you're there, fuzzy-match vendor+amount within a 14-day window too — a resent invoice often gets a fresh invoice number, and exact-match dedup waves it through.

Check 2: Make it match something

A duplicate is one failure mode; a wrong invoice is another. The classic defence is the three-way match — invoice vs. purchase order vs. what was actually delivered. The lightweight version of any workflow can do: the invoice amount must match what you agreed to pay, within a tolerance.

Keep a simple table of vendors and agreed amounts (or PO numbers and totals). The workflow compares: within 2% → proceed; outside it, or vendor not in the table at all → human. You have just converted "we pay what arrives" into "we pay what we ordered," which is a surprisingly large upgrade for one lookup step.

Check 3: Teach it suspicion

Three rules, in descending order of importance:

  1. Changed bank details → always a human. No tolerance, no exceptions. Redirecting payment by "correcting" account numbers on an otherwise-real invoice is how actual invoice fraud works, and an automation that auto-applies the correction is the accomplice of the year.

  2. First invoice from a new vendor → always a human. Once they're established, the machine can take over.

  3. Amount more than ~1.5× that vendor's usual → flag it. Usually legitimate. The point of the flag isn't to catch fraud; it's that someone looks before money moves.

Make it trustworthy: prove your checks work

Here's the part everyone skips, and the part this newsletter exists for: a check you've never seen fail is a check you have no evidence that works.

So feed it a duplicate on purpose. Take an invoice your ledger already knows (if you built the ledger this morning, backfill last month's paid invoices into it first, or your test will "fail" for the boring reason that the ledger has never heard of the invoice) and run it through the intake again, with the payment step pointed at a sandbox or the amount set to €0.01. If it sails through, your dedup check is decorative — better to learn that today than in an audit. Do this once a quarter. It's a fire drill for your workflow: mildly annoying and much cheaper than the fire.

The takeaway

An automation does what it was built to do and nothing else. "Paid twice" is not an error to a workflow that was never told "twice" exists. The fix isn't intelligence — it's memory, context, and suspicion, in that order, and the first one costs ten minutes.

--- The If Statement — the logic everything rides on. If your billing workflow has memory already, forward this to someone whose workflow doesn't.

Keep Reading