AutomationFlowsEmail & Gmail › Track Multi-currency Expenses From Receipts with Easybits, Telegram, and…

Track Multi-currency Expenses From Receipts with Easybits, Telegram, and…

Original n8n title: Track Multi-currency Expenses From Receipts with Easybits, Telegram, and Google Sheets

ByFelix @easybits on n8n.io

This workflow automates multi-currency expense tracking via Telegram. Send a receipt photo to your bot, and it automatically extracts the invoice details, converts the amount to EUR using a live exchange rate, and logs everything straight into Google Sheets.

Event trigger★★★★☆ complexity17 nodesTelegram TriggerHTTP RequestGoogle Sheets
Email & Gmail Trigger: Event Nodes: 17 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #14133 — we link there as the canonical source.

This workflow follows the Google Sheets → HTTP Request 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": "vgeuOiTcvnx5L4Fz",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Receipt-to-Sheet: Multi-Currency Expense Tracker with easybits & Telegram",
  "tags": [],
  "nodes": [
    {
      "id": "e459b32e-a5e3-4904-a765-ee29834f52a6",
      "name": "Telegram: Receipt Photo",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        -1168,
        128
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {
          "download": true
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "d6646126-92d6-4b74-9d23-d63564651176",
      "name": "Parse AI Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -224,
        128
      ],
      "parameters": {
        "jsCode": "// Safely capture the extracted data from easybits Extractor\nlet data = $input.first().json.data;\n\n// Sometimes AI returns it as a string, sometimes as an object\nif (typeof data === 'string') {\n  data = JSON.parse(data);\n}\n\nreturn {\n  invoice_number: data.invoice_number || \"UNKNOWN-ID\",\n  currency: data.currency || \"USD\",\n  amount: data.amount || 0\n};"
      },
      "typeVersion": 1
    },
    {
      "id": "bc9ab6e5-2908-49e9-8763-6d14156626e1",
      "name": "Get Exchange Rate (Primary)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        32,
        128
      ],
      "parameters": {
        "url": "=https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/{{ $json.currency.toLowerCase() }}.json",
        "options": {}
      },
      "typeVersion": 4.1
    },
    {
      "id": "33d25f7b-2d15-4fb7-a94e-24eab201a24c",
      "name": "Did Primary Fail?",
      "type": "n8n-nodes-base.if",
      "position": [
        208,
        128
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.error }}",
              "operation": "isNotEmpty"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "0a79dd9a-b86f-41eb-a334-33512d9db556",
      "name": "Fallback API (Cloudflare)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        432,
        32
      ],
      "parameters": {
        "url": "=https://latest.currency-api.pages.dev/v1/currencies/{{ $node[\"Parse AI Data\"].json.currency.toLowerCase() }}.json",
        "options": {}
      },
      "typeVersion": 4.1
    },
    {
      "id": "56343103-fbc4-4256-8a71-8ea985c88f5a",
      "name": "Calculate EUR",
      "type": "n8n-nodes-base.code",
      "position": [
        672,
        144
      ],
      "parameters": {
        "jsCode": "const invoice = $node[\"Parse AI Data\"].json;\nconst baseCurrency = invoice.currency.toLowerCase();\n\n// Capture the API response from whichever node succeeded\nconst rateData = $input.all()[0].json;\n\n// Extract the EUR rate dynamically\nconst eurRate = rateData[baseCurrency].eur;\nconst amountInEur = invoice.amount * eurRate;\n\nreturn {\n  invoice_no: invoice.invoice_number,\n  original_amount: invoice.amount,\n  original_currency: invoice.currency.toUpperCase(),\n  exchange_rate: eurRate,\n  amount_eur: amountInEur.toFixed(2)\n};"
      },
      "typeVersion": 1
    },
    {
      "id": "c7c9218e-fa67-48ed-b9ac-cf3824d57bd8",
      "name": "Append row in sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        944,
        144
      ],
      "parameters": {
        "columns": {
          "value": {
            "Currency": "={{ $json.original_currency }}",
            "Exchange Rate": "={{ $json.exchange_rate }}",
            "Invoice Number": "={{ $json.invoice_no }}",
            "Original Amount": "={{ $json.original_amount }}",
            "Final Amount (EUR)": "={{ $json.amount_eur }}"
          },
          "schema": [
            {
              "id": "Invoice Number",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Invoice Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Original Amount",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Original Amount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Currency",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Currency",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Exchange Rate",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Exchange Rate",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Final Amount (EUR)",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Final Amount (EUR)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SPREADSHEET_ID",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID",
          "cachedResultName": "Master Finance File"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "a38f9508-630c-42fc-be49-11e02b8910ed",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1232,
        -96
      ],
      "parameters": {
        "color": 7,
        "height": 400,
        "content": "## \ud83d\udce5 Entry Point\nListens for incoming Telegram messages. The user is expected to send a photo of a receipt or invoice. Make sure \"Download\" is enabled so the image binary is passed forward."
      },
      "typeVersion": 1
    },
    {
      "id": "41f5e5a0-c4f1-4edf-b977-33ba32090448",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -960,
        -96
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 400,
        "content": "## \ud83e\udd16 easybits' Data Extraction\nSends the receipt image to the easybits pipeline for data extraction. Returns structured data under `json.data` containing `invoice_number`, `currency`, and `amount`."
      },
      "typeVersion": 1
    },
    {
      "id": "c039e942-e541-4e9f-8015-a8beec3bdee1",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -288,
        -96
      ],
      "parameters": {
        "color": 7,
        "height": 400,
        "content": "## \ud83e\uddf9 Data Normalisation\nCleans and standardises the extracted fields. Provides fallback defaults in case the AI missed a field (e.g. unknown invoice number, USD as default currency)."
      },
      "typeVersion": 1
    },
    {
      "id": "16f141e1-95f9-42a3-b4b6-4e4772c9903c",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -16,
        -96
      ],
      "parameters": {
        "color": 7,
        "width": 592,
        "height": 400,
        "content": "## \ud83c\udf10 Exchange Rate Fetch (with Fallback)\nTries the primary jsDelivr-hosted currency API first. If it fails, the error output automatically reroutes to the Cloudflare fallback API. Both return the same response structure so `Calculate EUR` works identically either way."
      },
      "typeVersion": 1
    },
    {
      "id": "22a22e36-3ab9-4912-8cc9-cddf055ba528",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        608,
        -96
      ],
      "parameters": {
        "color": 7,
        "height": 400,
        "content": "## \ud83e\uddee Currency Conversion\nReads the exchange rate from whichever API succeeded and multiplies it by the original invoice amount to produce the final EUR value. Result is rounded to 2 decimal places."
      },
      "typeVersion": 1
    },
    {
      "id": "1254c1bf-c9c9-4cbf-9ba6-2fe750f02c73",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        880,
        -96
      ],
      "parameters": {
        "color": 7,
        "height": 400,
        "content": "## \ud83d\udcca Google Sheets Logging\nAppends one row per invoice to the Master Finance File. Maps `invoice_no` \u2192 Vendor Name and `amount_eur` \u2192 Overall Due. Make sure the sheet columns exist before running."
      },
      "typeVersion": 1
    },
    {
      "id": "d0e0f653-0064-4a7c-ba5b-ceeceeda4287",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1952,
        -784
      ],
      "parameters": {
        "width": 688,
        "height": 1808,
        "content": "# \ud83d\udcb1 Currency Converter Assistant\n\n## How It Works\n\nThis workflow automates multi-currency expense tracking via Telegram. Send a receipt photo to your bot, and it automatically extracts the invoice details, converts the amount to EUR using a live exchange rate, and logs everything straight into Google Sheets.\n\n**Flow overview:**\n1. User sends a receipt photo via Telegram\n2. easybits Extractor reads the document and returns structured data\n3. The data is normalised and cleaned\n4. The exchange rate is fetched (with fallback if needed)\n5. The amount is converted to EUR\n6. The result is appended to Google Sheets\n\n---\n\n## Step-by-Step Setup Guide\n\n### 1. Set Up Your easybits Extractor Pipeline\n\nBefore connecting this workflow, you need a configured extraction pipeline on easybits.\n\n1. Go to [extractor.easybits.tech](https://extractor.easybits.tech) and click **\"Create a Pipeline\"**.\n2. Fill in the **Pipeline Name** and **Description** \u2013 describe the type of document you're processing (e.g. \"Invoice / Receipt\").\n3. Upload a **sample receipt or invoice** as your reference document.\n4. Click **\"Map Fields\"** and define the following fields to extract:\n\n   - **`invoice_number`** (String) \u2013 The unique identifier of the invoice, e.g. `INV-20240301`\n   - **`currency`** (String) \u2013 The currency code found on the invoice, e.g. `USD`\n   - **`amount`** (Number) \u2013 The total amount due on the invoice, e.g. `149.99`\n\n5. Click **\"Save & Test Pipeline\"** in the Test tab to verify the extraction works correctly.\n\n---\n\n### 2. Connect the easybits Node in n8n\n\n1. Once you have finalized your pipeline, go back to your dashboard and click **Pipelines** in the left sidebar.\n2. Click **\"View Pipeline\"** on the pipeline you want to connect.\n3. On the Pipeline Details page, you will find:\n   - **API URL:** `https://extractor.easybits.tech/api/pipelines/[YOUR_PIPELINE_ID]`\n   - **API Key:** Your unique authentication token\n4. Copy both values and integrate them into the **\"easybits Extractor\"** HTTP Request node in the workflow.\n\n> **To keep in mind:** Each pipeline has its own API Key and Pipeline ID. If you have multiple pipelines (for example, one for receipts and one for invoices), you will need separate credentials for each.\n\n> **Important:** When adding your API Key, set the **Credential Type** to **Bearer Auth** and paste your API Key as the **Bearer Token** value.\n\n---\n\n### 3. Connect Your Telegram Bot\n\n1. Open the **Telegram: Receipt Photo** node.\n2. Connect your Telegram Bot credentials (Bot Token from [@BotFather](https://t.me/BotFather)).\n3. Make sure **\"Download\"** is enabled under Additional Fields so the image binary is forwarded correctly.\n\n---\n\n### 4. Connect Google Sheets\n\n1. Open the **Append row in sheet** node.\n2. Connect your Google Sheets account via OAuth2.\n3. Select your target spreadsheet and sheet.\n4. Make sure your sheet has at least these two columns: **Vendor Name** and **Overall Due**.\n\n---\n\n### 5. Activate the Workflow\n\n1. Click the **\"Active\"** toggle in the top-right corner of n8n to enable the workflow.\n2. Send a receipt photo to your Telegram bot to test it end to end.\n3. Check your Google Sheet \u2013 a new row with the invoice reference and EUR amount should appear."
      },
      "typeVersion": 1
    },
    {
      "id": "9bc5061a-50fa-4046-9a39-62e9f4737fc8",
      "name": "Extract from File",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        -896,
        128
      ],
      "parameters": {
        "options": {},
        "operation": "binaryToPropery",
        "binaryPropertyName": "=data"
      },
      "typeVersion": 1.1
    },
    {
      "id": "6aea2c8a-0a7a-4f6a-9b1b-02a931812f0a",
      "name": "Edit Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        -688,
        128
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "6acf44d4-9793-44dc-a9a8-bd4f357cc21f",
              "name": "data",
              "type": "string",
              "value": "=data:image/png;base64,{{ $json.data }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "329c1dde-101c-41ce-83b4-607052cf3abb",
      "name": "HTTP Request",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -480,
        128
      ],
      "parameters": {
        "url": "https://extractor.easybits.tech/api/pipelines/YOUR_PIPELINE_ID",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"files\": [\n    \"{{ $json.data }}\"\n  ]\n} ",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "httpBearerAuth"
      },
      "credentials": {
        "httpBearerAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.3
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "def2411d-dbdb-44e8-a78f-87a3f9573bcf",
  "connections": {
    "Edit Fields": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request": {
      "main": [
        [
          {
            "node": "Parse AI Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate EUR": {
      "main": [
        [
          {
            "node": "Append row in sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Data": {
      "main": [
        [
          {
            "node": "Get Exchange Rate (Primary)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Did Primary Fail?": {
      "main": [
        [
          {
            "node": "Fallback API (Cloudflare)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Calculate EUR",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract from File": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram: Receipt Photo": {
      "main": [
        [
          {
            "node": "Extract from File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fallback API (Cloudflare)": {
      "main": [
        [
          {
            "node": "Calculate EUR",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Exchange Rate (Primary)": {
      "main": [
        [
          {
            "node": "Did Primary Fail?",
            "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

This workflow automates multi-currency expense tracking via Telegram. Send a receipt photo to your bot, and it automatically extracts the invoice details, converts the amount to EUR using a live exchange rate, and logs everything straight into Google Sheets.

Source: https://n8n.io/workflows/14133/ — original creator credit. Request a take-down →

More Email & Gmail workflows → · Browse all categories →

Related workflows

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

Email & Gmail

Tags: Supply Chain Management, Logistics, Transportation

Telegram Trigger, Telegram, Google Drive +2
Email & Gmail

This workflow automates the process of retrieving Stripe invoices, validating API responses, generating payment receipts, sending them via email, storing PDFs in Google Drive, and appending details to

HTTP Request, Gmail, Google Drive +1
Email & Gmail

Transform messy receipt photos into a structured, searchable expense database in seconds. This workflow automates the entire journey from a WhatsApp message to a live Google Sheets entry by combining

Google Sheets, HTTP Request, N8N Nodes Wati
Email & Gmail

This workflow implements a lightweight authentication system for n8n web portals using Google Sheets as a simple user and session store. It supports email-based registration, username and password log

Google Sheets, Form Trigger, Gmail +1
Email & Gmail

Automatically sends Telegram notifications with optional screenshots when monitors change status (✅ UP/🔴 DOWN/⏸️ PAUSED)

Telegram, Crypto, HTTP Request +2