The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Invoice Intake Review Queue",
"nodes": [
{
"parameters": {},
"id": "manual-trigger",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
240,
220
]
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"weeksInterval": 1
}
]
}
},
"id": "weekly-schedule",
"name": "Weekly Schedule",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
240,
420
]
},
{
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"id": "invoices",
"name": "invoices",
"type": "array",
"value": [
{
"id": "INV-1001",
"vendorName": "Northstar Hosting",
"vendorEmail": "billing@northstar.example",
"invoiceNumber": "NH-2026-0421",
"invoiceDate": "2026-05-20",
"dueDate": "2026-06-19",
"currency": "USD",
"subtotal": 240,
"tax": 0,
"total": 240,
"poNumber": "PO-8841",
"category": "hosting",
"attachmentName": "northstar-hosting-nh-2026-0421.pdf",
"source": "email",
"extractedTextConfidence": 0.94,
"paymentStatus": "unpaid"
},
{
"id": "INV-1002",
"vendorName": "Northstar Hosting",
"vendorEmail": "billing@northstar.example",
"invoiceNumber": "NH-2026-0421",
"invoiceDate": "2026-05-20",
"dueDate": "2026-06-19",
"currency": "USD",
"subtotal": 240,
"tax": 0,
"total": 240,
"poNumber": "PO-8841",
"category": "hosting",
"attachmentName": "northstar-hosting-copy.pdf",
"source": "email",
"extractedTextConfidence": 0.92,
"paymentStatus": "unpaid"
},
{
"id": "INV-1003",
"vendorName": "",
"vendorEmail": "receipts@design.example",
"invoiceNumber": "",
"invoiceDate": "2026-05-22",
"dueDate": "",
"currency": "USD",
"subtotal": 89,
"tax": 8.9,
"total": 99.9,
"poNumber": "",
"category": "design tools",
"attachmentName": "receipt-0522.png",
"source": "upload",
"extractedTextConfidence": 0.61,
"paymentStatus": "unpaid"
}
]
},
{
"id": "approval-threshold",
"name": "autoApproveBelow",
"type": "number",
"value": 50
},
{
"id": "currency",
"name": "defaultCurrency",
"type": "string",
"value": "USD"
},
{
"id": "review-owner",
"name": "reviewOwner",
"type": "string",
"value": "Finance Ops"
},
{
"id": "confidence-threshold",
"name": "lowConfidenceThreshold",
"type": "number",
"value": 0.8
},
{
"id": "duplicate-window",
"name": "duplicateWindowDays",
"type": "number",
"value": 45
}
]
}
},
"id": "invoice-input",
"name": "Invoice Intake Input",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
540,
320
],
"notesInFlow": true,
"notes": "Replace the sample invoices with records from OCR, email parsing, form uploads, Google Drive, Dropbox, Airtable, or your accounting export. Do not include raw bank credentials or payment authorization data."
},
{
"parameters": {
"jsCode": "const input = $json;\nconst invoices = Array.isArray(input.invoices) ? input.invoices : [];\nconst lowConfidenceThreshold = Number(input.lowConfidenceThreshold ?? 0.8);\nconst autoApproveBelow = Number(input.autoApproveBelow ?? 0);\n\nconst normalize = (value) => String(value ?? '').trim();\nconst lower = (value) => normalize(value).toLowerCase();\nconst amount = (value) => Number.isFinite(Number(value)) ? Number(value) : null;\nconst dateMs = (value) => {\n const ms = Date.parse(value);\n return Number.isFinite(ms) ? ms : null;\n};\nconst duplicateKey = (invoice) => [lower(invoice.vendorName), lower(invoice.invoiceNumber), amount(invoice.total), lower(invoice.currency)].join('|');\nconst seen = new Map();\nconst normalized = invoices.map((invoice, index) => {\n const subtotal = amount(invoice.subtotal);\n const tax = amount(invoice.tax) ?? 0;\n const total = amount(invoice.total);\n const confidence = amount(invoice.extractedTextConfidence);\n const issues = [];\n const warnings = [];\n if (!normalize(invoice.vendorName)) issues.push('Missing vendor name');\n if (!normalize(invoice.invoiceNumber)) issues.push('Missing invoice number');\n if (!dateMs(invoice.invoiceDate)) issues.push('Missing or invalid invoice date');\n if (!total || total <= 0) issues.push('Missing or invalid invoice total');\n if (subtotal != null && total != null && Math.abs((subtotal + tax) - total) > 0.05) warnings.push('Subtotal plus tax does not match total');\n if (confidence != null && confidence < lowConfidenceThreshold) issues.push('Low extraction confidence');\n if (!normalize(invoice.poNumber)) warnings.push('Missing purchase order number');\n if (!normalize(invoice.dueDate)) warnings.push('Missing due date');\n const key = duplicateKey(invoice);\n const duplicateOf = seen.get(key);\n if (duplicateOf) issues.push('Possible duplicate invoice');\n else seen.set(key, invoice.id || invoice.invoiceNumber || String(index + 1));\n const riskScore = issues.length * 30 + warnings.length * 10 + (total && total > 1000 ? 20 : 0);\n let reviewStatus = 'ready_for_review';\n if (issues.length > 0) reviewStatus = 'needs_fix';\n else if (total != null && total <= autoApproveBelow) reviewStatus = 'low_value_ready';\n return {\n ...invoice,\n normalizedVendor: normalize(invoice.vendorName),\n normalizedInvoiceNumber: normalize(invoice.invoiceNumber),\n subtotal,\n tax,\n total,\n confidence,\n duplicateKey: key,\n duplicateOf: duplicateOf || null,\n issues,\n warnings,\n riskScore,\n reviewStatus,\n suggestedOwner: input.reviewOwner || 'Finance Ops'\n };\n});\nreturn normalized.map((invoice) => ({ json: invoice }));"
},
"id": "validate-invoices",
"name": "Validate and Score Invoices",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
840,
320
],
"notesInFlow": true,
"notes": "Normalizes invoice fields, checks required values, flags duplicates, detects low-confidence OCR/extraction, checks subtotal + tax against total, and assigns a review status."
},
{
"parameters": {
"jsCode": "const invoices = $input.all().map((item) => item.json);\nconst needsFix = invoices.filter((invoice) => invoice.reviewStatus === 'needs_fix');\nconst duplicateRisk = invoices.filter((invoice) => invoice.issues?.includes('Possible duplicate invoice'));\nconst lowConfidence = invoices.filter((invoice) => invoice.issues?.includes('Low extraction confidence'));\nconst missingPo = invoices.filter((invoice) => invoice.warnings?.includes('Missing purchase order number'));\nconst ready = invoices.filter((invoice) => invoice.reviewStatus !== 'needs_fix');\nconst reviewQueue = invoices\n .slice()\n .sort((a, b) => (b.riskScore || 0) - (a.riskScore || 0))\n .map((invoice) => ({\n id: invoice.id,\n vendorName: invoice.vendorName || '(missing vendor)',\n invoiceNumber: invoice.invoiceNumber || '(missing invoice number)',\n total: invoice.total,\n currency: invoice.currency,\n dueDate: invoice.dueDate || null,\n reviewStatus: invoice.reviewStatus,\n riskScore: invoice.riskScore,\n issues: invoice.issues,\n warnings: invoice.warnings,\n duplicateOf: invoice.duplicateOf,\n suggestedOwner: invoice.suggestedOwner,\n attachmentName: invoice.attachmentName\n }));\nconst approvalCandidates = ready.filter((invoice) => invoice.reviewStatus === 'low_value_ready').map((invoice) => ({\n id: invoice.id,\n vendorName: invoice.vendorName,\n invoiceNumber: invoice.invoiceNumber,\n total: invoice.total,\n currency: invoice.currency,\n note: 'Low value and no blocking issues. Still requires finance policy approval before payment.'\n}));\nreturn [{ json: {\n totalInvoices: invoices.length,\n needsFixCount: needsFix.length,\n duplicateRiskCount: duplicateRisk.length,\n lowConfidenceCount: lowConfidence.length,\n missingPoCount: missingPo.length,\n readyCount: ready.length,\n reviewQueue,\n approvalCandidates,\n duplicateRisk,\n lowConfidence,\n missingPo,\n invoices\n} }];"
},
"id": "build-review-queue",
"name": "Build Finance Review Queue",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1140,
320
],
"notesInFlow": true,
"notes": "Builds a sorted finance review queue and separates duplicate-risk, low-confidence, missing-PO, and low-value-ready items. This workflow does not approve or pay invoices."
},
{
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"id": "report-title",
"name": "reportTitle",
"type": "string",
"value": "={{'Invoice Intake Review Queue - ' + $json.needsFixCount + ' need fixes of ' + $json.totalInvoices + ' invoices'}}"
},
{
"id": "report-summary",
"name": "reportSummary",
"type": "string",
"value": "={{'Reviewed ' + $json.totalInvoices + ' invoices. Needs fix: ' + $json.needsFixCount + '. Duplicate risk: ' + $json.duplicateRiskCount + '. Low confidence: ' + $json.lowConfidenceCount + '. Missing PO: ' + $json.missingPoCount + '.'}}"
},
{
"id": "review-queue",
"name": "reviewQueue",
"type": "array",
"value": "={{$json.reviewQueue}}"
},
{
"id": "approval-candidates",
"name": "approvalCandidates",
"type": "array",
"value": "={{$json.approvalCandidates}}"
},
{
"id": "duplicate-risk",
"name": "duplicateRisk",
"type": "array",
"value": "={{$json.duplicateRisk}}"
},
{
"id": "low-confidence",
"name": "lowConfidence",
"type": "array",
"value": "={{$json.lowConfidence}}"
},
{
"id": "missing-po",
"name": "missingPo",
"type": "array",
"value": "={{$json.missingPo}}"
}
]
}
},
"id": "final-report",
"name": "Final Finance Review Report",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1440,
320
],
"notesInFlow": true,
"notes": "Final structured report. Connect this to Slack, email, Notion, Airtable, Google Sheets, ClickUp, or an accounting review dashboard after validating your finance policy."
},
{
"parameters": {
"content": "## Invoice Intake Review Queue\n\nUse this workflow after OCR, email parsing, or invoice upload steps. It turns extracted invoice records into a finance review queue with missing-field checks, duplicate-risk flags, low-confidence extraction flags, and approval candidates.",
"height": 240,
"width": 440
},
"id": "note-overview",
"name": "Overview Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
500,
40
]
},
{
"parameters": {
"content": "## Setup\n\n1. Replace sample invoices in **Invoice Intake Input**.\n2. Tune low-confidence and low-value thresholds.\n3. Run manually before activating the weekly schedule.\n4. Connect the final report to your review destination.",
"height": 220,
"width": 440
},
"id": "note-setup",
"name": "Setup Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
780,
560
]
},
{
"parameters": {
"content": "## Finance safety boundary\n\nThis workflow does not pay invoices, approve expenses, create accounting entries, or make tax decisions. It prepares a review queue so a human can verify documents, vendors, amounts, policies, and approvals.",
"height": 220,
"width": 440
},
"id": "note-safety",
"name": "Finance Safety Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1080,
40
]
},
{
"parameters": {
"content": "## Common integrations\n\nAdd Gmail, Outlook, Google Drive, Dropbox, Airtable, Notion, Google Sheets, Slack, ClickUp, QuickBooks, Xero, or your OCR/parser upstream or downstream depending on your stack.",
"height": 200,
"width": 440
},
"id": "note-integrations",
"name": "Integration Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1360,
560
]
}
],
"connections": {
"Manual Trigger": {
"main": [
[
{
"node": "Invoice Intake Input",
"type": "main",
"index": 0
}
]
]
},
"Weekly Schedule": {
"main": [
[
{
"node": "Invoice Intake Input",
"type": "main",
"index": 0
}
]
]
},
"Invoice Intake Input": {
"main": [
[
{
"node": "Validate and Score Invoices",
"type": "main",
"index": 0
}
]
]
},
"Validate and Score Invoices": {
"main": [
[
{
"node": "Build Finance Review Queue",
"type": "main",
"index": 0
}
]
]
},
"Build Finance Review Queue": {
"main": [
[
{
"node": "Final Finance Review Report",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"tags": [
{
"name": "Finance"
},
{
"name": "Invoices"
},
{
"name": "Human Review"
}
]
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Invoice Intake Review Queue. Event-driven trigger; 10 nodes.
Source: https://github.com/xiaopeng215-sys/geo-visibility-audit-mcp/blob/main/workflows/invoice-intake-review-queue.n8n.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This is the ultimate sales-to-cash automation. When a deal in Airtable is marked "Approved for Invoicing," this workflow intelligently syncs customer data across QuickBooks and Stripe (creating them i
How It Works Trigger: Watches for new emails in Gmail with PDF/image attachments. OCR: Sends the attachment to OCR.space API (https://ocr.space/OCRAPI) to extract invoice text. Parsing: Extracts key f
Automated Stripe Payment to QuickBooks Sales Receipt
This workflow allows you to quickly generate and send invoices by collecting missing billing details from clients through an automated form and email sequence. It integrates Gmail and QuickBooks Onlin
Upload the same invoice in different qualities (original PDF, scanned copy, phone photo, compressed JPEG, etc.) and instantly see how accurately each field was extracted. The workflow compares every e