This workflow corresponds to n8n.io template #10481 — we link there as the canonical source.
This workflow follows the HTTP Request → Telegram 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 →
{
"nodes": [
{
"id": "7df92b27-05c7-4c4a-9b2f-76883f0d8974",
"name": "If1",
"type": "n8n-nodes-base.if",
"position": [
624,
-64
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "8e217cc9-d842-4b92-a62b-b79e99a059df",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.sequence }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.2
},
{
"id": "40fe3671-d5ed-49fd-a342-f89df42f5657",
"name": "Identify trips",
"type": "n8n-nodes-base.code",
"position": [
400,
-64
],
"parameters": {
"jsCode": "const travelKeywords = ['trip', 'travel', 'vacation', 'flight', 'holiday'];\n\nreturn items.filter(item => {\n const title = (item.json.summary || '').toLowerCase();\n const description = (item.json.description || '').toLowerCase();\n\n return travelKeywords.some(keyword => title.includes(keyword) || description.includes(keyword));\n});\n"
},
"typeVersion": 2
},
{
"id": "41df49be-15c4-4c83-8c69-6c8cc109ac34",
"name": "Extract locations",
"type": "n8n-nodes-base.code",
"position": [
848,
-64
],
"parameters": {
"jsCode": "return items.map(item => {\n const startDate = item.json.start.dateTime || item.json.start.date;\n const endDate = item.json.end.dateTime || item.json.end.date;\n\n // Initialize location variable\n let location = null;\n\n // Extract destination from summary first (e.g. \"Flight to Istanbul\")\n const summary = (item.json.summary || '').toLowerCase();\n const matchSummary = summary.match(/to ([a-zA-Z\\s,]+)/);\n if (matchSummary && matchSummary[1]) {\n location = matchSummary[1].trim();\n }\n\n // If no destination yet, try description (e.g. \"Flight from Mauritius (port Louis) to Istanbul\")\n if (!location) {\n const description = (item.json.description || '').toLowerCase();\n // Match pattern after \"to \"\n const matchDescription = description.match(/to ([a-zA-Z\\s,]+)/);\n if (matchDescription && matchDescription[1]) {\n location = matchDescription[1].trim();\n }\n }\n\n // Only use the location field if above patterns fail or if location is not a \"from\" location\n if (!location && item.json.location) {\n const locLower = item.json.location.toLowerCase();\n if (!locLower.startsWith('from ')) { // Avoid departure locations starting with \"from\"\n location = item.json.location;\n }\n }\n\n // Final fallback if no location found\n if (!location) {\n location = null; // or 'YOUR_DEFAULT_TRIP_LOCATION'\n }\n\n return {\n json: {\n startDate,\n endDate,\n location\n }\n };\n});\n"
},
"typeVersion": 2
},
{
"id": "1c3d2277-df1f-4178-bdba-bff40a182c2d",
"name": "Wait",
"type": "n8n-nodes-base.wait",
"position": [
1104,
320
],
"parameters": {
"resume": "specificTime",
"dateTime": "={{ new Date(new Date($('Extract locations').item.json.startDate).getTime() - 24 * 60 * 60 * 1000).toISOString() }}"
},
"typeVersion": 1.1
},
{
"id": "0d3f1ec2-2377-46e3-84bc-c303adddb825",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-448,
-400
],
"parameters": {
"width": 560,
"height": 752,
"content": "## How it works\nWhen a calendar event is created or updated, identified as trip, provides timely weather alerts and forecasts tailored to travel dates and locations.\n\n## Step-by-step\n\n\ud83d\udcc5 **Google Calendar Triggers** (Event Created/Updated): \nThe workflow starts immediately upon creation or update of any calendar event, enabling real-time detection of new or changed travel plans.\n\n\u2708 **Identify Trips & Extract Locations**: \nFilters these calendar events to detect travel-related trips by matching keywords such as \"trip,\" \"flight,\" or \"vacation\" in titles or descriptions and extract start and end dates & the trip destination.\n\n\ud83c\udf10 **Get Forecast & send it**: \nUsing Visual Crossing API (1000 free daily requests) fetches the detailed weather forecast and alert data for the trip location then formats the raw weather data into a readable summary \ud83c\udf24\ufe0f\ud83c\udf2a\ud83c\udf00, and eventual severe weather alerts.\n\n\ud83d\udcf2 \ud83d\udce7 **Send Forecast**: \nSends the forecast summary with alerts via Telegram to keep the user informed instantly.\n\n\u231b**One day before the trip**: \nPauses the workflow until exactly one day before the trip start date, ensuring a timely second fetch when more accurate or updated weather data is available and the updated forecast is sent.\n\n## Optional\nYou can replace the Telegram node with email, WhatsApp, Slack, SMS notifications, or add multiple notification nodes to receive them across all desired channels."
},
"typeVersion": 1
},
{
"id": "79b81985-724d-4208-a225-25ecde0851e7",
"name": "Event created",
"type": "n8n-nodes-base.googleCalendarTrigger",
"position": [
176,
-160
],
"parameters": {
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"triggerOn": "eventCreated",
"calendarId": {
"__rl": true,
"mode": "list",
"value": "user@example.com",
"cachedResultName": "user@example.com"
}
},
"typeVersion": 1
},
{
"id": "38413099-7d19-4c06-bec7-a2d542607474",
"name": "Event updated",
"type": "n8n-nodes-base.googleCalendarTrigger",
"position": [
176,
32
],
"parameters": {
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"triggerOn": "eventUpdated",
"calendarId": {
"__rl": true,
"mode": "list",
"value": "user@example.com",
"cachedResultName": "user@example.com"
}
},
"credentials": {},
"typeVersion": 1
},
{
"id": "1cac0052-349b-4ae2-8c4e-b1a63bd834f4",
"name": "Send Forecast",
"type": "n8n-nodes-base.telegram",
"position": [
1808,
-64
],
"parameters": {
"operation": "send"
},
"notesInFlow": [
{
"note": "Sends the trip weather forecast summary to user via Telegram."
}
],
"typeVersion": 1
},
{
"id": "bb15bccd-f857-4de6-907f-7a48457a125f",
"name": "Build interogation URL",
"type": "n8n-nodes-base.code",
"position": [
1072,
-64
],
"parameters": {
"jsCode": "const location = encodeURIComponent($json.location);\nconst startDate = $json.startDate.split('T')[0]; // date only\nconst endDate = $json.endDate.split('T')[0]; // date only\nconst apiKey = '[your API]';\n\nconst url = `https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/${location}/${startDate}/${endDate}?unitGroup=metric&key=YOUR_TOKEN_HERE&include=days,alerts`;\n\nreturn [{ json: { url } }];\n"
},
"typeVersion": 2
},
{
"id": "e29e2ff1-46fd-44c5-ab8f-02d345586e41",
"name": "Get updated Destination Weather Forecast",
"type": "n8n-nodes-base.httpRequest",
"notes": "One day before the trip",
"position": [
1328,
320
],
"parameters": {
"url": "={{ $json.url }}",
"options": {}
},
"notesInFlow": [
{
"note": "Fetches weather forecast and alerts for trip location and dates from Visual Crossing."
}
],
"typeVersion": 1
},
{
"id": "c69a55de-bbf3-4921-bd89-24d4b8826111",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
368,
-160
],
"parameters": {
"color": 7,
"width": 608,
"height": 240,
"content": "## Identify \n**if** the event is a trip\n**Then** > extracting destination and period"
},
"typeVersion": 1
},
{
"id": "50def3d9-67bc-4f5c-b2a4-18f4235fffec",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
1024,
208
],
"parameters": {
"color": 6,
"width": 448,
"height": 288,
"content": "## Update the forecast one day before the trip\nWait one day before the trip and request again the forecast"
},
"typeVersion": 1
},
{
"id": "8aec9d53-2af5-4e3f-87a2-f0f67a2497e3",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
1008,
-160
],
"parameters": {
"color": 7,
"width": 480,
"height": 672,
"content": "## Get Forecast\n"
},
"typeVersion": 1
},
{
"id": "57b375da-2354-4b04-a6ee-b510bed576d0",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1536,
-160
],
"parameters": {
"color": 7,
"width": 416,
"height": 256,
"content": "## Send notification\nMake sure Telegram credentials and chat ID are set in the node."
},
"typeVersion": 1
},
{
"id": "b4b8e95d-3ea8-4ea2-ab88-a889a74d6818",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
128,
-272
],
"parameters": {
"color": 7,
"width": 192,
"height": 464,
"content": "## Trigger:\n**EVENT**\nCreated or updated"
},
"typeVersion": 1
},
{
"id": "5c65c141-7527-43b9-8e85-bd8f1291b54e",
"name": "Get Destination Weather forecast",
"type": "n8n-nodes-base.httpRequest",
"notes": "At the event creation/update",
"position": [
1296,
-64
],
"parameters": {
"url": "={{ $json.url }}",
"options": {}
},
"notesInFlow": [
{
"note": "Fetches weather forecast and alerts for trip location and dates from Visual Crossing."
}
],
"typeVersion": 1
},
{
"id": "b92af965-eecf-4019-b5cf-ca38e8ee9928",
"name": "Format message",
"type": "n8n-nodes-base.code",
"position": [
1584,
-64
],
"parameters": {
"jsCode": "return items.map(item => {\n let forecastSummary = `Weather forecast for your trip to ${item.json.address || 'destination'} from ${item.json.days[0].datetime} to ${item.json.days[item.json.days.length - 1].datetime}:\\n`;\n\n item.json.days.forEach(day => {\n const precipprob = day.precipprob !== undefined ? `, Precip Prob: ${day.precipprob}%` : '';\n const preciptype = day.preciptype ? `, Precip Type: ${day.preciptype.join(\", \")}` : '';\n const conditions = day.conditions ? `, Conditions: ${day.conditions}` : '';\n const description = day.description ? `, ${day.description}` : '';\n forecastSummary += `${day.datetime}: Max ${day.tempmax}\u00b0C, Min ${day.tempmin}\u00b0C${precipprob}${preciptype}${conditions}${description}\\n`;\n });\n\n if (item.json.alerts && item.json.alerts.length > 0) {\n forecastSummary += `\\nALERTS:\\n`;\n item.json.alerts.forEach(alert => {\n forecastSummary += `${alert.description}\\n`;\n });\n }\n\n return {\n json: { forecastSummary }\n };\n});\n"
},
"typeVersion": 2
}
],
"connections": {
"If1": {
"main": [
[
{
"node": "Extract locations",
"type": "main",
"index": 0
}
]
]
},
"Wait": {
"main": [
[
{
"node": "Get updated Destination Weather Forecast",
"type": "main",
"index": 0
}
]
]
},
"Event created": {
"main": [
[
{
"node": "Identify trips",
"type": "main",
"index": 0
}
]
]
},
"Event updated": {
"main": [
[
{
"node": "Identify trips",
"type": "main",
"index": 0
}
]
]
},
"Format message": {
"main": [
[
{
"node": "Send Forecast",
"type": "main",
"index": 0
}
]
]
},
"Identify trips": {
"main": [
[
{
"node": "If1",
"type": "main",
"index": 0
}
]
]
},
"Extract locations": {
"main": [
[
{
"node": "Build interogation URL",
"type": "main",
"index": 0
}
]
]
},
"Build interogation URL": {
"main": [
[
{
"node": "Wait",
"type": "main",
"index": 0
},
{
"node": "Get Destination Weather forecast",
"type": "main",
"index": 0
}
]
]
},
"Get Destination Weather forecast": {
"main": [
[
{
"node": "Format message",
"type": "main",
"index": 0
}
]
]
},
"Get updated Destination Weather Forecast": {
"main": [
[
{
"node": "Format message",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow for trip weather forecasting is event-driven, starting when a calendar event is created or updated, and provides timely weather alerts and forecasts tailored to your travel dates and locations.
Source: https://n8n.io/workflows/10481/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
N8N Complete Final. Uses telegramTrigger, dataTable, telegram, mqtt. Event-driven trigger; 58 nodes.
TextMain. Uses telegramTrigger, stopAndError, telegram, httpRequest. Event-driven trigger; 56 nodes.
Pede Ai. Uses httpRequest, telegram, postgres, telegramTrigger. Event-driven trigger; 53 nodes.
📄 Documentation: Notion Guide
checkProcess(old). Uses googleSheets, httpRequest, telegram, @n-octo-n/n8n-nodes-json-database. Event-driven trigger; 40 nodes.