Ep 05: Your First Live Workflow — Webhook Trigger & Telegram Bot Interaction

⏱ Est. reading time: 17 min Updated on 4/10/2026

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:#fff

Prerequisites

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:#fff

Node-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:#d97706

Activating 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.