Log expenses by texting a Telegram bot
Send "12.50 lunch" to your bot and it's saved to a Google Sheet instantly.
How it works
Trigger: Telegram — new message to your bot
- Receive the message from your Telegram bot.
- Read the amount and note from text like "taxi 40".
- If it's valid, add a row to your "Expenses" sheet and confirm with ✅.
- If not, reply with an example of the right format.
Diagram
Trigger App Logic Code
Code
JavaScript used in the Code step — already included in the template below.
// Understands messages like "12.50 lunch", "taxi 40" or "coffee 3,5".
const msg = $input.first().json.message;
const text = (msg.text || "").trim();
const match = text.match(/\d+(?:[.,]\d+)?/);
if (!match) {
return [{ json: { valid: false, chatId: msg.chat.id } }];
}
const amount = parseFloat(match[0].replace(",", "."));
const note = text.replace(match[0], "").trim() || "expense";
return [{
json: {
valid: true,
chatId: msg.chat.id,
date: new Date(msg.date * 1000).toISOString().slice(0, 10),
amount,
note,
},
}];Build it
Pick your tool — same automation, step by step.
Copy and paste straight into an n8n canvas (Ctrl+V), or download and use Import from File. Then connect your own credentials and replace every YOUR_… placeholder.
Show code (210 lines)
{
"name": "Log expenses by texting a Telegram bot (Flowngn)",
"nodes": [
{
"parameters": {
"content": "## Log expenses by texting a Telegram bot\nSend \"12.50 lunch\" to your bot and it's saved to a Google Sheet instantly.\n\n**Setup**\n1. Open each node and connect your own credentials.\n2. Replace every YOUR_… placeholder.\n3. Test once, then activate.\n\nFree template from Flowngn",
"height": 260,
"width": 340
},
"id": "3da33d1f-b8e3-42d1-aace-213b83d434e0",
"name": "Setup notes",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-420,
-60
]
},
{
"parameters": {
"updates": [
"message"
],
"additionalFields": {}
},
"id": "3da33d1f-b8e3-42d1-aace-213b83d434e0",
"name": "New message",
"type": "n8n-nodes-base.telegramTrigger",
"typeVersion": 1.1,
"position": [
0,
0
],
"webhookId": "3da33d1f-b8e3-42d1-aace-213b83d434e0"
},
{
"parameters": {
"jsCode": "// Understands messages like \"12.50 lunch\", \"taxi 40\" or \"coffee 3,5\".\nconst msg = $input.first().json.message;\nconst text = (msg.text || \"\").trim();\nconst match = text.match(/\\d+(?:[.,]\\d+)?/);\n\nif (!match) {\n return [{ json: { valid: false, chatId: msg.chat.id } }];\n}\n\nconst amount = parseFloat(match[0].replace(\",\", \".\"));\nconst note = text.replace(match[0], \"\").trim() || \"expense\";\n\nreturn [{\n json: {\n valid: true,\n chatId: msg.chat.id,\n date: new Date(msg.date * 1000).toISOString().slice(0, 10),\n amount,\n note,\n },\n}];"
},
"id": "3da33d1f-b8e3-42d1-aace-213b83d434e0",
"name": "Parse expense",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
300,
0
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.valid }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "3da33d1f-b8e3-42d1-aace-213b83d434e0",
"name": "Valid expense?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
600,
0
]
},
{
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"value": "YOUR_SPREADSHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "Expenses",
"mode": "name"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"date": "={{ $json.date }}",
"amount": "={{ $json.amount }}",
"note": "={{ $json.note }}"
}
},
"options": {}
},
"id": "3da33d1f-b8e3-42d1-aace-213b83d434e0",
"name": "Add to Expenses",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
900,
0
]
},
{
"parameters": {
"chatId": "={{ $('Parse expense').item.json.chatId }}",
"text": "=✅ Saved {{ $('Parse expense').item.json.amount }} — {{ $('Parse expense').item.json.note }}",
"additionalFields": {
"appendAttribution": false
}
},
"id": "3da33d1f-b8e3-42d1-aace-213b83d434e0",
"name": "Confirm",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
1200,
0
]
},
{
"parameters": {
"chatId": "={{ $json.chatId }}",
"text": "Send an amount and a note, for example \"12.50 lunch\" or \"taxi 40\".",
"additionalFields": {
"appendAttribution": false
}
},
"id": "3da33d1f-b8e3-42d1-aace-213b83d434e0",
"name": "Explain format",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
900,
220
]
}
],
"connections": {
"New message": {
"main": [
[
{
"node": "Parse expense",
"type": "main",
"index": 0
}
]
]
},
"Parse expense": {
"main": [
[
{
"node": "Valid expense?",
"type": "main",
"index": 0
}
]
]
},
"Valid expense?": {
"main": [
[
{
"node": "Add to Expenses",
"type": "main",
"index": 0
}
],
[
{
"node": "Explain format",
"type": "main",
"index": 0
}
]
]
},
"Add to Expenses": {
"main": [
[
{
"node": "Confirm",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"meta": {
"templateCredsSetupCompleted": false
},
"tags": []
}In Make, create a new scenario and add these modules in order:
- Telegram Bot › Watch Updates
- Tools › Set variable / Iterator + Array aggregator (same logic as the Code step)
- Router with two routes: “Valid expense?” (yes / no filters)
- If yesGoogle Sheets › Add a Row
- If noTelegram Bot › Send a Text Message or a Reply
- If yesTelegram Bot › Send a Text Message or a Reply
- Every module run uses Make operations — the free plan includes a monthly quota.
- Make's built-in functions (map, sum, formatDate…) can replace most Code steps; for complex logic use a code module.
- Module and step names can differ slightly as Make and Zapier update their apps.
In Zapier, create a new Zap and add these steps in order:
- Telegram — New Message
- Code by Zapier — Run JavaScript (paste the code from the Code section)
- Paths by Zapier: “Valid expense?” (yes / no)
- If yesGoogle Sheets — Create Spreadsheet Row
- If noTelegram — Send Message
- If yesTelegram — Send Message
- Zaps with more than two steps need a paid Zapier plan.
- Webhooks, Filters/Paths and Code by Zapier are premium features on some Zapier plans.
- Code by Zapier runs JavaScript: reuse the code shown above, reading inputs from inputData instead of $json.
- Module and step names can differ slightly as Make and Zapier update their apps.
Tips
- Add a monthly Schedule flow that totals the sheet and texts you the summary.
Related flows
Finance & AdminEasy
Subscription renewal reminders on WhatsApp
Seven days before a client's subscription or membership ends, remind them on WhatsApp and tell your team.
Google Sheets → WhatsApp → Telegram5h/mo
Finance & AdminEasy
Weekly cash flow summary on Telegram
Every Sunday, see money in, money out, net and your biggest expenses of the week on Telegram.
Google Sheets → Telegram2h/mo
Finance & AdminEasy
Instant alert when a Stripe payment fails
Know about failed payments right away and log them for follow-up.
Stripe → Slack → Google Sheets2h/mo