Exchange rate alert on Telegram

Check the US dollar rate every hour and get a Telegram alert when it moves more than 1%.

Easy Saves ~2 hours/month Finance & AdminBy Flowngn team

Works with n8n · Make · Zapier

How it works

Trigger: Schedule — every hour
  1. Get the latest USD rates from a free exchange-rate API.
  2. Compare your currencies with the last rate the flow saw.
  3. If any moved more than 1%, send the old rate, new rate and change to Telegram.

Diagram

trueEvery hourSchedule TriggerGet USD ratesHTTP Request{ }Compare with last r…Code⑂Moved 1% or more?IfTAlert on TelegramTelegram
Trigger App Logic Code

Code

JavaScript used in the Code step — already included in the template below.

Compare with last rate · Code node
// Compares today's USD rates with the last rates this workflow saw.
// The last rates are kept in workflow static data, which only saves on active (production) runs.
const CURRENCIES = ["EGP", "SAR", "AED"];
const THRESHOLD = 1; // percent

const rates = $input.first().json.rates || {};
const memory = $getWorkflowStaticData("global");
const last = memory.rates || {};

const moves = CURRENCIES
  .filter((c) => rates[c] && last[c])
  .map((c) => ({ currency: c, from: last[c], to: rates[c], change: ((rates[c] - last[c]) / last[c]) * 100 }))
  .filter((m) => Math.abs(m.change) >= THRESHOLD);

// Only move the baseline when we alert, so slow drifts still add up to an alert.
if (!memory.rates || moves.length) memory.rates = Object.fromEntries(CURRENCIES.map((c) => [c, rates[c]]));

return [{
  json: {
    alert: moves.length > 0,
    message: moves
      .map((m) => `USD/${m.currency}: ${m.from.toFixed(2)} → ${m.to.toFixed(2)} (${m.change > 0 ? "+" : ""}${m.change.toFixed(1)}%)`)
      .join("\n"),
  },
}];

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.

Workflow JSON
Show code (171 lines)
{
  "name": "Exchange rate alert on Telegram (Flowngn)",
  "nodes": [
    {
      "parameters": {
        "content": "## Exchange rate alert on Telegram\nCheck the US dollar rate every hour and get a Telegram alert when it moves more than 1%.\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": "22e076db-ed1a-48e3-aaf2-6913115a1802",
      "name": "Setup notes",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -420,
        -60
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 1
            }
          ]
        }
      },
      "id": "22e076db-ed1a-48e3-aaf2-6913115a1802",
      "name": "Every hour",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "url": "https://open.er-api.com/v6/latest/USD",
        "options": {}
      },
      "id": "22e076db-ed1a-48e3-aaf2-6913115a1802",
      "name": "Get USD rates",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        300,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Compares today's USD rates with the last rates this workflow saw.\n// The last rates are kept in workflow static data, which only saves on active (production) runs.\nconst CURRENCIES = [\"EGP\", \"SAR\", \"AED\"];\nconst THRESHOLD = 1; // percent\n\nconst rates = $input.first().json.rates || {};\nconst memory = $getWorkflowStaticData(\"global\");\nconst last = memory.rates || {};\n\nconst moves = CURRENCIES\n  .filter((c) => rates[c] && last[c])\n  .map((c) => ({ currency: c, from: last[c], to: rates[c], change: ((rates[c] - last[c]) / last[c]) * 100 }))\n  .filter((m) => Math.abs(m.change) >= THRESHOLD);\n\n// Only move the baseline when we alert, so slow drifts still add up to an alert.\nif (!memory.rates || moves.length) memory.rates = Object.fromEntries(CURRENCIES.map((c) => [c, rates[c]]));\n\nreturn [{\n  json: {\n    alert: moves.length > 0,\n    message: moves\n      .map((m) => `USD/${m.currency}: ${m.from.toFixed(2)} → ${m.to.toFixed(2)} (${m.change > 0 ? \"+\" : \"\"}${m.change.toFixed(1)}%)`)\n      .join(\"\\n\"),\n  },\n}];"
      },
      "id": "22e076db-ed1a-48e3-aaf2-6913115a1802",
      "name": "Compare with last rate",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        600,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.alert }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "22e076db-ed1a-48e3-aaf2-6913115a1802",
      "name": "Moved 1% or more?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        900,
        0
      ]
    },
    {
      "parameters": {
        "chatId": "YOUR_CHAT_ID",
        "text": "=Exchange rate moved:\n{{ $json.message }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "22e076db-ed1a-48e3-aaf2-6913115a1802",
      "name": "Alert on Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1200,
        0
      ]
    }
  ],
  "connections": {
    "Every hour": {
      "main": [
        [
          {
            "node": "Get USD rates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get USD rates": {
      "main": [
        [
          {
            "node": "Compare with last rate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compare with last rate": {
      "main": [
        [
          {
            "node": "Moved 1% or more?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Moved 1% or more?": {
      "main": [
        [
          {
            "node": "Alert on Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}

Open in the playground to edit →

Tips

  • The last rate is saved in the workflow itself, so it only works once the workflow is active (not in manual test runs).
  • Free APIs update a few times a day; use your bank's rate for actual pricing.

Related flows