
n8n Invoice Approval Automation: The AP Workflow Playbook
TL;DR: Invoice approval is accounts-payable, not refunds. The pipeline is email-to-OCR-to-validation-to-approval-to-ERP, and every stage has a specific failure mode: wrong-amount extraction, duplicate invoices, PO mismatches, fraud patterns, stale vendor data. This post covers the three ways to build it in n8n — a lightweight extract-and-route pattern, a database-driven exception queue, and a managed HITL approach — with concrete configurations for the checks that actually matter and the audit trail AP needs.
Accounts payable looks easy until you run it. An invoice arrives. Someone extracts the fields. Someone checks it against a purchase order. Someone routes it to the right approver. Someone schedules the payment. In a fifty-person company, that is four people's time, or one person's full attention on nothing else. Most of it is not decision work — it is transcription and lookup.
This is the work n8n was built to eat. An inbound-email trigger, OCR on the PDF, a few validation steps, a routing node, and a posting step to the ERP. You can put the skeleton together in an afternoon and it will process real invoices. The problem is not the skeleton. The problem is the exceptions: the vendor who changed bank details and did not tell you, the duplicate that slipped through because the file name was slightly different, the $47,000 charge that should have gone through VP approval but did not because the extraction grabbed the wrong line item.
An invoice approval workflow is not "AI approves everything under $500." It is a carefully-layered pipeline of deterministic checks plus a human approval gate sized to the risk of each invoice. This post walks through how to build it in n8n — three patterns at three levels of sophistication — plus the specific checks that keep it honest.
The shape of a real AP pipeline
Before patterns, the pipeline every AP workflow goes through.
1. Inbound. Invoices arrive via email, a shared mailbox, a vendor portal, or a paper scan. First step is to corral them into n8n — usually an email trigger on a dedicated AP mailbox.
2. Extraction. The PDF (or image) becomes structured data. Vendor name, invoice number, dates, line items, totals, bank details. OCR plus an LLM extraction step is the 2026 default; GPT-4o and Gemini do this reliably on well-formed invoices and badly on scans.
3. Validation. Deterministic checks that do not need a human. Is this a duplicate (same vendor, same invoice number)? Does the total match the sum of line items? Do the numbers parse? Is the vendor in your system? If there is a purchase order, does the invoice match it?
4. Routing. Which human needs to approve this? Amount thresholds ($2,000 goes to the team lead, $20,000 adds the finance manager, $100,000 adds the VP). Department routing (IT invoices go to IT lead, not to marketing). Urgency routing (late invoices go to the top of the queue).
5. Approval. A human sees the extracted data, the validation results, the original PDF, and clicks approve / reject / request clarification. They can correct extraction errors before approving.
6. Posting. The approved invoice goes to the ERP (QuickBooks, Xero, NetSuite, SAP). Payment terms get set. A notification goes back to the vendor. An audit row gets written.
Every pattern below does these six stages. The difference is how much of each stage you wire yourself versus offload to a platform, and how cleanly each one handles exceptions.
Pattern 1: Lightweight extract-and-route
The minimum viable AP workflow. One n8n workflow, email trigger to ERP post, with a single approval step in the middle via native Send and Wait for Response. Right for small volumes (say under 50 invoices a month) or a single approver.
Shape
[Gmail Trigger: new invoice in [email protected]]
|
[IF: has PDF attachment?] (skip non-invoice emails)
|
[HTTP Request: OCR the PDF] (Mistral OCR, Google Document AI, or Tesseract)
|
[AI Agent: extract structured fields]
Output: {
vendor_name, vendor_id, invoice_number, invoice_date, due_date,
line_items: [...], subtotal, tax, total, currency,
bank_details: { iban, account_number, routing }
}
|
[Validation step: Code node or n8n Function]
Checks: sum(line_items) == subtotal, subtotal + tax == total,
required fields present
|
[Postgres / Airtable: check for duplicate (vendor_id + invoice_number)]
|
[IF: duplicate] -> [Notify AP lead, stop]
|
[Switch: route by amount]
|-- total < $2,000 -> approver: team_lead
|-- total < $20,000 -> approver: finance_manager
|-- total >= $20,000 -> approver: vp
|
[Slack / Email: Send and Wait for Response — Custom Form]
Fields:
- vendor (text, prefilled, editable)
- invoice_number (text, prefilled, editable)
- total (number, prefilled, editable)
- decision (select: approve / reject / request_clarification)
- notes (textarea, optional)
Limit Wait Time: 48h
|
[Switch: on decision]
|-- approve -> [HTTP Request: post to ERP (QuickBooks / Xero / NetSuite)]
| -> [Email vendor: "invoice received, scheduled for payment"]
| -> [Audit log: write row]
|-- reject -> [Email vendor: "invoice could not be processed, reason: ..."]
| -> [Audit log: write row]
|-- request_clarification -> [Email vendor with questions]
-> [Postgres: mark invoice pending_clarification]
|
[Timeout branch]
-> [Slack: remind approver]
-> [If second timeout: escalate to VP or backup]
What makes this work
Editable fields in the approval step. Extraction gets the right fields wrong regularly — the total is fine but the invoice number is missing a digit, or the vendor name is abbreviated. The approver sees the proposed data, corrects what is wrong, and approves. The corrected values are what get posted to the ERP. Without editable fields, every small extraction error becomes a full reject → re-run cycle.
Duplicate detection before routing. A vendor who emails the same invoice twice (or emails from two different addresses) is the most common false-positive source. Check for duplicates on (vendor_id, invoice_number) — not on file hash, which changes when the PDF is re-saved. If it is a duplicate, stop cold and notify AP manually.
Amount-based routing. Three tiers is usually enough. The thresholds depend on your business; the principle is that a $200 office-supplies invoice does not need the same review depth as a $50,000 vendor renewal. Getting the thresholds right is a policy call; getting the routing enforced is n8n's job.
Where this pattern stays honest
- One to three approvers, known by name.
- Clear vendor master data (you know who your vendors are, bank details are stable).
- Under 50 invoices per month.
- No purchase-order matching (either you do not use POs, or you match manually).
Where it breaks down
No exception queue. An invoice that fails validation (sum mismatch, unknown vendor, extraction incomplete) has nowhere to go except "AP manually handles it." In volume, that queue becomes the real bottleneck and it is invisible to the workflow.
No PO matching. Most real AP at scale runs two-way matching (invoice vs. PO) or three-way matching (invoice vs. PO vs. receipt). This pattern skips it entirely; you add it as another validation step but the wiring grows quickly.
Audit trail is per-execution. The n8n execution log records what happened in each run but reconstructing "show me every invoice over $10k approved by Alex in Q2" means writing your own logging in the audit-log step and querying it manually.
Fraud signals are absent. Unusual vendor, unusual amount, new bank details, weekend submission — none of these are checked. In a small company this is fine. In a company where AP is a target, this is a gap.
Verdict. Ship this on day one to eat 70% of AP volume. Plan Pattern 2 for when you need exception queues, PO matching, or richer audit.
If Pattern 1 is humming but the exceptions are where you are losing time, join the early-access list for Humangent — Pattern 3 is a design direction, shaped by beta users handling exactly this gap.
Pattern 2: Database-driven with an exception queue
Pattern 1 treats every invoice as a linear execution. Pattern 2 treats invoices as records with states, stored in a database, moving through a state machine: received → extracted → validated → routed → approved → posted. An exception queue catches anything that fails a step and surfaces it to AP.
Schema
CREATE TABLE invoices (
id UUID PRIMARY KEY,
vendor_id TEXT,
invoice_number TEXT,
invoice_date DATE,
due_date DATE,
total NUMERIC(12,2),
currency TEXT,
bank_details JSONB,
status TEXT DEFAULT 'received',
-- 'received' | 'extracted' | 'validated' | 'routed' | 'approved'
-- | 'rejected' | 'posted' | 'exception'
exception_reason TEXT,
assigned_approver TEXT,
pdf_url TEXT,
extracted_data JSONB,
po_number TEXT,
po_match_status TEXT,
-- 'none' | '2-way-match' | '3-way-match' | 'mismatch'
fraud_signals JSONB,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE invoice_events (
id UUID PRIMARY KEY,
invoice_id UUID REFERENCES invoices(id),
event_type TEXT,
-- 'received' | 'extracted' | 'validated' | 'routed'
-- | 'approved' | 'rejected' | 'posted' | 'exception_raised'
actor TEXT, -- 'system' | '[email protected]'
data JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
Workflow topology
Three independent workflows work together.
Workflow A: Ingest and extract.
[Email Trigger]
-> INSERT invoice row (status='received')
-> OCR + LLM extraction
-> UPDATE status='extracted', extracted_data=...
-> Validation step
-> IF validation fails:
UPDATE status='exception', exception_reason=...
INSERT invoice_event (exception_raised)
Notify AP lead
ELSE:
UPDATE status='validated'
Workflow B: PO matching and routing (runs on schedule or DB trigger).
[Schedule Trigger: every 2 min]
-> SELECT invoices WHERE status='validated'
-> For each:
Look up PO by vendor + PO number
Compare invoice lines vs PO lines
IF match within tolerance:
UPDATE po_match_status='2-way-match' (or '3-way-match' with receipts)
Determine approver by amount
UPDATE status='routed', assigned_approver=...
Send approval request (Send-and-Wait or HTTP Request to Humangent)
ELSE:
UPDATE status='exception', exception_reason='PO mismatch'
Raise exception
Workflow C: Approval response handler.
[Webhook: /invoice/:id/decision]
-> Parse decision (approve / reject / request_clarification)
-> INSERT invoice_event (approved or rejected) with actor=approver
-> IF approve:
UPDATE status='approved'
Post to ERP
UPDATE status='posted'
Email vendor
IF reject:
UPDATE status='rejected'
Email vendor with reason
IF request_clarification:
Email vendor with questions
UPDATE status='exception', exception_reason='awaiting clarification'
The exception queue is the key
Any invoice that fails any step — validation, PO match, duplicate check, fraud signal — lands in an exception queue (invoices with status='exception'). AP opens the queue, sees the reason, and takes action: correct the data, chase the vendor, unblock the invoice.
A small UI (an n8n Form workflow, a Retool dashboard, even a read-only Postgres view piped into Airtable) lets AP work the queue. The important part is that exceptions are visible as work, not invisible as silently-stuck executions.
What this pattern gives you
PO matching is first-class. Two-way matching (invoice + PO) and three-way matching (invoice + PO + receipt) are built into the state machine. Invoices without POs can still flow if your policy allows it; invoices with POs have to match before they route.
Queryable audit. Every event on every invoice is a row in invoice_events. "Show me every invoice over $10k approved by Alex in Q2" is a SQL query. "How long does the average invoice sit between validated and approved?" is a SQL query. You get reporting for free.
Real fraud checks. Add a fraud-signal step before routing: new bank details for an existing vendor, first-time vendor with a high amount, submission outside business hours, amount near a routing threshold (just under $20k). Flag any of these as an exception; do not silently auto-approve around them.
Restart safety. If n8n restarts mid-pipeline, the invoice state is in the database. A recovery workflow re-runs the next step for any invoices stuck in transient states. Nothing is lost.
What it costs
You are maintaining a small application. Schema, three workflows, an exception queue UI, a reviewer form. Shipping the first version takes a week; maintaining it is a few hours a month.
Polling latency. Workflow B runs on a schedule. A 2-minute polling interval means up to 2 minutes of delay between validation and routing. Acceptable for AP, where next-day is often fine, but make it deliberate.
Reviewer UI is still DIY. Unless you use a tool for the approval step itself, the approver's experience depends on what you build. Most teams use Slack Send-and-Wait or an Airtable form — both work, neither feels great at scale.
Verdict. The right level of sophistication once you cross ~200 invoices/month or need PO matching. Costs a week to build, saves that week every month after.
Pattern 3: Managed HITL inbox for the approval step
Patterns 1 and 2 both put the approval interface on you. Whether Slack Send-and-Wait or a Retool form, the reviewer experience is built from data-entry primitives. For AP specifically, where the approver needs to see the PDF, the extracted data, the PO comparison, and prior events on this vendor — in one place — that interface is non-trivial to build well.
Pattern 3 keeps Patterns 1 or 2's pipeline shape but delegates the approval step to a managed human-in-the-loop inbox. The workflow still ingests, extracts, validates, matches, and posts to the ERP. The "human clicks approve" part — with all the context the approver needs — lives on the platform.
This is a design direction for Humangent. Humangent is in private beta. The description below is how I am thinking about AP-specific approval surfaces, not a list of shipped features.
Shape of the approval step
# (after extraction + validation + PO match, approver determined)
HTTP Request: POST to Humangent
Body:
{
"callback_url": "{{ $node['Wait for Webhook'].webhookUrl }}",
"title": "Invoice {{ $json.invoice_number }} — {{ $json.vendor_name }} (${{ $json.total }})",
"body": "Full invoice details",
"attachments": [ { "url": "{{ $json.pdf_url }}", "type": "pdf" } ],
"fields": [
{ "name": "vendor_name", "value": "...", "editable": true },
{ "name": "invoice_number", "value": "...", "editable": true },
{ "name": "total", "value": "...", "editable": true, "type": "money" },
{ "name": "bank_details", "value": "...", "editable": true, "sensitive": true }
],
"context": {
"po_match": "...",
"fraud_signals": [ ... ],
"previous_invoices": "link to vendor history"
},
"actions": ["approve", "reject", "request_clarification"],
"assignee": "team-finance",
"escalate_to": "finance-lead",
"timeout": "48h"
}
Wait for Webhook
Switch on decision -> approve / reject / request_clarification
What the design is aiming at
- PDF preview alongside structured data. Reviewer sees the source invoice side-by-side with what was extracted. Corrections happen in the structured fields; the PDF stays available for verification.
- PO comparison inline. If there is a PO, the reviewer sees the line-by-line comparison with variances highlighted. No flipping between systems.
- Vendor history in context. "This vendor has 47 prior approved invoices, average $3,200. This one is $28,000." Makes the unusual instantly visible.
- Fraud signals as tags. "New bank details" / "submission 11:47pm Saturday" / "above threshold for vendor's historical pattern" — visible as warning tags, not hidden in the raw data.
- Reviewer picks their channel. Finance lead prefers email. CFO prefers Slack. Same workflow, different notification deliveries.
- Audit trail is built-in. Who approved, what they edited, what warnings they saw and dismissed, when — logged as part of the platform, not something you write yourself.
Honest status
Humangent is in private beta. AP-specific features (PDF attachments, vendor history views, PO comparison layouts) are design directions that the beta is shaping. If you need a shipped AP product today, Pattern 1 or 2 plus the existing Slack / Teams / Airtable approval UIs is the honest answer. If you want the approval-surface design to be informed by actual AP workflows, early access is the direct way in.
Validation checks that actually matter
Every AP pipeline is defined by its validations. The default n8n build has almost none. These are the checks that pay for themselves in prevented fraud and extraction errors.
Structural.
- Line items sum to the subtotal.
- Subtotal + tax equals total.
- Required fields (vendor, invoice number, date, total) are all present.
Identity.
- Vendor exists in the vendor master; otherwise flag as "unknown vendor" and route to manual review (one of the top fraud indicators).
- Invoice number + vendor combination is not a duplicate of an existing invoice.
- Bank details match the vendor's saved bank details; flag any change.
Business logic.
- Invoice date is within a reasonable window (not dated 14 months in the past, not dated tomorrow).
- Due date is reasonable (30, 60, 90 days typical; anything else flag for review).
- Currency matches the vendor's usual currency.
- Tax calculation matches the expected rate for the vendor's jurisdiction.
PO matching (if you use POs).
- PO number exists and is open.
- Invoice vendor matches PO vendor.
- Invoice line items match PO line items (quantities, unit prices, within a tolerance).
- Total invoiced to date against the PO does not exceed the PO value.
Fraud signals.
- Vendor bank details changed within the last 90 days.
- Invoice amount is just below a routing threshold ("$19,950 when approvals over $20k go to VP" is a classic pattern).
- Invoice submitted outside business hours, especially weekends and holidays.
- First-time vendor with a high-value invoice.
- Invoice email sent from a domain that does not match the vendor's saved domain.
None of these are definitive rejections on their own. Each is a signal that either adds a layer of approval (escalate to the finance lead), bumps the invoice to manual review, or at minimum records a note in the audit trail so the approver sees it.
Bank-detail changes always require out-of-band confirmation. The single most common AP fraud pattern is a spoofed "the vendor updated their bank details" email. Before processing any invoice with changed bank details, confirm the change with the vendor by phone on a number from your records — not a number in the email. Never by reply. This one practice catches most business-email-compromise attacks.
Comparison at a glance
| Lightweight extract-and-route | Database-driven with exceptions | Managed HITL on approval step | |
|---|---|---|---|
| Setup time | 1 day | 1 week | Designed for 1–2 days |
| Invoice volume | <50/month | 50–500/month | Any |
| PO matching | Manual | Built-in | Built-in |
| Exception queue | Implicit (unresolved executions) | Explicit (SQL queue) | Explicit (inbox) |
| Fraud checks | DIY | DIY, richer context | DIY flags, shown in approval UI |
| Approval UI | Slack / Teams / email form | DIY + whichever approval channel | Dedicated review page |
| Audit trail | Per-execution | Queryable events table | Built-in (design goal) |
| Best fit | Small teams, simple AP | Mid-market, need reporting | Mid-market, want less UI work |
"Managed HITL on approval step" describes Pattern 3 design intent, not shipped features.
Starter template: inbound AP pipeline with PO matching
A complete workflow for a mid-market AP pipeline with two-way PO matching and a three-tier approval structure.
[Email Trigger: [email protected], with PDF]
|
[Gmail: download attachment]
|
[IF: file is PDF] (skip non-invoice emails like vendor statements)
|
[HTTP Request: Mistral OCR on the PDF]
|
[OpenAI / Gemini: extract structured fields]
|
[Postgres: INSERT invoice (status='received')]
|
[Validation step (Code node)]
| If fail: UPDATE status='exception', reason; notify AP; stop.
|
[Postgres: check (vendor_id + invoice_number) uniqueness]
| If dup: UPDATE status='exception', reason='duplicate'; notify; stop.
|
[Postgres: look up PO by PO number, compare lines]
| If mismatch: UPDATE status='exception', reason='PO mismatch'; notify; stop.
| If match: UPDATE po_match_status='2-way-match'
|
[Code node: compute fraud signals]
Signals: bank_changed, above_threshold, off_hours, first_time_vendor
UPDATE fraud_signals column
|
[Switch: route by amount]
|-- < $2k -> approver: team_lead
|-- < $20k -> approver: finance_manager
|-- >= $20k -> approver: vp
|
[Approval step: native Send-and-Wait OR external HITL]
Display: extracted data (editable), PDF link, PO comparison summary,
fraud signal tags, vendor's previous invoice count and average
Timeout: 48h; On Timeout: escalate one level up
|
[Switch: on decision]
|-- approve -> UPDATE status='approved'
| -> [HTTP Request: post to ERP]
| -> UPDATE status='posted'
| -> [Email vendor: received, scheduled]
| -> INSERT invoice_event(approved, actor, edits)
|-- reject -> UPDATE status='rejected'
| -> [Email vendor: reason]
| -> INSERT invoice_event(rejected, actor, reason)
|-- request_clarification -> UPDATE status='exception'
-> [Email vendor with questions]
Editable extracted fields are the key. Reviewers routinely correct the vendor name (abbreviations), the invoice number (missed digits), the total (when OCR misread a 0 as an 8). If the approver cannot edit before approving, every small extraction error triggers a reject → manual handling cycle. Pre-fill the fields from extraction; let the approver correct them; use the corrected values when posting to the ERP.
Common questions
Can I use native Send-and-Wait Custom Form for the approval step? Yes, and it is the right starting point for Pattern 1. The limits are (a) the form is reached via a link, so approvers on mobile bounce to a browser, and (b) showing a PDF preview alongside the form is not native — you include a link. For small volume, both are fine. At higher volume, the reviewer UX starts to matter more, which is where Pattern 3 enters.
Do I need an ERP integration from day one? No. For the first few months, post approved invoices to a shared Google Sheet or Airtable and have AP process them into the ERP manually. Automate the ERP posting only after the approval flow itself is stable. Premature ERP automation creates a class of reconciliation bugs that are annoying to chase.
What OCR tool should I use? The current 2026 defaults for n8n invoice OCR are Mistral Document AI, Google Document AI, and GPT-4o-vision / Gemini 2.5-vision direct. Mistral is fast and cheap on structured PDFs. Document AI is accurate on messy scans. GPT-4o / Gemini are best when you want the extraction and the structuring in one step. Benchmark on a sample of your actual invoices before picking.
How do I handle invoices in multiple currencies? Extract the currency and the raw total. Store both. Convert to a reporting currency for your amount-based routing (team_lead / finance_manager / VP thresholds) using a daily FX rate. Post to the ERP in the original currency — never pre-convert, because FX reconciliation is what accounting is going to want.
What if the extraction confidence is low? Add a confidence threshold to your extraction step. Below the threshold (say 0.7), route directly to the exception queue with reason "low extraction confidence — manual review required." Above the threshold, continue to validation. This is cheaper than trusting bad extractions and chasing them downstream.
How do I handle invoices that do not have a PO? Have a policy for PO-less invoices: either allow them below a certain amount (with extra approval scrutiny), or reject them at intake and ask the vendor to resubmit referencing a PO. Either is fine; pick one and enforce it deterministically.
Related guides
- The complete guide to human-in-the-loop for n8n — pillar overview and decision framework
- n8n payment and refund approval workflows — financial outbound flows with stricter rules
- n8n multi-level approval workflows — the approval chain patterns applied here to AP levels
- n8n approval timeouts and escalation — reminder and backup approver logic for AP
- Audit trails for n8n AI agents — the compliance angle, critical for AP
- n8n Slack approval workflow — if your AP approvers live in Slack
If your AP pipeline is mostly built but the approval interface is what is costing you time, Humangent is the AI approval workflow for n8n built for it — reviewers see the PDF, vendor history, and PO comparison alongside editable extracted fields; escalation handles month-end crunch; every change lands in the audit. Join the waitlist at humangent.io. Free during private beta, no credit card.