Exchange rate alert on Telegram
Check the US dollar rate every hour and get a Telegram alert when it moves more than 1%.
How it works
- Get the latest USD rates from a free exchange-rate API.
- Compare your currencies with the last rate the flow saw.
- If any moved more than 1%, send the old rate, new rate and change to Telegram.
Diagram
Code
JavaScript used in the Code step — already included in the template below.
// 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.
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": []
}In Make, create a new scenario and add these modules in order:
- Schedule the scenario: Every hour
- HTTP › Make a request
- Tools › Set variable / Iterator + Array aggregator (same logic as the Code step)
- Filter on the next route: “Moved 1% or more?”
- 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:
- Schedule by Zapier — Every hour
- Webhooks by Zapier — GET
- Code by Zapier — Run JavaScript (paste the code from the Code section)
- Filter by Zapier: “Moved 1% or more?”
- 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
- 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
Log expenses by texting a Telegram bot
Send "12.50 lunch" to your bot and it's saved to a Google Sheet instantly.
Subscription renewal reminders on WhatsApp
Seven days before a client's subscription or membership ends, remind them on WhatsApp and tell your team.
Weekly cash flow summary on Telegram
Every Sunday, see money in, money out, net and your biggest expenses of the week on Telegram.