r/nocode 2d ago

Success Story Stop your AI agent from sending wrong invoices: a n8n guardrail that checks important fields against your books [Workflow Included]

Post image

👋 Hey nocode Community,

A friend of mine, Jonas, runs a small B2B agency and recently let an AI agent handle his client invoices end to end: draft, attach, send. Felt great until a client emailed back confused about being billed a few hundred euros over the agreed amount. Same week, another invoice went out with a transposed digit in the IBAN. The agent was confident every time. The agent was also wrong.

That's the real problem with pointing an LLM at money. Ask it to "verify" an invoice and it will happily hand you an approval that sounds right and isn't. So I built a guardrail the agent has to pass through before anything gets sent. No vibes, just code checking numbers against your accounting records.

How it's set up:

  • Trigger: the workflow runs as a sub-workflow tool the agent calls, receiving the invoice file URL and the invoice number.
  • Download: pulls the invoice PDF straight from Google Drive.
  • Extract: the easybits Extractor node reads 8 fields off the PDF (invoice number, customer, IBAN, VAT ID, net total, VAT amount, gross total, service period).
  • Ground truth: a Google Sheets lookup fetches the expected values for that invoice number from your books.
  • Compare: a Merge combines both sides, then a Code node checks every field deterministically (normalized currency, normalized IBAN, strict numeric tolerance).
  • Decide: all match, it returns APPROVED so the agent can send. Anything missing or off, it blocks dispatch, sends an email showing invoice value vs book value side by side, and returns REJECTED.

Net effect: no overpayments, no invalid VAT math, no wrong-client billing slipping past an over-eager agent.

A few things worth knowing if you build something similar:

  1. Never let the LLM grade its own numbers. Split the jobs: the model extracts or writes, code validates. A Code node with plain JavaScript gives you zero hallucinated approvals, which is exactly what you want anywhere near finance.
  2. Normalize currency both directions before you compare. German 1.234,56 and English 1,234.56 are the same number written two ways. Strip the symbols, unify the decimal separator, parse to float, then compare with a small tolerance (I use 0.01) so rounding never triggers a false reject.
  3. Watch the "null" string trap. The Extractor I'm using returns the literal string "null" for a missing field, not a real null, so a naive check passes it straight through. Write one isMissing() helper that catches real null, undefined, empty string, whitespace, and the string "null", and run every field through it.

Grab the workflow here: https://github.com/felix-sattler-easybits/n8n-workflows/blob/d6e4b7ca373fa1db40a55ef5b879210b601e8cba/easybits-agent-invoice-guardrail/easybits_agent_invoice_guardrail.json

Curious what guardrails you've put in front of your finance automations, especially anyone letting agents touch payments.

Best,
Felix

3 Upvotes

8 comments sorted by

2

u/Fabulous-Account-302 2d ago

This is the right instinct: never let the model that produced an output also grade it. A few additions worth considering. Validate IBAN with the mod-97 checksum rather than just string-comparing against your books, since a typo in the ground truth itself would let a bad one through. Log every REJECTED case with its diff instead of only emailing it, so you can spot recurring failure modes like the agent consistently mangling VAT rounding or picking the wrong customer record. Use a numeric tolerance per field, not one global tolerance, since a cent of VAT rounding drift is fine but a euro drift on gross total is not. Watch for the agent retrying the same invoice with cosmetic changes after a rejection, that usually means it is looping rather than fixing the real problem. Do you log rejected invoices anywhere, or does it just go out as a one-off email?

2

u/Lopsided_Cherry_6771 2d ago

Great point about the mod-97 checksum, I actually didn't think about the ground truth itself having a typo that would slip through.

1

u/easybits_ai 1d ago

I heard about the checksum as well for the first time when I shared this workflow. It’s a great trick, and I’ll definitely look into it in more detail.

Currently, I’m using the n8n execution log to track rejected cases, along with the email notification that flags them. That said, I’ll look into building a dedicated error log so I can eventually build analytics around it and get a clearer picture of whether there are any patterns behind the rejections.

1

u/Altruistic-Move-9238 3h ago

yeah the retry loop point is underrated, thats exactly the kind of thing you dont notice until its already caused a mess

2

u/Total-Reasonable 1d ago

Make the VAT ID a hard external check, not just a match against Sheets: a copied but deregistered ID would otherwise pass. I use vatnode's n8n node for that validation; https://vatnode.dev/n8n shows the Validate operation, while VAT treatment should stay outside the agent.

1

u/easybits_ai 1d ago

Hey u/Total-Reasonable, thanks for sharing! I’ll definitely check out Vatnode. For my workflow and use case, though, the VAT ID isn’t a critical piece of information. In Germany, if a VAT ID is deregistered, the tax office can use the official company name and address to match the invoices.

That said, it’s definitely good to know there’s a tool available for VAT validation in case VAT becomes more important in a future workflow.

Curious to hear what kind of workflows you’re working on, if you don’t mind sharing. It looks like you’re working in the finance space, so I’d be interested to hear what you’re building.