This workflow corresponds to n8n.io template #15937 — we link there as the canonical source.
This workflow follows the Form Trigger → Googlegemini recipe pattern — see all workflows that pair these two integrations.
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 →
{
"id": "PV9o3emEAeIt5SvF",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "AI OCR Invoice Processing - Gemini Sheets Telegram",
"tags": [],
"nodes": [
{
"id": "form-trigger",
"name": "Upload Invoice Form",
"type": "n8n-nodes-base.formTrigger",
"position": [
-2384,
544
],
"parameters": {
"options": {
"path": "invoice",
"ignoreBots": true,
"buttonLabel": "Submit",
"appendAttribution": false,
"respondWithOptions": {
"values": {}
},
"useWorkflowTimezone": true
},
"formTitle": "AI OCR Invoice Upload",
"formFields": {
"values": [
{
"fieldName": "invoice_file",
"fieldType": "file",
"fieldLabel": "Invoice file",
"multipleFiles": false,
"requiredField": true,
"acceptFileTypes": ".pdf,.png,.jpg,.jpeg,.webp,.gif"
}
]
},
"formDescription": "Upload a PDF or image invoice for OCR extraction."
},
"typeVersion": 2.5
},
{
"id": "prepare-input",
"name": "Prepare Invoice Input",
"type": "n8n-nodes-base.code",
"position": [
-2160,
640
],
"parameters": {
"jsCode": "try {\n const item = $input.first();\n const inputJson = item.json || {};\n const inputBinary = item.binary || {};\n const body = inputJson.body && typeof inputJson.body === 'object' ? inputJson.body : {};\n const sourceTrigger = inputJson.message || inputJson.callback_query ? 'telegram' : (inputJson.headers || inputJson.query || inputJson.params ? 'webhook' : 'form');\n const binaryCandidates = ['invoice_file', 'data', 'file', 'attachment'];\n let binaryKey = binaryCandidates.find((key) => inputBinary[key]) || Object.keys(inputBinary)[0];\n const outputBinary = {};\n let meta = {};\n\n if (binaryKey && inputBinary[binaryKey]) {\n meta = inputBinary[binaryKey];\n outputBinary.data = inputBinary[binaryKey];\n binaryKey = 'data';\n }\n\n const base64Value = body.file_base64 || inputJson.file_base64;\n if (!binaryKey && base64Value) {\n const cleanBase64 = String(base64Value).replace(/^data:[^;]+;base64,/, '');\n const mimeType = body.mime_type || inputJson.mime_type || 'application/pdf';\n const fileName = body.file_name || inputJson.file_name || 'invoice-upload';\n outputBinary.data = {\n data: cleanBase64,\n mimeType,\n fileName,\n };\n meta = outputBinary.data;\n binaryKey = 'data';\n }\n\n const mimeType = meta.mimeType || body.mime_type || inputJson.mime_type || '';\n const fileName = meta.fileName || body.file_name || inputJson.file_name || 'invoice-upload';\n const allowedMimeTypes = ['application/pdf', 'image/png', 'image/jpeg', 'image/jpg', 'image/webp', 'image/gif'];\n const allowedExtensions = /\\.(pdf|png|jpe?g|webp|gif)$/i;\n const isAllowed = allowedMimeTypes.includes(String(mimeType).toLowerCase()) || allowedExtensions.test(fileName);\n const errors = [];\n\n if (!binaryKey) errors.push('Missing invoice_file binary upload or file_base64 payload.');\n if (binaryKey && !isAllowed) errors.push(`Unsupported file type: ${mimeType || fileName || 'unknown'}.`);\n\n return [{\n json: {\n source_trigger: sourceTrigger,\n source_filename: fileName,\n mime_type: mimeType,\n openai_content_type: String(mimeType).toLowerCase() === 'application/pdf' || /\\.pdf$/i.test(fileName) ? 'input_file' : 'input_image',\n is_processable: errors.length === 0,\n validation_errors: errors.join('; '),\n },\n binary: outputBinary,\n }];\n} catch (error) {\n return [{\n json: {\n source_trigger: 'unknown',\n source_filename: 'unknown_file',\n mime_type: '',\n is_processable: false,\n validation_errors: `Prepare input failed: ${error.message}`,\n },\n }];\n}"
},
"typeVersion": 2
},
{
"id": "if-processable",
"name": "File Is Processable?",
"type": "n8n-nodes-base.if",
"onError": "continueErrorOutput",
"position": [
-1936,
640
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "processable-true",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{$json.is_processable}}",
"rightValue": true
}
]
}
},
"typeVersion": 2.3
},
{
"id": "invalid-result",
"name": "Build Invalid File Result",
"type": "n8n-nodes-base.code",
"position": [
-1712,
736
],
"parameters": {
"jsCode": "try {\n const item = $input.first();\n const data = item.json || {};\n\n const errors = Array.isArray(data.validation_errors)\n ? data.validation_errors.join(', ')\n : data.validation_errors || 'Unsupported or missing invoice file.';\n\n const fileName = data.source_filename || 'unknown_file';\n const trigger = data.source_trigger || 'unknown_trigger';\n\n return [{\n json: {\n status: 'error',\n source_trigger: trigger,\n source_filename: fileName,\n invoice_number: data.invoice_number || null,\n validation_status: 'rejected',\n validation_errors: errors,\n sheet_append_result: null,\n telegram_message: `\u274c Invoice OCR Rejected\\n\\n\ud83d\udcc4 File:\\n${fileName}\\n\\n\u26a0\ufe0f Reason:\\n${errors}\\n\\n\ud83e\uddfe Invoice Number:\\n${data.invoice_number || 'N/A'}\\n\\n\ud83d\udd04 Trigger:\\n${trigger}`,\n log: {\n timestamp: new Date().toISOString(),\n stage: 'validation',\n success: false,\n },\n },\n }];\n} catch (error) {\n return [{\n json: {\n status: 'error',\n validation_status: 'rejected',\n validation_errors: `Build invalid result failed: ${error.message}`,\n telegram_message: `\u274c Invoice OCR Rejected\\n\\n\u26a0\ufe0f Reason:\\n${error.message}`,\n },\n }];\n}"
},
"typeVersion": 2
},
{
"id": "append-sheets",
"name": "Append Invoice to Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
-592,
544
],
"parameters": {
"columns": {
"value": {},
"schema": [],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {
"handlingExtraData": "insertInNewColumn"
},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Invoices",
"cachedResultName": "Invoices"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "REPLACE_WITH_GOOGLE_SHEET_ID",
"cachedResultName": "Invoice OCR Google Sheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "112a2616-87b9-4ec3-a7f0-04122d23b4b0",
"name": "Upload a media file",
"type": "@n8n/n8n-nodes-langchain.googleGemini",
"position": [
-1712,
544
],
"parameters": {
"resource": "file",
"inputType": "binary"
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "52b4d01b-a879-4f27-be9f-1d2943a62b3d",
"name": "Analyze document",
"type": "@n8n/n8n-nodes-langchain.googleGemini",
"position": [
-1264,
448
],
"parameters": {
"text": "=Analyze the uploaded document and extract structured financial data.\n\nThe document may be:\n\n* Sales invoice\n* Retail receipt\n* Tax invoice\n* Service invoice\n* Product invoice\n* Restaurant bill\n* Utility bill\n* Logistics invoice\n* Purchase receipt\n* POS receipt\n\nReturn ONLY valid JSON.\nDo not return markdown.\nDo not explain anything.\n\nExtraction Rules:\n\n* Use null if a value cannot be detected.\n* Normalize all dates to YYYY-MM-DD format.\n* Normalize all amounts as numbers only.\n* Remove currency symbols and thousand separators.\n* Preserve original language text.\n* Detect fields dynamically across different invoice layouts.\n* Support multilingual invoices and receipts.\n* Line items may be empty if unavailable.\n* Confidence must be between 0 and 1.\n\nOutput JSON schema:\n\n{\n\"document_type\": string | null,\n\n\"vendor_name\": string | null,\n\"vendor_tax_id\": string | null,\n\"vendor_address\": string | null,\n\"vendor_phone\": string | null,\n\n\"customer_name\": string | null,\n\"customer_tax_id\": string | null,\n\n\"invoice_number\": string | null,\n\"receipt_number\": string | null,\n\n\"invoice_date\": string | null,\n\"due_date\": string | null,\n\n\"currency\": string | null,\n\n\"subtotal\": number | null,\n\"tax_amount\": number | null,\n\"discount_amount\": number | null,\n\"service_charge\": number | null,\n\"shipping_amount\": number | null,\n\"tip_amount\": number | null,\n\"total_amount\": number | null,\n\n\"payment_method\": string | null,\n\"payment_terms\": string | null,\n\n\"line_items\": [\n{\n\"description\": string | null,\n\"sku\": string | null,\n\"barcode\": string | null,\n\"category\": string | null,\n\n```\n \"quantity\": number | null,\n \"unit\": string | null,\n\n \"unit_price\": number | null,\n \"discount\": number | null,\n \"tax\": number | null,\n \"amount\": number | null\n}\n```\n\n],\n\n\"notes\": string | null,\n\n\"confidence\": number\n}\n",
"modelId": {
"__rl": true,
"mode": "list",
"value": "models/gemini-2.5-flash",
"cachedResultName": "models/gemini-2.5-flash"
},
"options": {},
"resource": "document",
"documentUrls": "={{ $json.fileUri }}"
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "9cf30a4c-5f94-4f91-b9b4-bef7806b7901",
"name": "If",
"type": "n8n-nodes-base.if",
"onError": "continueErrorOutput",
"position": [
-1488,
544
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "055db48e-bc84-472e-849f-1d29dc4cbc06",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.mimeType }}",
"rightValue": "application/pdf"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "db90b41a-f6d4-4ce3-ac49-71494c483adb",
"name": "Analyze an image",
"type": "@n8n/n8n-nodes-langchain.googleGemini",
"position": [
-1264,
640
],
"parameters": {
"text": "=Analyze the uploaded document and extract structured financial data.\n\nThe document may be:\n\n* Sales invoice\n* Retail receipt\n* Tax invoice\n* Service invoice\n* Product invoice\n* Restaurant bill\n* Utility bill\n* Logistics invoice\n* Purchase receipt\n* POS receipt\n\nReturn ONLY valid JSON.\nDo not return markdown.\nDo not explain anything.\n\nExtraction Rules:\n\n* Use null if a value cannot be detected.\n* Normalize all dates to YYYY-MM-DD format.\n* Normalize all amounts as numbers only.\n* Remove currency symbols and thousand separators.\n* Preserve original language text.\n* Detect fields dynamically across different invoice layouts.\n* Support multilingual invoices and receipts.\n* Line items may be empty if unavailable.\n* Confidence must be between 0 and 1.\n\nOutput JSON schema:\n\n{\n\"document_type\": string | null,\n\n\"vendor_name\": string | null,\n\"vendor_tax_id\": string | null,\n\"vendor_address\": string | null,\n\"vendor_phone\": string | null,\n\n\"customer_name\": string | null,\n\"customer_tax_id\": string | null,\n\n\"invoice_number\": string | null,\n\"receipt_number\": string | null,\n\n\"invoice_date\": string | null,\n\"due_date\": string | null,\n\n\"currency\": string | null,\n\n\"subtotal\": number | null,\n\"tax_amount\": number | null,\n\"discount_amount\": number | null,\n\"service_charge\": number | null,\n\"shipping_amount\": number | null,\n\"tip_amount\": number | null,\n\"total_amount\": number | null,\n\n\"payment_method\": string | null,\n\"payment_terms\": string | null,\n\n\"line_items\": [\n{\n\"description\": string | null,\n\"sku\": string | null,\n\"barcode\": string | null,\n\"category\": string | null,\n\n```\n \"quantity\": number | null,\n \"unit\": string | null,\n\n \"unit_price\": number | null,\n \"discount\": number | null,\n \"tax\": number | null,\n \"amount\": number | null\n}\n```\n\n],\n\n\"notes\": string | null,\n\n\"confidence\": number\n}",
"modelId": {
"__rl": true,
"mode": "list",
"value": "models/gemini-2.5-flash",
"cachedResultName": "models/gemini-2.5-flash"
},
"options": {},
"resource": "image",
"imageUrls": "={{ $json.fileUri }}",
"operation": "analyze"
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "cea31058-3432-438f-9e8e-63814ed6bbab",
"name": "Normalize and Validate",
"type": "n8n-nodes-base.code",
"position": [
-1040,
544
],
"parameters": {
"jsCode": "try {\n const items = $input.all();\n\n function parseJson(value) {\n if (typeof value !== 'string') return value;\n\n try {\n return JSON.parse(\n value\n .replace(/```json/g, '')\n .replace(/```/g, '')\n .trim()\n );\n } catch {\n return null;\n }\n }\n\n function looksNumeric(text) {\n if (!text) return false;\n const cleaned = text.replace(/,/g, '');\n return cleaned !== '' && !Number.isNaN(Number(cleaned));\n }\n\n function cleanValue(value) {\n if (value === undefined || value === '') return null;\n\n if (typeof value === 'string') {\n const text = value.trim();\n\n if (looksNumeric(text)) {\n const num = Number(text.replace(/,/g, ''));\n if (!isNaN(num)) return num;\n }\n\n const date = new Date(text);\n if (!isNaN(date.getTime()) && /\\d{4}|\\d{2}/.test(text)) {\n return date.toISOString().split('T')[0];\n }\n\n return text;\n }\n\n if (Array.isArray(value)) return value.map(cleanValue);\n\n if (typeof value === 'object' && value !== null) {\n const cleaned = {};\n for (const key in value) cleaned[key] = cleanValue(value[key]);\n return cleaned;\n }\n\n return value;\n }\n\n return items.map((item) => {\n let raw = item.json?.content?.parts?.[0]?.text || item.json?.text || item.json;\n raw = parseJson(raw);\n\n if (!raw) {\n return {\n json: {\n is_valid: false,\n error: 'Cannot parse JSON',\n data: { line_items: [] },\n },\n };\n }\n\n const cleanedData = cleanValue(raw);\n if (!Array.isArray(cleanedData.line_items)) cleanedData.line_items = [];\n\n return {\n json: {\n is_valid: true,\n data: cleanedData,\n },\n };\n });\n} catch (error) {\n return [{\n json: {\n is_valid: false,\n error: `Normalize and validate failed: ${error.message}`,\n data: { line_items: [] },\n },\n }];\n}"
},
"typeVersion": 2
},
{
"id": "784d2c04-9643-4bf9-b1be-8695d601caf5",
"name": "Split Out",
"type": "n8n-nodes-base.splitOut",
"position": [
-816,
544
],
"parameters": {
"include": "allOtherFields",
"options": {},
"fieldToSplitOut": "data.line_items"
},
"typeVersion": 1
},
{
"id": "eb99e761-95cb-40dd-8fd1-bfc2d6c1c0ab",
"name": "Telegram Trigger",
"type": "n8n-nodes-base.telegramTrigger",
"position": [
-2384,
736
],
"parameters": {},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "94cc1f7d-cb12-45fd-a259-15ac3852d902",
"name": "Send Telegram Notification1",
"type": "n8n-nodes-base.telegram",
"onError": "continueRegularOutput",
"position": [
-144,
544
],
"parameters": {
"text": "={{ $json.telegram_message }}",
"chatId": "REPLACE_WITH_TELEGRAM_CHAT_ID",
"additionalFields": {
"parse_mode": "HTML",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "1f4f6b38-e806-4b16-a64f-9d3ce5403427",
"name": "Send Telegram Rejection1",
"type": "n8n-nodes-base.telegram",
"onError": "continueRegularOutput",
"position": [
-1488,
736
],
"parameters": {
"text": "={{$json.telegram_message}}",
"chatId": "REPLACE_WITH_TELEGRAM_CHAT_ID",
"additionalFields": {
"parse_mode": "HTML",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "8466b9b2-376a-4093-a484-fe104335fc08",
"name": "Build Success File",
"type": "n8n-nodes-base.code",
"position": [
-368,
544
],
"parameters": {
"jsCode": "try {\n const items = $('Split Out').all();\n const first = items[0]?.json || {};\n const rootData = {};\n\n for (const key in first) {\n if (key === 'data_line_items' || key === 'line_items') continue;\n rootData[key] = first[key];\n }\n\n function getLineItem(item) {\n return item.json.data_line_items || item.json.line_items || item.json.item || {};\n }\n\n function htmlEscape(value) {\n return String(value ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n }\n\n const infoHtml = Object.entries(rootData)\n .map(([key, value]) => {\n if (value === null || value === undefined || typeof value === 'object') return '';\n return `\n <tr>\n <td style=\"padding:8px;border:1px solid #ddd;font-weight:bold;background:#f8fafc;\">${htmlEscape(key)}</td>\n <td style=\"padding:8px;border:1px solid #ddd;\">${htmlEscape(value)}</td>\n </tr>`;\n })\n .join('');\n\n const sampleLine = getLineItem(items[0]);\n const columns = Object.keys(sampleLine);\n const tableHeader = columns\n .map((col) => `<th style=\"padding:8px;border:1px solid #ddd;background:#f1f5f9;\">${htmlEscape(col)}</th>`)\n .join('');\n\n const tableRows = items\n .map((item) => {\n const line = getLineItem(item);\n return `<tr>${columns.map((col) => `<td style=\"padding:8px;border:1px solid #ddd;\">${htmlEscape(line[col] ?? '')}</td>`).join('')}</tr>`;\n })\n .join('');\n\n const html = `\n<div style=\"font-family: Arial;padding: 20px;background: #f8fafc;border-radius: 12px;border: 1px solid #e2e8f0;\">\n <h2 style=\"margin-top:0;color:#16a34a;\">\u2705 OCR Processing Completed</h2>\n <h3>Invoice Information</h3>\n <table style=\"width:100%;border-collapse:collapse;background:white;margin-bottom:20px;\">${infoHtml}</table>\n <h3>Line Items (${items.length})</h3>\n <table style=\"width:100%;border-collapse:collapse;background:white;\">\n <thead><tr>${tableHeader}</tr></thead>\n <tbody>${tableRows}</tbody>\n </table>\n</div>`;\n\n return [{\n json: {\n status: 'success',\n total_items: items.length,\n html,\n telegram_message: `\u2705 OCR Processing Completed\\n\\n\ud83d\udce6 Total Line Items: ${items.length}`,\n data: rootData,\n },\n }];\n} catch (error) {\n return [{\n json: {\n status: 'error',\n total_items: 0,\n html: '',\n telegram_message: `\u26a0\ufe0f OCR completed, but summary build failed: ${error.message}`,\n data: {},\n },\n }];\n}"
},
"typeVersion": 2
},
{
"id": "note-overview-creator",
"name": "README - Workflow Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2400,
-192
],
"parameters": {
"color": 4,
"width": 816,
"height": 316,
"content": "## AI OCR Invoice Processing\n\nUse this workflow to process invoice PDFs/images with AI OCR, extract structured invoice data, validate the result, save rows to Google Sheets, and notify via Telegram.\n\n**Main flow**\n1. Upload invoice from n8n Form or send a file to Telegram.\n2. Validate that the file is a supported PDF/image.\n3. Upload/analyze with Gemini.\n4. Normalize invoice JSON and validation status.\n5. Append to Google Sheets.\n6. Send Telegram summary.\n\n**Best for**: accounts payable automation, invoice OCR demos, AI document processing portfolios."
},
"typeVersion": 1
},
{
"id": "note-setup-credentials",
"name": "SETUP - Credentials and Required Config",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1568,
-192
],
"parameters": {
"color": 3,
"width": 816,
"height": 472,
"content": "## Setup before using\n\nConfigure these nodes after importing the workflow:\n\n**Gemini**\n- `Upload a media file`\n- `Analyze document`\n- `Analyze an image`\n- Add your Google Gemini credential/API key.\n\n**Google Sheets**\n- `Append Invoice to Google Sheets`\n- Select your spreadsheet and worksheet.\n- Recommended sheet name: `Invoices`.\n\n**Telegram**\n- `Telegram Trigger`\n- `Send Telegram Notification1`\n- `Send Telegram Rejection1`\n- Add your Telegram bot credential and target chat.\n\nKeep the workflow inactive while configuring credentials, then run a manual test before publishing."
},
"typeVersion": 1
},
{
"id": "note-inputs",
"name": "USAGE - Upload Options",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2400,
144
],
"parameters": {
"color": 5,
"width": 816,
"height": 288,
"content": "## How to use\n\n**Option 1: n8n Form**\n- Open the production form URL from `Upload Invoice Form`.\n- Upload one invoice file.\n- Supported formats: PDF, PNG, JPG, JPEG, WEBP, GIF.\n\n**Option 2: Telegram**\n- Send an invoice file/image to the configured bot.\n- The Telegram trigger passes the file into the same processing path.\n\nIf the file is missing or unsupported, the workflow routes to `Build Invalid File Result` and sends a rejection message instead of writing to Google Sheets."
},
"typeVersion": 1
},
{
"id": "note-data-schema",
"name": "REFERENCE - Output Schema",
"type": "n8n-nodes-base.stickyNote",
"position": [
-736,
80
],
"parameters": {
"color": 6,
"width": 812,
"height": 296,
"content": "## Google Sheets output columns\n\nCreate matching headers in your `Invoices` sheet for clean reporting:\n\n`processed_at`, `source_filename`, `vendor_name`, `vendor_tax_id`, `invoice_number`, `invoice_date`, `due_date`, `currency`, `subtotal`, `tax_amount`, `total_amount`, `payment_terms`, `line_items_json`, `confidence`, `validation_status`, `validation_errors`, `raw_response_id`\n\n**Validation behavior**\n- Missing optional fields are allowed.\n- Missing required invoice fields should be marked for review.\n- `validation_status = ok` means ready for accounting review.\n- `validation_status = needs_review` means human check required."
},
"typeVersion": 1
},
{
"id": "note-testing-publish",
"name": "TESTING - Creator Checklist",
"type": "n8n-nodes-base.stickyNote",
"position": [
-736,
-192
],
"parameters": {
"color": 2,
"width": 816,
"height": 264,
"content": "## Test checklist before publishing\n\nRun these tests before posting to n8n Creator:\n\n1. Upload a normal PDF invoice from the form.\n2. Upload a JPG/PNG invoice image.\n3. Send an invoice to the Telegram bot.\n4. Try an unsupported file type and confirm rejection notification.\n5. Confirm Google Sheets receives one row per valid invoice.\n6. Confirm Telegram summary includes vendor, invoice number, amount, and status.\n\nAfter testing, remove any private sample invoice data from execution history before sharing publicly."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"timezone": "Asia/Bangkok",
"binaryMode": "separate",
"executionOrder": "v1",
"saveDataErrorExecution": "all",
"saveDataSuccessExecution": "all"
},
"versionId": "d5043788-8083-481b-a02f-ba91a5117db8",
"connections": {
"If": {
"main": [
[
{
"node": "Analyze document",
"type": "main",
"index": 0
}
],
[
{
"node": "Analyze an image",
"type": "main",
"index": 0
}
]
]
},
"Split Out": {
"main": [
[
{
"node": "Append Invoice to Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"Analyze an image": {
"main": [
[
{
"node": "Normalize and Validate",
"type": "main",
"index": 0
}
]
]
},
"Analyze document": {
"main": [
[
{
"node": "Normalize and Validate",
"type": "main",
"index": 0
}
]
]
},
"Telegram Trigger": {
"main": [
[
{
"node": "Prepare Invoice Input",
"type": "main",
"index": 0
}
]
]
},
"Build Success File": {
"main": [
[
{
"node": "Send Telegram Notification1",
"type": "main",
"index": 0
}
]
]
},
"Upload Invoice Form": {
"main": [
[
{
"node": "Prepare Invoice Input",
"type": "main",
"index": 0
}
]
]
},
"Upload a media file": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"File Is Processable?": {
"main": [
[
{
"node": "Upload a media file",
"type": "main",
"index": 0
}
],
[
{
"node": "Build Invalid File Result",
"type": "main",
"index": 0
}
]
]
},
"Prepare Invoice Input": {
"main": [
[
{
"node": "File Is Processable?",
"type": "main",
"index": 0
}
]
]
},
"Normalize and Validate": {
"main": [
[
{
"node": "Split Out",
"type": "main",
"index": 0
}
]
]
},
"Build Invalid File Result": {
"main": [
[
{
"node": "Send Telegram Rejection1",
"type": "main",
"index": 0
}
]
]
},
"Append Invoice to Google Sheets": {
"main": [
[
{
"node": "Build Success File",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googlePalmApigoogleSheetsOAuth2ApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow accepts invoice PDFs or images from an n8n Form or Telegram, uses Google Gemini to OCR and extract structured invoice fields and line items, appends the results to Google Sheets, and sends a success or rejection notification via Telegram. Receives an invoice file…
Source: https://n8n.io/workflows/15937/ — 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.
automation_financial_recording. Uses telegramTrigger, telegram, googleGemini, lmChatGoogleGemini. Event-driven trigger; 35 nodes.
automation_financial_recording. Uses telegramTrigger, telegram, googleGemini, lmChatGoogleGemini. Event-driven trigger; 35 nodes.
automation_financial_recording. Uses telegramTrigger, telegram, googleGemini, lmChatGoogleGemini. Event-driven trigger; 35 nodes.
This workflow automatically extracts structured data from invoices sent via Telegram (PDF or image) and saves it to Excel. A user sends an invoice (PDF or image) to a Telegram bot The workflow detects
automation_financial_recording. Uses telegramTrigger, telegram, googleGemini, lmChatGoogleGemini. Event-driven trigger; 29 nodes.