n8n for Invoice Processing: OCR, AI Extraction and Approval
Why invoice processing is the perfect n8n first project
Every finance team has the same folder problem. Invoices arrive as PDFs attached to emails, as photos of paper receipts dropped into a shared drive, or as exports from a vendor portal. Someone opens each one, reads the vendor name, the invoice number, the line items, the total, and the due date, then retypes all of it into an accounting tool or a spreadsheet. It is slow, it is boring, and it is exactly the kind of task that produces typos at 4pm on a Friday.
This is also why invoice processing is one of the best first automations to build in n8n. It touches almost every pattern you need to know as an automation builder: pulling files from an inbox, running OCR on documents, using an LLM to turn messy text into structured data, validating that data against business rules, and routing the result to a human when something looks off. Get this one workflow right and you have a template for dozens of other document-heavy processes — expense reports, purchase orders, delivery receipts, insurance claims.
This article walks through building a real invoice processing pipeline in n8n: watching for new invoices, extracting text with OCR, using an AI model to pull out structured fields, validating the numbers, and routing approvals based on amount and confidence. We will also cover the failure modes that trip people up in production, because a demo that works on three clean PDFs is very different from a system that survives six months of real vendor invoices.
Mapping the invoice workflow before touching n8n
Before opening the n8n editor, write down the actual process on paper or in a doc. Most teams skip this step and pay for it later, because invoice processing has more edge cases than it looks like at first glance.
At minimum, answer these questions:
- Where do invoices arrive? A shared mailbox, a specific email alias, a Google Drive folder, an upload form, or a vendor API. Each source needs a different trigger node.
- What formats show up? Native PDFs, scanned PDFs, JPEG photos of paper invoices, and occasionally Excel or Word attachments. OCR quality differs wildly across these.
- What fields actually matter to your business? Common ones are vendor name, invoice number, invoice date, due date, line items, subtotal, tax, total, currency, and PO number. Do not extract everything possible — extract what downstream systems need.
- What counts as "needs a human"? Usually: total above a threshold, a new vendor not seen before, a mismatch between the extracted total and the sum of line items, or low OCR confidence.
- Where does approved data go? A spreadsheet, an accounting system like Xero or QuickBooks, a database, or straight into a payment run.
Once this is written down, the n8n workflow becomes a translation exercise rather than a design exercise. That is the order you want: decide the process first, then build the automation.
Workflow architecture: five stages in n8n
A production-grade invoice pipeline in n8n breaks cleanly into five stages, each of which can be a separate section of nodes (or even a separate sub-workflow called with Execute Workflow):
- Ingestion — trigger on new invoice arrival and normalize the file into a common format.
- OCR / text extraction — turn the PDF or image into raw text or structured text blocks.
- AI extraction — send the raw text to an LLM with a strict schema and get back structured JSON.
- Validation — check the JSON against business rules (totals add up, required fields present, vendor is known).
- Approval routing — auto-approve low-risk invoices, send everything else to a human via Slack or email, and log the outcome.
Keeping these as distinct stages, rather than one giant chain of twenty nodes, matters because you will want to debug and re-run individual stages. If OCR was fine but the AI extraction hallucinated a field, you should be able to re-run just stage 3 against the stored OCR output instead of re-processing the whole PDF.
Stage 1: ingestion — catching invoices reliably
The most common ingestion trigger is an Email Trigger (IMAP) node watching a dedicated invoices@ mailbox, or a Gmail Trigger node filtered by label. Configure the trigger to poll on a reasonable interval — every 5 minutes is typical for invoice processing, since these are rarely time-critical to the second.
Immediately after the trigger, add a filter node to drop anything that clearly isn't an invoice: no attachment, attachment type not in [pdf, jpg, jpeg, png], or sender not in an allowed list if you're strict about that. This keeps garbage out of your OCR and AI calls, which is where you pay real money per request.
A simple normalization step here saves headaches later:
// Function node: normalize incoming attachments
const items = [];
for (const item of $input.all()) {
const binary = item.binary;
for (const key of Object.keys(binary || {})) {
const file = binary[key];
items.push({
json: {
fileName: file.fileName,
mimeType: file.mimeType,
source: 'email',
receivedAt: new Date().toISOString(),
},
binary: { data: file },
});
}
}
return items;This turns a multi-attachment email into one n8n item per invoice file, which makes every downstream node operate on a predictable one-item-per-invoice shape.
If invoices also arrive by upload, add a second entry point using a Webhook node backed by a simple upload form, or a Google Drive Trigger watching a folder. Merge both paths into the same normalized shape before OCR so the rest of the workflow doesn't care where the invoice came from.
Stage 2: OCR — turning documents into text
For scanned PDFs and photographed receipts, you need real OCR, not just PDF text extraction. n8n does not ship a built-in OCR engine, so this stage typically calls an external service via an HTTP Request node. Common choices:
- A cloud OCR API (document AI style services that return text plus bounding boxes and confidence scores).
- A self-hosted OCR engine like Tesseract behind a small HTTP wrapper, which keeps documents on your own infrastructure — important if invoices contain vendor bank details.
- A multimodal LLM call that reads the image directly and returns text, skipping a separate OCR step entirely.
For native, text-based PDFs (most B2B invoices generated by accounting software), you can often skip OCR and use a Code node with a PDF-parsing library, or n8n's built-in PDF extraction, to pull raw text directly. This is faster and cheaper than OCR, so branch on file type early:
// Function node: decide OCR path
const mime = $json.mimeType;
const isImage = mime === 'image/jpeg' || mime === 'image/png';
return [{ json: { ...$json, needsOcr: isImage || $json.isScannedPdf === true } }];Feed the needsOcr flag into an IF node. True routes to your OCR HTTP call; false routes to direct PDF text extraction. Both paths converge on the same output shape: { rawText: string, confidence: number }.
Store the raw OCR/extraction output somewhere durable — a database table or even a Google Sheet row — before moving to AI extraction. This gives you an audit trail and a replay point if the AI step needs tuning later.
Stage 3: AI extraction — from raw text to structured JSON
This is where an LLM earns its keep. Raw OCR text is messy: line breaks in odd places, tables that lost their columns, currency symbols mixed with numbers. Asking a general-purpose regex to parse that reliably across dozens of vendor formats is a losing game. An LLM with a well-specified schema handles it far better.
Use n8n's AI Agent node or a plain HTTP Request node against your LLM provider, with a system prompt that is strict about output format. The key discipline here is forcing structured JSON output and refusing to accept free text.
Example prompt structure for the extraction call:
You are an invoice data extraction system. Given the raw OCR text of an
invoice, extract the following fields and return ONLY valid JSON matching
this schema. Do not include explanations.
{
"vendor_name": string,
"invoice_number": string,
"invoice_date": string (YYYY-MM-DD),
"due_date": string (YYYY-MM-DD) or null,
"currency": string (ISO 4217 code),
"line_items": [
{ "description": string, "quantity": number, "unit_price": number, "amount": number }
],
"subtotal": number,
"tax": number,
"total": number,
"confidence": number (0-1, your own estimate of extraction accuracy)
}
If a field cannot be found, use null. Never invent numbers that are not
present in the text.That last line matters more than it looks. Invoice extraction is a domain where hallucinated numbers are actively dangerous — an LLM that "helpfully" fills in a plausible total when the OCR text was garbled can cause a wrong payment. Always instruct the model to return null rather than guess, and always cross-check the returned total against a computed sum in the validation stage.
In the n8n AI Agent node, set the output parser to expect JSON and add a Structured Output Parser so malformed responses fail the node instead of silently passing bad data downstream. Wrap the AI call in an Error Trigger or Retry On Fail setting (2-3 retries with a short backoff) since LLM APIs occasionally return transient errors.
// Function node: parse and guard AI output
let data;
try {
data = JSON.parse($json.output);
} catch (e) {
throw new Error('AI extraction returned invalid JSON, needs manual review');
}
const requiredFields = ['vendor_name', 'invoice_number', 'total', 'currency'];
const missing = requiredFields.filter((f) => !data[f]);
if (missing.length) {
data._needsReview = true;
data._reviewReason = `Missing fields: ${missing.join(', ')}`;
}
return [{ json: data }];Stage 4: validation — catching what the AI got wrong
Never trust extracted data blindly, no matter how good the model's prompt is. Validation is where you turn "the AI said so" into "the numbers actually check out." Build this as its own set of nodes so it is easy to extend as you discover new failure patterns.
Core checks worth implementing:
- Line item math: sum
quantity * unit_pricefor every line item and compare tosubtotal. Flag anything off by more than a cent or two (rounding tolerance). - Total math:
subtotal + taxshould equaltotalwithin tolerance. - Duplicate detection: check
invoice_numberplusvendor_nameagainst previously processed invoices to catch accidental double-submission or, worse, duplicate payment attempts. - Vendor allow-list: if the vendor is not in your known-vendors list, route for extra scrutiny — this catches both new legitimate vendors and invoice fraud attempts.
- Confidence threshold: if the AI's self-reported confidence, or the OCR confidence score, is below a set bar (say 0.85), force manual review regardless of what the numbers say.
// Function node: validation rules
const d = $json;
const errors = [];
const lineSum = (d.line_items || []).reduce((s, li) => s + (li.amount || 0), 0);
if (Math.abs(lineSum - d.subtotal) > 0.02) {
errors.push(`Line items sum to ${lineSum} but subtotal is ${d.subtotal}`);
}
const expectedTotal = (d.subtotal || 0) + (d.tax || 0);
if (Math.abs(expectedTotal - d.total) > 0.02) {
errors.push(`Subtotal + tax (${expectedTotal}) does not match total (${d.total})`);
}
if ((d.confidence || 0) < 0.85) {
errors.push(`Low extraction confidence: ${d.confidence}`);
}
return [{ json: { ...d, validationErrors: errors, isValid: errors.length === 0 } }];This single node is doing more risk reduction than the entire AI extraction step. Treat it as the non-negotiable gate between "AI extracted this" and "we act on this."
Stage 5: approval routing and human-in-the-loop
Now decide who acts on the invoice. A reasonable default policy:
- Auto-approve: valid data, known vendor, total below a set threshold (say $500), no duplicate flag. Push straight to your accounting system or a "ready to pay" sheet.
- Manager approval: valid data but total above threshold, or a new vendor. Send to Slack or email with a summary and an approve/reject action.
- Manual review: any validation errors, low confidence, or duplicate flag. Route to a review queue, never auto-process.
In n8n, use a Switch node keyed on a computed routingDecision field, then branch to a Slack node (using interactive buttons via a message action, or a simple approve link that hits a webhook), an Email node, or a direct write to your accounting API/database for auto-approved items.
// Function node: compute routing decision
const d = $json;
let decision = 'auto_approve';
if (!d.isValid) {
decision = 'manual_review';
} else if (d.total > 500 || d._isNewVendor) {
decision = 'manager_approval';
}
return [{ json: { ...d, routingDecision: decision } }];For manager approval via Slack, send a message with the extracted summary (vendor, amount, due date, a link to the original PDF) and two buttons wired to a Webhook node that receives the click and resumes the workflow — n8n's Wait node with a webhook resume is the standard pattern here. This avoids polling and keeps the workflow instance alive until a human actually responds.
Whatever the outcome, always write a row to a log — vendor, amount, decision, who approved it, timestamp. This single log becomes your audit trail when someone asks "why did this invoice get paid automatically" six months from now, and it is also the dataset you'll use to tighten your confidence thresholds over time.
Handling failure modes that don't show up in a demo
A workflow that works on ten test invoices will meet reality once real vendors start sending real documents. A few failure modes worth designing for up front:
- Multi-page invoices: OCR and AI extraction both need to handle invoices where line items span multiple pages. Concatenate OCR text across pages before sending to the AI step, and be explicit in the prompt that line items may continue across page breaks.
- Non-English invoices: if you work with international vendors, either detect language and route to a locale-aware OCR/AI path, or explicitly instruct the AI extraction prompt to handle the languages you expect and to normalize dates and number formats (many countries use comma as a decimal separator).
- Currency mismatches: extracting "100" without confirming the currency symbol or ISO code has bitten more than one team. Always extract currency as its own field and validate it against a known list.
- Silent AI drift: LLM behavior can shift slightly between model versions. Keep a small fixed set of golden test invoices with known-correct extracted values, and periodically replay them through the workflow to catch regressions before they hit production data.
- Rate limits and cost: OCR and LLM calls cost money per document. Add a Wait node or use n8n's built-in rate-limiting options on the HTTP Request node when processing invoices in bulk (e.g., backfilling a quarter's worth of historical invoices), and cache OCR results so you never re-run OCR on the same file twice.
- PDF quality: some scanned invoices are simply unreadable. Set a hard floor on OCR confidence below which the workflow skips AI extraction entirely and goes straight to manual review — don't waste an LLM call on text that was garbage to begin with.
Building in these guardrails from the start is cheaper than retrofitting them after the first wrong payment or the first angry vendor call.
Testing your invoice pipeline before going live
Before pointing this workflow at a live mailbox, build a test harness inside n8n itself:
- Collect 15-20 real (or realistic) sample invoices covering your actual vendor mix: clean PDFs, scanned PDFs, a photo, a multi-page one, and at least one deliberately malformed file.
- Run each through the workflow manually using n8n's Pin Data feature on the trigger node, so you can re-run downstream stages without re-triggering email polling.
- Record expected output for each sample — the fields you'd expect a human to extract — and compare against what the workflow actually produces.
- Track accuracy per stage: OCR text quality, AI field accuracy, validation catch rate, and final routing correctness. This tells you which stage to improve first rather than guessing.
- Only after this test set passes consistently should you connect the live trigger and start with a lower auto-approve threshold, raising it as confidence grows.
This test-first approach also gives you a regression suite. Every time you tweak the extraction prompt or swap an OCR provider, re-run the same sample set and confirm nothing broke.
Closing thoughts
Invoice processing is a deceptively rich automation problem. The happy path — clean PDF, one page, one currency, known vendor — is easy to build and easy to demo. The real value of an n8n workflow like this comes from how it handles everything else: the scanned receipt with a coffee stain, the vendor who changed their invoice template, the total that doesn't quite add up. Build the validation and approval-routing stages with the same care as the AI extraction stage, because that is where trust in the system is actually earned.
Once this pattern clicks, it generalizes fast. The same five-stage shape — ingest, extract text, structure with AI, validate, route for approval — powers expense report automation, contract review triage, and support ticket classification with only the prompts and validation rules changing. If you want a guided, hands-on walkthrough of building agentic workflows like this one in n8n, including deeper patterns for tool use, memory, and multi-step approval chains, check out the n8n AI Agent Tutorial course on teachyou.ai.
BootcampA 30-day guided bootcamp: build, harden and ship a production autonomous agent from scratch.
AI AgentsUnderstand how AI agents really work: the loop, the tools, the memory, and why most agent projects fail.