AutomationFlows β€Ί AI & RAG β€Ί Track Telegram Expenses with Gpt-4 and Google Sheets (self-learning Categories)

Track Telegram Expenses with Gpt-4 and Google Sheets (self-learning Categories)

ByRobin @robinvm on n8n.io

πŸ’Έ HOW IT WORKS β€” AI TELEGRAM EXPENSE TRACKER

Event triggerβ˜…β˜…β˜…β˜…β˜… complexityAI-powered36 nodesTelegram TriggerOpenAIGoogle SheetsTelegram
AI & RAG Trigger: Event Nodes: 36 Complexity: β˜…β˜…β˜…β˜…β˜… AI nodes: yes Added:

This workflow corresponds to n8n.io template #13667 β€” we link there as the canonical source.

This workflow follows the Google Sheets β†’ OpenAI 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 β†’

Download .json
{
  "id": "",
  "name": "AI Telegram Expense Tracker (Self-Learning Categories)",
  "tags": [],
  "nodes": [
    {
      "id": "9f0e8a05-1820-4372-b4c7-1e1902bd93c2",
      "name": "Telegram - Receive Message",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        -3472,
        272
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "951ab95a-9e41-4634-ac6b-571ddb7eb3ce",
      "name": "Security \u2014 Allow Approved Chat IDs",
      "type": "n8n-nodes-base.if",
      "position": [
        -3232,
        272
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "0f85f7d5-8b7e-4bcd-ae10-d7a1785192c4",
              "operator": {
                "type": "number",
                "operation": "equals"
              },
              "leftValue": "={{ $json.message.chat.id }}",
              "rightValue": 0
            },
            {
              "id": "2d18f8cf-2910-4e20-b2f3-16a9ef04e63e",
              "operator": {
                "type": "number",
                "operation": "equals"
              },
              "leftValue": "={{ $json.message.chat.id }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "6ce435ca-7ee5-4b6f-9b17-68eaa70b344e",
      "name": "AI \u2014 Detect Expense Message",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -2256,
        256
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-nano",
          "cachedResultName": "GPT-4.1-NANO"
        },
        "options": {},
        "responses": {
          "values": [
            {
              "content": "=You are an EXPENSE EXTRACTION assistant.\n\nYour task:\nAnalyze a chat message and decide whether it contains an EXPENSE.\n\n---\n\nOUTPUT FORMAT (STRICT JSON ONLY)\n\nReturn ONLY one JSON object with EXACTLY these fields:\n\n{\n\"is_expense\": boolean,\n}\n\nNever add explanations.\nNever add additional fields.\n----------------------------\n\nDEFINITION OF AN EXPENSE\n\nAn expense MUST include:\n\n* a description (what money was spent on)\n* a numeric amount\n\nIf one of these is missing \u2192 it is NOT an expense.\n\n---\n\nFIELD RULES\n\nis_expense\n\n* true if message clearly contains a payment or spending\n* otherwise false\n\n\n---\n\nLANGUAGE\n\nMessages may be German or English.\nExtract meaning naturally.\n\n---\n\n\n\n\nPROCESSING LOGIC\n\n1. First decide if it is an expense.\n2. If NOT an expense, return:\n\n{\n\"is_expense\": false\n}\n\n3. If it IS an expense, return:\n\n{\n\"is_expense\": false\n}\n\n---\n\nCHAT MESSAGE:\n{{ $json.message.text }}\n"
            }
          ]
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "2a9ec1ab-070b-4317-9992-154b1aa8ee1b",
      "name": "Parse \u2014 Expense Detection Output",
      "type": "n8n-nodes-base.set",
      "position": [
        -1968,
        256
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={{ $('AI \u2014 Detect Expense Message').item.json.output[0].content[0].text }}"
      },
      "typeVersion": 3.4
    },
    {
      "id": "aa04206a-f3d1-4470-98e4-4e76a833d093",
      "name": "Filter \u2014 Only Continue If Expense",
      "type": "n8n-nodes-base.if",
      "position": [
        -1776,
        256
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "32b37996-d536-4142-98f3-8d1cb436ed80",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.is_expense }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "1251654a-de72-4bcd-a548-6416280cfb55",
      "name": "Sheets \u2014 Load Existing Categories",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -784,
        240
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "",
          "cachedResultName": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "5f1bda9e-fbdd-46e6-ace5-e6afac19c0ca",
      "name": "Format \u2014 Build Category Prompt",
      "type": "n8n-nodes-base.code",
      "position": [
        -544,
        240
      ],
      "parameters": {
        "jsCode": "const rows = $input.all();\n\n// Header hilft dem Modell die Struktur zu verstehen\nlet categoryText = \"Each line is one category in this format:\\n[Category] | Description | Examples\\n\\n\";\n\nfor (const item of rows) {\n  const c = item.json.category;\n  const d = item.json.description;\n  const e = item.json.examples;\n\n  // Kategorien mit [] markieren (stabileres Parsing f\u00fcrs LLM)\n  categoryText += `[${c}] | ${d} | ${e}\\n`;\n}\n\nreturn [\n  {\n    json: {\n      category_prompt: categoryText.trim()\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "a5f1180a-966f-487e-a303-ddc574085a42",
      "name": "AI \u2014 Classify Expense Category",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -320,
        240
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini",
          "cachedResultName": "GPT-4.1-MINI"
        },
        "options": {},
        "responses": {
          "values": [
            {
              "role": "system",
              "content": "You are a strict classification engine.\n\nYou receive category lines in this format:\n[Category] | Description | Examples\n\nTask:\n1) Check if the message belongs to ANY existing category.\n2) If YES \u2192 return match:true.\n3) If NO \u2192 propose ONE new category.\n\nRules:\n- Be concise.\n- Use similar style as existing categories.\n- Return ONLY valid JSON.\n- No markdown.\n- No explanations.\n\nOutput schema:\n{\n  \"match\": boolean,\n  \"suggested_category\": string | null,\n  \"description\": string | null,\n  \"examples\": string | null\n}"
            },
            {
              "content": "=CATEGORIES:\n{{$json.category_prompt}}\n\nMESSAGE:\n{{ $node[\"Security \u2014 Allow Approved Chat IDs\"].json.message.text }}"
            }
          ]
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "b2ea33a5-d737-4f38-8e71-8cf4cebfe70b",
      "name": "Parse \u2014 Category Classification Output",
      "type": "n8n-nodes-base.set",
      "position": [
        32,
        240
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={{ $json.output[0].content[0].text }}"
      },
      "typeVersion": 3.4
    },
    {
      "id": "81ffab19-c6eb-4e75-bc26-2001085d2f9c",
      "name": "Decision \u2014 Category Exists?",
      "type": "n8n-nodes-base.if",
      "position": [
        240,
        240
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "61384aec-0483-48ab-92a8-b49385e3a581",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.match }}",
              "rightValue": false
            }
          ]
        }
      },
      "typeVersion": 2.3,
      "alwaysOutputData": false
    },
    {
      "id": "e9b20118-56f8-415e-92e0-4d2aba77d3ed",
      "name": "Telegram \u2014 Ask To Create New Category",
      "type": "n8n-nodes-base.telegram",
      "position": [
        416,
        32
      ],
      "parameters": {
        "chatId": "={{ $node[\"Security \u2014 Allow Approved Chat IDs\"].json.message.chat.id }}",
        "message": "=\ud83e\udd16 **New category detected**\n\nI couldn\u2019t match this expense to any existing category.\nWould you like to create the following new category?\n\n**\ud83d\udcc2 Name:** {{$json.suggested_category}}\n**\ud83d\udcdd Description:** {{$json.description}}\n**\ud83d\udca1 Examples:** {{$json.examples}}\n\nPlease choose:\n\n\u2705 **YES** \u2013 Create category\n\u274c **NO** \u2013 Discard",
        "options": {
          "appendAttribution": false
        },
        "operation": "sendAndWait",
        "approvalOptions": {
          "values": {
            "approvalType": "double",
            "approveLabel": "\u2705 Yes",
            "disapproveLabel": "\u274c No"
          }
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "28e02ea5-9ef6-4597-a281-752219d2f38a",
      "name": "Decision \u2014 Category Approved?",
      "type": "n8n-nodes-base.if",
      "position": [
        640,
        32
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "599fb7c5-ab7a-4199-a7c8-fa515a39a088",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.data.approved }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "481ec102-a6dc-426c-85c9-75b76b4f27cc",
      "name": "Telegram \u2014 Edit New Category Form",
      "type": "n8n-nodes-base.telegram",
      "position": [
        912,
        128
      ],
      "parameters": {
        "chatId": "={{ $node[\"Security \u2014 Allow Approved Chat IDs\"].json.message.chat.id }}",
        "message": "=\ud83d\udcdd Please fill out the form for the new category",
        "options": {
          "appendAttribution": false
        },
        "operation": "sendAndWait",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Category",
              "placeholder": "={{ $('Decision \u2014 Category Exists?').item.json.suggested_category }}",
              "defaultValue": "={{ $('Decision \u2014 Category Exists?').item.json.suggested_category }}"
            },
            {
              "fieldLabel": "Description",
              "placeholder": "={{ $('Decision \u2014 Category Exists?').item.json.description }}",
              "defaultValue": "={{ $('Decision \u2014 Category Exists?').item.json.description }}"
            },
            {
              "fieldLabel": "Examples",
              "defaultValue": "={{ $('Decision \u2014 Category Exists?').item.json.examples }}"
            }
          ]
        },
        "responseType": "customForm"
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "4d6dd679-b641-41cf-9af7-2841831575c4",
      "name": "Sheets \u2014 Add Suggested Category",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        912,
        -64
      ],
      "parameters": {
        "columns": {
          "value": {
            "category": "={{ $('Decision \u2014 Category Exists?').item.json.suggested_category }}",
            "examples": "={{ $('Decision \u2014 Category Exists?').item.json.examples }}",
            "description": "={{ $('Decision \u2014 Category Exists?').item.json.description }}"
          },
          "schema": [
            {
              "id": "category",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "description",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "description",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "examples",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "examples",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {},
        "documentId": {}
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "b8c7f4df-4750-4de1-b1fc-e943a659ee71",
      "name": "Sheets \u2014 Add Edited Category",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1104,
        128
      ],
      "parameters": {
        "columns": {
          "value": {
            "category": "={{ $json.data.Category }}",
            "examples": "={{ $json.data.Examples }}",
            "description": "={{ $json.data.Description }}"
          },
          "schema": [
            {
              "id": "category",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "description",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "description",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "examples",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "examples",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {},
        "documentId": {}
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "a45d1f24-7cd8-4891-8836-be67cb5260f4",
      "name": "Pass Through \u2014 Category Data",
      "type": "n8n-nodes-base.code",
      "position": [
        1600,
        80
      ],
      "parameters": {
        "jsCode": "return $input.all();"
      },
      "typeVersion": 2
    },
    {
      "id": "956a4aff-8c37-4e4e-9630-38c40184b92b",
      "name": "Prepare \u2014 Category Fields (Form)",
      "type": "n8n-nodes-base.set",
      "position": [
        1280,
        128
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "2b51b13d-18b7-4e34-9151-1e736f2a5c7e",
              "name": "category",
              "type": "string",
              "value": "={{ $json.category }}"
            },
            {
              "id": "6da0578d-9562-4aa5-8187-00af912cb0ef",
              "name": "description",
              "type": "string",
              "value": "={{ $json.description }}"
            },
            {
              "id": "23b3b673-a2d7-4c7a-9e39-0b5695ed3db0",
              "name": "examples",
              "type": "string",
              "value": "={{ $json.examples }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "c317d6df-12aa-4f3e-a031-7de112c5a37a",
      "name": "Prepare \u2014 Category Fields (Auto)",
      "type": "n8n-nodes-base.set",
      "position": [
        1264,
        -64
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "8c5414ba-3769-4776-8b00-9df2551e20cb",
              "name": "category",
              "type": "string",
              "value": "={{ $json.category }}"
            },
            {
              "id": "c830b288-2921-424c-9f53-a867bb96d741",
              "name": "description",
              "type": "string",
              "value": "={{ $json.description }}"
            },
            {
              "id": "d855ab2f-a7b2-431f-89db-acb39bda70f5",
              "name": "examples",
              "type": "string",
              "value": "={{ $json.examples }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "b945c2cc-3749-4976-aadb-3f23cb22773d",
      "name": "Telegram \u2014 Category Created Confirmation",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1888,
        80
      ],
      "parameters": {
        "text": "=\u2705 **Category Created Successfully**\n\nYour new category has been added and will now be used for future expense classifications.\n\n**\ud83d\udcc2 Category:** {{ $('Pass Through \u2014 Category Data').item.json.category }}\n\n**\ud83d\udcdd Description:** {{ $('Pass Through \u2014 Category Data').item.json.description }}\n\n**\ud83d\udca1 Example: ** {{ $('Pass Through \u2014 Category Data').item.json.examples }}\n\nThanks for your confirmation \u2014 your system just got smarter \ud83d\ude80",
        "chatId": "={{ $node[\"Security \u2014 Allow Approved Chat IDs\"].json.message.chat.id }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "b5171992-70c9-4801-8971-e28efdaabe3a",
      "name": "Prepare \u2014 Updated Categories",
      "type": "n8n-nodes-base.set",
      "position": [
        2144,
        80
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ead6c399-5004-4982-b931-488c683c3eb7",
              "name": "category",
              "type": "string",
              "value": "={{ $('Pass Through \u2014 Category Data').item.json.category }}"
            },
            {
              "id": "6472df58-a527-4be1-b778-d900c370e587",
              "name": "description",
              "type": "string",
              "value": "={{ $('Pass Through \u2014 Category Data').item.json.description }}"
            },
            {
              "id": "6eeebd0c-fa44-481e-94b6-d33b687b6cbd",
              "name": "examples",
              "type": "string",
              "value": "={{ $('Pass Through \u2014 Category Data').item.json.examples }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "14787bc4-6242-4527-8244-781c6ecb9982",
      "name": "Format \u2014 Rebuild Category Prompt",
      "type": "n8n-nodes-base.code",
      "position": [
        2432,
        80
      ],
      "parameters": {
        "jsCode": "const rows = $input.all().filter(item => item.json?.category);\n\nlet categoryText = \"Each line is one category in this format:\\n[Category] | Description | Examples\\n\\n\";\n\nfor (const item of rows) {\n  const c = item.json.category ?? \"\";\n  const d = item.json.description ?? \"\";\n  const e = item.json.examples ?? \"\";\n\n  categoryText += `[${c}] | ${d} | ${e}\\n`;\n}\n\nreturn [\n  {\n    json: {\n      category_prompt: categoryText.trim()\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "06bdd6d2-5edf-49b3-809a-e38d93ddc6fd",
      "name": "Prepare \u2014 Category Prompt Payload",
      "type": "n8n-nodes-base.set",
      "position": [
        1424,
        256
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "7db24e27-69f8-4c94-a6ea-e6c4b724d4b9",
              "name": "category_prompt",
              "type": "string",
              "value": "={{ $('Format \u2014 Build Category Prompt').item.json.category_prompt }}"
            }
          ]
        }
      },
      "typeVersion": 3.4,
      "alwaysOutputData": false
    },
    {
      "id": "2d4df9a7-1811-420d-b910-55dd092de1ed",
      "name": "Merge \u2014 Expense + Categories",
      "type": "n8n-nodes-base.code",
      "position": [
        2672,
        256
      ],
      "parameters": {
        "jsCode": "return $input.all();"
      },
      "typeVersion": 2
    },
    {
      "id": "63e73043-7814-4e07-9084-3a8a45e5dfaf",
      "name": "AI \u2014 Extract Structured Expense Data",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        3552,
        256
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4",
          "cachedResultName": "GPT-4"
        },
        "options": {},
        "responses": {
          "values": [
            {
              "content": "=You are an EXPENSE EXTRACTION AGENT.\n\nYour task:\nAnalyze a chat message and extract expense data.\n\n---\n\nOUTPUT FORMAT (STRICT JSON ONLY)\n\nReturn ONLY one JSON object with EXACTLY these fields:\n\n{\n\"date\": string,\n\"amount\": float,\n\"category\": string,\n\"description\": string,\n\"common_expense\": boolean\n}\n\nNever add explanations.\nNever add additional fields.\n----------------------------\n\nFIELD RULES\n\ndate\n\n* Extract date only if mentioned\n* Format: YYYY-MM-DD\n* If missing AND is_expense = true \u2192 use TODAY_DATE\n* If is_expense = false \u2192 \"\"\n\namount\n\n* Numeric only\n* No currency symbols\n* If is_expense = false \u2192 0\n\ndescription\n\n* Short, clean object or service\n* NO numbers\n* NO date words\n* Examples: \"Pizza\", \"Taxi ride\", \"Groceries\"\n* If is_expense = false \u2192 \"\"\n\ncategory\n\n* Choose ONE category from AVAILABLE_CATEGORIES\n* If none fits \u2192 create a NEW short category label\n* If is_expense = false \u2192 \"\"\n\ncommon_expense\n\n* true ONLY if shared spending is implied\n  (\"wir\", \"geteilt\", \"split\", \"shared\", \"f\u00fcr uns\")\n* otherwise false\n\n---\n\nLANGUAGE\n\nMessages may be German or English.\nExtract meaning naturally.\n\n---\n\nINPUT VARIABLES\n\nTODAY_DATE = {{ $now.format('yyyy-MM-dd') }}\n\nAVAILABLE_CATEGORIES:\n{{ $json.category_prompt }}\n\n---\n\nPROCESSING LOGIC\n\n1. Decide if the message is an expense.\n2. If NOT an expense, return:\n\n{\n\"is_expense\": false,\n\"date\": \"\",\n\"amount\": 0,\n\"category\": \"\",\n\"description\": \"\",\n\"common_expense\": false\n}\n\n3. If it IS an expense:\n   Fill all fields according to rules.\n\n---\n\nCHAT MESSAGE:\n{{ $node[\"Security \u2014 Allow Approved Chat IDs\"].json.message.text }}"
            }
          ]
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "1355837d-9b0a-4eed-997f-1ea3ae165acf",
      "name": "Parse \u2014 Expense Extraction Output",
      "type": "n8n-nodes-base.set",
      "position": [
        3888,
        256
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={{ $json.output[0].content[0].text }}"
      },
      "typeVersion": 3.4
    },
    {
      "id": "d83c533d-1cf8-4fa4-83fa-f6fa16a64bf9",
      "name": "Telegram \u2014 Confirm Expense Before Save",
      "type": "n8n-nodes-base.telegram",
      "position": [
        5200,
        256
      ],
      "parameters": {
        "chatId": "={{ $node[\"Security \u2014 Allow Approved Chat IDs\"].json.message.chat.id }}",
        "message": "=Please review your entry \ud83d\udc47\n\n\ud83d\udcc5 date: {{ $json.date }}\n\ud83d\udcb0 amount: {{ $json.amount }}\n\ud83d\udcc2 category: {{ $json.category }}\n\ud83d\udcdd description: {{ $json.description }}\n\ud83d\udc65 common_expense: {{ $json.common_expense }}\n\nShould I save this data to the database?\n\n\u2705 Reply with: YES\n\u274c Reply with: NO",
        "options": {
          "appendAttribution": false
        },
        "operation": "sendAndWait",
        "approvalOptions": {
          "values": {
            "approvalType": "double",
            "approveLabel": "\u2705 YES",
            "disapproveLabel": "\u274c NO"
          }
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "d4d8ec37-2ffa-4d14-a66b-96cc97dacbb7",
      "name": "Prepare \u2014 Add Person Name",
      "type": "n8n-nodes-base.set",
      "position": [
        5392,
        256
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "d031284c-bea2-4b4f-97b4-ba55cffa9a73",
              "name": "person",
              "type": "string",
              "value": "={{ $node[\"Security \u2014 Allow Approved Chat IDs\"].json.message.from.first_name }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "2db75d68-4c1d-4050-a8ba-f9762c63f393",
      "name": "Filter \u2014 Only Approved Expenses",
      "type": "n8n-nodes-base.filter",
      "position": [
        5600,
        256
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "42845ab5-2e11-497e-8fd8-871a437511e9",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $('Telegram \u2014 Confirm Expense Before Save').item.json.data.approved }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "f880bafe-d5ff-43f3-a1b1-be47c2d4adea",
      "name": "Sheets \u2014 Save Expense",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        5792,
        256
      ],
      "parameters": {
        "columns": {
          "value": {
            "date": "={{ $('Parse \u2014 Expense Extraction Output').item.json.date }}",
            "Person": "={{ $('Prepare \u2014 Add Person Name').item.json.person }}",
            "amount": "={{ $('Parse \u2014 Expense Extraction Output').item.json.amount }}",
            "category": "={{ $('Parse \u2014 Expense Extraction Output').item.json.category }}",
            "description": "={{ $('Parse \u2014 Expense Extraction Output').item.json.description }}",
            "common_expense": "={{ $('Parse \u2014 Expense Extraction Output').item.json.common_expense }}"
          },
          "schema": [
            {
              "id": "date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "amount",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "amount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "category",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "description",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "description",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "common_expense",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "common_expense",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Person",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Person",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {},
        "documentId": {}
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "575b4efd-f1f0-4a8f-b13d-cdcbd65bec72",
      "name": "Telegram \u2014 Expense Saved Confirmation",
      "type": "n8n-nodes-base.telegram",
      "position": [
        5984,
        256
      ],
      "parameters": {
        "text": "\u2705 Your expense was successfully added!",
        "chatId": "={{ $node[\"Security \u2014 Allow Approved Chat IDs\"].json.message.chat.id }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "6dd3ad87-9410-4d23-aad6-afbe14d233be",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4560,
        -928
      ],
      "parameters": {
        "color": 7,
        "width": 736,
        "height": 1424,
        "content": "## HOW IT WORKS \u2014 AI TELEGRAM EXPENSE TRACKER\n\nThis workflow automatically converts Telegram messages into structured expenses using AI.\n\nInstead of filling out forms, simply send a natural message like:\n\nGroceries 23\u20ac yesterday\n\nThe workflow analyzes, validates, and stores your expense step by step.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\ud83d\udd04 **WORKFLOW OVERVIEW**\n\n\ud83d\udfe9 **Input & Security**\nA Telegram message is received and validated against approved Chat IDs.\n\n\ud83d\udfe6 **AI Detection Layer**\nAI checks whether the message contains a real expense.\nNon-financial messages are ignored.\n\n\ud83d\udfe8 **Category Intelligence**\nExisting categories are loaded and compared.\nIf no match is found, the workflow can suggest and learn new categories.\n\n\ud83d\udfea **Expense Extraction**\nAI extracts structured data such as date, amount, category, and description.\n\n\ud83d\udfe5 **Approval & Save**\nYou confirm the result before it is saved to Google Sheets.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\ud83d\udccb **WHAT YOU NEED**\n\nCreate two Google Sheets before using this workflow.\n\nEXPENSES\nColumns:\ndate, amount, category, description, common_expense, Person\n\nEXPENSE_CATEGORIES\nColumns:\ncategory, description, examples\n\nThe workflow will read and append data automatically.\n\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\ud83d\udca1 **KEY FEATURES**\n\n\u2022 AI-powered expense detection\n\u2022 Self-learning category system\n\u2022 Human approval before saving\n\u2022 Works with natural language (German & English)\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\u2b50 **MULTI-USER HINT**\n\nThis workflow is designed for multiple people.\n\nYou can add several Chat IDs in the\nSecurity \u2014 Allow Approved Chat IDs node\nso different users can track expenses at the same time.\n\nEach entry is tagged with the person who sent the message.\n\nExpenses can be marked as shared or personal.\nShared expenses are stored in the common_expense column as a boolean value (true/false).\n\nBy default, expenses are treated as personal unless shared spending is detected."
      },
      "typeVersion": 1
    },
    {
      "id": "59d9a681-502e-489e-a931-1b314eddb7a4",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3568,
        -80
      ],
      "parameters": {
        "color": 4,
        "width": 704,
        "height": 576,
        "content": "## INPUT\nWhen a new Telegram message is received, the workflow first performs a **security validation**.\nOnly messages from approved Telegram users are allowed to continue.\n\nThis prevents unauthorized users from triggering your expense automation.\n\n\n\n\u26a0\ufe0f **Action Required**\n-> Add your Telegram Credentials to the following Nodes:\n   **Receive Message**\n-> Replace Telegram Chat IDs in following Security Nodes:\n**Allow Approved Chat IDs**"
      },
      "typeVersion": 1
    },
    {
      "id": "3445cc94-ae89-486c-9a86-9fe37e1ba310",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2736,
        -80
      ],
      "parameters": {
        "color": 5,
        "width": 1136,
        "height": 576,
        "content": "## AI DETECTION LAYER\nThis section uses AI to determine whether an incoming Telegram message actually contains an expense.\n\nInstead of processing every message, the workflow first performs a lightweight classification step to reduce errors and unnecessary processing.\n\n**1. AI \u2014 Detect Expense Message**\nThe AI analyzes the text and decides if it represents a real expense.\n\n**2. Parse \u2014 Expense Detection Output**\nThe AI response is converted into structured JSON data.\n\n**3. Filter \u2014 Only Continue If Expense**\nThe workflow only continues if is_expense = true.\n\n\n\u26a0\ufe0f **Action Required**\n\n--> Add your AI Credentials to the following Node:\n   **Detect Expense Message**"
      },
      "typeVersion": 1
    },
    {
      "id": "7504dec5-0779-4f08-bb40-359d1e5a9ba3",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1552,
        -80
      ],
      "parameters": {
        "width": 4496,
        "height": 576,
        "content": "## CATEGORY INTELLIGENCE - Smart Category Handling\nThis section ensures that every expense is assigned to the correct category.\n\nFirst, the workflow loads your existing categories from Google Sheets and formats them into an AI-readable structure.\nThe AI then checks whether the new expense fits an existing category.\n\nIf no match is found, the workflow proposes a new category and asks for confirmation before adding it to your category list.\n\n\n\n\ud83d\udd0e **What happens here?**\n-> Load existing categories from Sheets\n-> AI compares the message against known categories\n-> If no match is found, AI suggests a new category (name, description, examples)\n-> You can approve the suggestion or create your own category via form\n-> New categories are added to your system and used automatically in future expenses\n\n\n\n\n\n\u26a0\ufe0f **Action Required**\n-> Connect to the Sheet that contains the category data in following Sheet Nodes: \n     **Load Existing Categories, Add Suggested Category, Add Edited Category**\n\n-> Add your Telegram Credentials to the following Nodes:\n   **Ask To Create New Category,  Edit New Category Form, Category Created Confirmation**\n\n--> Add your AI Credentials to the following Node:\n   **Classify Expense Category**"
      },
      "typeVersion": 1
    },
    {
      "id": "f154793b-d1e9-42c5-8f13-21edb177b88e",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3136,
        -80
      ],
      "parameters": {
        "color": 6,
        "width": 1008,
        "height": 576,
        "content": "## EXPENSE EXTRACTION\nIn this section, the workflow uses AI to transform the detected expense message into structured data.\n\nThe AI receives the original chat message together with the current category list and extracts all relevant expense information in a strict JSON format.\n\n\ud83d\udd0e **What happens here?**\n-> Merge message data with available categories\n-> AI extracts structured fields (date, amount, category, description, shared expense)\n-> Output is converted into clean JSON for further processing\n-> This step converts natural language into database-ready expense records.\n\n\n\u26a0\ufe0f **Action Required**\n--> Add your AI Credentials to the following Node:\n   **Extract Structured Expense Data**"
      },
      "typeVersion": 1
    },
    {
      "id": "54a789a8-ea82-4833-a0c2-1127fd2459e2",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4592,
        -80
      ],
      "parameters": {
        "color": 3,
        "width": 1584,
        "height": 576,
        "content": "## APPROVAL & SAVE\n\nBefore saving the expense, the workflow asks the user for final confirmation via Telegram.\nThis ensures that extracted data is correct and prevents incorrect entries from being stored.\n\nOnly approved expenses are written to Google Sheets and confirmed back to the user.\n\n\n\ud83d\udd0e **What happens here?**\n-> Show extracted expense data for review\n-> Wait for user approval\n-> Add user information to the record\n-> Save the expense to Google Sheets\n-> Send a confirmation message\n\nThis step adds a human-in-the-loop safety layer before data is stored permanently.\n\n\n\n\n\n\u26a0\ufe0f **Action Required**\n-> Connect to the Sheet that contains the category data in following Sheet Nodes: \n     **Save Expense**\n\n-> Add your Telegram Credentials to the following Nodes:\n   **Confirm Expense Before Save, Expense Saved Confirmation**"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "",
  "connections": {
    "Sheets \u2014 Save Expense": {
      "main": [
        [
          {
            "node": "Telegram \u2014 Expense Saved Confirmation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram - Receive Message": {
      "main": [
        [
          {
            "node": "Security \u2014 Allow Approved Chat IDs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare \u2014 Add Person Name": {
      "main": [
        [
          {
            "node": "Filter \u2014 Only Approved Expenses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI \u2014 Detect Expense Message": {
      "main": [
        [
          {
            "node": "Parse \u2014 Expense Detection Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decision \u2014 Category Exists?": {
      "main": [
        [
          {
            "node": "Telegram \u2014 Ask To Create New Category",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare \u2014 Category Prompt Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge \u2014 Expense + Categories": {
      "main": [
        [
          {
            "node": "AI \u2014 Extract Structured Expense Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pass Through \u2014 Category Data": {
      "main": [
        [
          {
            "node": "Telegram \u2014 Category Created Confirmation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare \u2014 Updated Categories": {
      "main": [
        [
          {
            "node": "Format \u2014 Rebuild Category Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets \u2014 Add Edited Category": {
      "main": [
        [
          {
            "node": "Prepare \u2014 Category Fields (Form)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decision \u2014 Category Approved?": {
      "main": [
        [
          {
            "node": "Sheets \u2014 Add Suggested Category",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Telegram \u2014 Edit New Category Form",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI \u2014 Classify Expense Category": {
      "main": [
        [
          {
            "node": "Parse \u2014 Category Classification Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format \u2014 Build Category Prompt": {
      "main": [
        [
          {
            "node": "AI \u2014 Classify Expense Category",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter \u2014 Only Approved Expenses": {
      "main": [
        [
          {
            "node": "Sheets \u2014 Save Expense",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets \u2014 Add Suggested Category": {
      "main": [
        [
          {
            "node": "Prepare \u2014 Category Fields (Auto)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format \u2014 Rebuild Category Prompt": {
      "main": [
        [
          {
            "node": "Merge \u2014 Expense + Categories",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse \u2014 Expense Detection Output": {
      "main": [
        [
          {
            "node": "Filter \u2014 Only Continue If Expense",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare \u2014 Category Fields (Auto)": {
      "main": [
        [
          {
            "node": "Pass Through \u2014 Category Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare \u2014 Category Fields (Form)": {
      "main": [
        [
          {
            "node": "Pass Through \u2014 Category Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter \u2014 Only Continue If Expense": {
      "main": [
        [
          {
            "node": "Sheets \u2014 Load Existing Categories",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse \u2014 Expense Extraction Output": {
      "main": [
        [
          {
            "node": "Telegram \u2014 Confirm Expense Before Save",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare \u2014 Category Prompt Payload": {
      "main": [
        [
          {
            "node": "Merge \u2014 Expense + Categories",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets \u2014 Load Existing Categories": {
      "main": [
        [
          {
            "node": "Format \u2014 Build Category Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram \u2014 Edit New Category Form": {
      "main": [
        [
          {
            "node": "Sheets \u2014 Add Edited Category",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Security \u2014 Allow Approved Chat IDs": {
      "main": [
        [
          {
            "node": "AI \u2014 Detect Expense Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI \u2014 Extract Structured Expense Data": {
      "main": [
        [
          {
            "node": "Parse \u2014 Expense Extraction Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram \u2014 Ask To Create New Category": {
      "main": [
        [
          {
            "node": "Decision \u2014 Category Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse \u2014 Category Classification Output": {
      "main": [
        [
          {
            "node": "Decision \u2014 Category Exists?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram \u2014 Confirm Expense Before Save": {
      "main": [
        [
          {
            "node": "Prepare \u2014 Add Person Name",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram \u2014 Category Created Confirmation": {
      "main": [
        [
          {
            "node": "Prepare \u2014 Updated Categories",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

πŸ’Έ HOW IT WORKS β€” AI TELEGRAM EXPENSE TRACKER

Source: https://n8n.io/workflows/13667/ β€” original creator credit. Request a take-down β†’

More AI & RAG workflows β†’ Β· Browse all categories β†’

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Ask questions like β€œHow much did I spend on food last month?” and get instant answers from your financial data β€” directly in Telegram.

Telegram Trigger, OpenAI, Google Sheets +2
AI & RAG

&gt; ⚠️ Disclaimer: This workflow uses Community Nodes and must be run on a self-hosted instance of n8n.

HTTP Request, Telegram Trigger, Telegram +2
AI & RAG

Viral Tik Tok Clone Finder. Uses httpRequest, telegramTrigger, openAi, googleSheets. Event-driven trigger; 41 nodes.

HTTP Request, Telegram Trigger, OpenAI +2
AI & RAG

This workflow is designed for content creators, agencies, influencers, and automation builders who want to transform viral videos into personalized avatar-based edits β€” and automatically publish them

Telegram Trigger, Telegram, HTTP Request +3
AI & RAG

Send a target niche and location via Telegram message Workflow discovers businesses via Google Maps API AI enriches contacts with email and LinkedIn data via Serper GPT-4o scores and qualifies each le

Telegram Trigger, OpenAI, Google Sheets +3