Ep 05: Your First Live Workflow — Webhook Trigger & Telegram Bot Interaction
Episode Goal
Build a complete, production-ready workflow: A user sends a city name via Telegram, n8n calls a weather API for real-time data, then replies with a beautifully formatted result.
graph LR
User[👤 User] -->|"Sends: Shanghai"| TG[📱 Telegram]
TG -->|"Webhook push"| N8N[🤖 n8n Workflow]
N8N -->|"Call Weather API"| API[🌤️ OpenWeather API]
API -->|"Return JSON"| N8N
N8N -->|"Formatted reply"| TG
TG -->|"Shanghai: Sunny 28°C"| User
style N8N fill:#ff6d5b,stroke:#e55a4e,color:#fffPrerequisites
1. Create a Telegram Bot
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Create a Bot via BotFather in Telegram
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Step 1: Open Telegram, search for @BotFather
# Step 2: Send /newbot
# Step 3: Enter Bot display name, e.g.: "My n8n Weather Bot"
# Step 4: Enter Bot username (must end in 'bot'), e.g.: my_n8n_weather_bot
# Step 5: BotFather returns your Bot Token:
# e.g.: 7123456789:AAHxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# ⚠️ This Token IS the Bot's password — keep it secret!
2. Get an OpenWeather API Key
# Register at https://openweathermap.org/api (free tier)
# Free plan: 1000 calls/day — more than enough for learning
# Save the API Key in n8n's Credentials manager
Workflow Architecture
The complete workflow has 5 nodes with data flowing linearly:
graph TB
subgraph "Complete Workflow Topology"
T[🔔 Telegram Trigger
Listen for messages]
Extract[⚙️ Set Node
Extract city name]
API[🌐 HTTP Request
Call Weather API]
Format[💻 Code Node
Format reply text]
Reply[📤 Telegram Node
Send reply]
T --> Extract --> API --> Format --> Reply
end
style T fill:#0088cc,stroke:#006699,color:#fff
style API fill:#f59e0b,stroke:#d97706,color:#fff
style Format fill:#22c55e,stroke:#16a34a,color:#fff
style Reply fill:#0088cc,stroke:#006699,color:#fffNode-by-Node Configuration
Node 1: Telegram Trigger
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Telegram Trigger: Listens for all incoming Bot messages
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{
"node": "Telegram Trigger",
"parameters": {
"updates": ["message"] // Only listen for text messages (ignore callbacks)
},
"credentials": {
"telegramApi": {
"accessToken": "7123456789:AAHxxxxxxx" // Token from BotFather
}
}
}
// 📤 Output Item structure:
// {
// "json": {
// "message": {
// "message_id": 42,
// "from": { "id": 123456, "first_name": "Alice" },
// "chat": { "id": 123456, "type": "private" },
// "text": "Shanghai" ← The city name sent by the user
// }
// }
// }
Node 2: Set Node (Extract City)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Set Node: Extract key fields from the raw Telegram message
// Purpose: Downstream nodes deal with clean data, not raw Telegram API structures
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Field: city
// Value (expression): {{ $json.message.text }}
// ↑ n8n expression syntax; $json refers to current Item's json property
// Field: chatId
// Value (expression): {{ $json.message.chat.id }}
// ↑ Needed to send the reply to the correct chat
// 📤 Output Item:
// { "json": { "city": "Shanghai", "chatId": 123456 } }
Node 3: HTTP Request (Weather API)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// HTTP Request: Call the OpenWeather API
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{
"method": "GET",
"url": "https://api.openweathermap.org/data/2.5/weather",
"queryParameters": {
"q": "={{ $json.city }}", // Dynamic: references upstream city name
"appid": "your_api_key_here",
"units": "metric", // Celsius
"lang": "en" // English descriptions
},
"options": {
"timeout": 10000 // 10s timeout
}
}
Node 4: Code Node (Format Reply)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Code Node: Transform raw API data into user-friendly reply
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const weather = $input.first().json;
// Cross-node reference: $('NodeName') accesses data from any previous node
const chatId = $('Set').first().json.chatId;
// Check for API errors
if (!weather.main) {
return [{
json: {
chatId,
text: `❌ Sorry, couldn't find weather for "${$('Set').first().json.city}".`
}
}];
}
// Build formatted message
const message = [
`🌍 ${weather.name} Weather`,
`━━━━━━━━━━━━━━━━`,
`🌡️ Temp: ${Math.round(weather.main.temp)}°C`,
`🤒 Feels: ${Math.round(weather.main.feels_like)}°C`,
`☁️ Condition: ${weather.weather[0].description}`,
`💧 Humidity: ${weather.main.humidity}%`,
`🌬️ Wind: ${weather.wind.speed} m/s`
].join('\n');
return [{ json: { chatId, text: message } }];
Node 5: Telegram Send Reply
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Telegram Node: Reply to the user
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{
"operation": "sendMessage",
"chatId": "={{ $json.chatId }}", // Dynamic: reply to the sender
"text": "={{ $json.text }}", // Dynamic: formatted weather info
"additionalFields": {
"parse_mode": "Markdown" // Support bold, italic, etc.
}
}
Full Data Flow Sequence
sequenceDiagram
participant User as 👤 Alice (Telegram)
participant Bot as 🤖 Telegram Bot
participant Trigger as 🔔 Telegram Trigger
participant Set as ⚙️ Set Node
participant HTTP as 🌐 HTTP Request
participant Code as 💻 Code Node
participant Reply as 📤 Telegram Send
User->>Bot: Send "Shanghai"
Bot->>Trigger: Webhook push
Note over Trigger: Produces Item:
{message: {text: "Shanghai", chat: {id: 123}}}
Trigger->>Set: Pass Item
Note over Set: Extract: city="Shanghai", chatId=123
Set->>HTTP: Pass {city: "Shanghai"}
Note over HTTP: GET openweathermap.org?q=Shanghai
HTTP->>Code: Pass weather JSON
Note over Code: Format into friendly text
Code->>Reply: Pass {chatId: 123, text: "🌍Shanghai...28°C"}
Reply->>Bot: Call Telegram API
Bot->>User: "🌍 Shanghai Weather
🌡️ Temp: 28°C
☁️ Sunny"Error Handling Enhancement
graph TB
T[Telegram Trigger] --> Validate{Is city name empty?}
Validate -->|"Empty message"| ErrReply1[Reply: Please enter a city name]
Validate -->|"Has content"| API[HTTP Request]
API --> Check{HTTP status?}
Check -->|"200 OK"| Format[Code: Format]
Check -->|"404 Not Found"| ErrReply2[Reply: City not found]
Check -->|"Other error"| ErrReply3[Reply: Service temporarily unavailable]
Format --> Reply[Telegram Reply]
style Validate fill:#f59e0b,stroke:#d97706
style Check fill:#f59e0b,stroke:#d97706Activating the Workflow
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# After building, activate to enable production execution
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# In n8n editor:
# 1. Click the "Inactive" toggle (top-right) → switch to "Active"
# 2. Telegram Trigger begins listening
# 3. Test by sending any city name to your Bot in Telegram
Module 1 Complete!
Congratulations on finishing all 5 episodes of Module 1! You've mastered n8n's foundational concepts and built your first production-ready workflow. In Module 2, we'll dive into If/Switch branching, Data Tables persistence, and advanced data manipulation techniques.