DEV Community

Pirate Prentice
Pirate Prentice

Posted on

n8n Telegram Node: Send Messages, Handle Commands, and Build Bots in Your Workflows (Free Workflow JSON)

What the Telegram node can do

Operation What it does
Send Message Send text (Markdown/HTML), reply keyboards, inline keyboards
Send Photo / Audio / Document / Video / Sticker Send binary media from upstream nodes
Edit Message Update a previously sent message in-place
Delete Message Remove a message from a chat
Pin / Unpin Chat Message Pin or unpin a message
Get Chat / Get Chat Members Count Inspect chat metadata
Answer Inline Query Respond to inline @bot queries
Answer Callback Query Acknowledge inline keyboard button presses
Set Chat Description / Title Manage group metadata
Send Location / Venue / Contact Send structured location or contact data

The Telegram Trigger node listens for: messages, edited messages, callback queries (inline keyboard presses), inline queries, channel posts, and more.


Setting up credentials

  1. Open Telegram and message @botfather.
  2. Run /newbot, follow the prompts, and copy your bot token (format: 1234567890:ABCdef...).
  3. In n8n: Settings → Credentials → New → Telegram API.
  4. Paste the token. Save.

Getting the chat ID:

  • For a private chat: message your bot, then call https://api.telegram.org/bot<TOKEN>/getUpdates — the chat.id is in the response.
  • For a group: add your bot to the group, send a message, then check getUpdates — group IDs are negative numbers (e.g. -100123456789).
  • For channels: use @channelusername (the public handle) or the numeric ID.

Telegram Trigger node

The Trigger fires your workflow on inbound events. Key configuration:

Field Notes
Credential Same bot token as above
Updates Select which event types trigger the workflow: message, callback_query, inline_query, etc. Select only what you need — every update type adds webhook overhead
Additional Fields → Allowed Updates Fine-grained filter; leave blank to receive all selected types

Accessing trigger data:

// The incoming message text
{{ $json.message.text }}

// The chat ID to reply to
{{ $json.message.chat.id }}

// Callback query data (from inline keyboard button press)
{{ $json.callback_query.data }}

// The sender's username
{{ $json.message.from.username }}
Enter fullscreen mode Exit fullscreen mode

Gotcha — webhook vs polling: n8n uses webhooks by default. Your n8n instance must be reachable over HTTPS (public URL or a tunnel). If you're running n8n locally without a tunnel, the Trigger won't receive updates. Use ngrok or similar to expose the webhook endpoint during development.


Send Message operation

The most-used operation. Core fields:

Field Notes
Chat ID Numeric ID, @channelusername, or expression from trigger data
Text Supports Markdown and HTML (set Parse Mode accordingly)
Parse Mode Markdown, MarkdownV2, or HTML — pick one and be consistent
Reply To Message ID Thread a reply to a specific message
Disable Notification Silent push for non-urgent messages
Reply Markup Attach an inline keyboard or reply keyboard

MarkdownV2 gotcha: MarkdownV2 requires escaping special characters (-, ., (, ), !, etc.) with a backslash. If your text comes from dynamic expressions, escape it in a Code node first or use HTML parse mode instead — HTML is less brittle.

// Code node: escape text for MarkdownV2
const text = $input.first().json.rawText;
const escaped = text.replace(/([_*\[\]()~`>#+\-=|{}.!\\])/g, '\\$1');
return [{ json: { escapedText: escaped } }];
Enter fullscreen mode Exit fullscreen mode

Inline keyboards (callback buttons)

Inline keyboards attach buttons directly to a message. When a user clicks a button, a callback_query event fires with the button's data payload.

Sending a message with inline keyboard:

Operation: Send Message
Reply Markup: Inline Keyboard
Inline Keyboard Buttons:
  - Row 1:
    - Text: ✅ Approve   | Callback Data: approve_123
    - Text: ❌ Reject    | Callback Data: reject_123
Enter fullscreen mode Exit fullscreen mode

Handling the callback in the Trigger:

// In the workflow triggered by callback_query:
Chat ID:    {{ $json.callback_query.message.chat.id }}
Message ID: {{ $json.callback_query.message.message_id }}
Button pressed: {{ $json.callback_query.data }}
Enter fullscreen mode Exit fullscreen mode

After handling the callback, always call Answer Callback Query with the callback_query_id to dismiss the loading spinner on the button:

Operation: Answer Callback Query
Callback Query ID: {{ $json.callback_query.id }}
Text: Done! (optional toast message shown to user)
Enter fullscreen mode Exit fullscreen mode

Sending files (photos, documents, audio)

Use the binary-input operations. Feed binary data from an upstream node (HTTP Request, Read Files, etc.) into the Telegram node:

Operation: Send Document
Chat ID: {{ $json.chatId }}
Binary Data: true
Binary Property: data   ← must match the binary field name from the upstream node
File Name: report.pdf
Caption: Your weekly report
Enter fullscreen mode Exit fullscreen mode

Gotcha — file size limits: Telegram bots can send files up to 50 MB via the Bot API. For larger files, upload to a CDN first and send the URL instead (set Binary Data: false, provide a URL in the document field).


Gotchas & common errors

1. Chat not found / bot not in chat

The bot must have been added to the group/channel before it can send messages there. For channels, the bot needs to be an admin with post permission.

2. Message too long (400 Bad Request)

Telegram messages have a 4096 character limit. For longer output, split with a Code node or send as a document.

3. Flood control (429)

Telegram rate-limits at ~30 messages/second globally and ~1 message/second per chat. For bulk sends, add a Wait node (1s delay) between messages or use a Split in Batches node.

4. Webhook conflict

If you test locally and then deploy to production, old webhook registrations can conflict. Call https://api.telegram.org/bot<TOKEN>/deleteWebhook to clear stale registrations.

5. Callback query timeout

Telegram shows a loading spinner on button presses for up to 10 seconds. Always answer callback queries promptly — if your workflow takes longer, answer immediately with a "processing…" text and update the message when done.

6. HTML parse mode vs Markdown

<b>bold</b> and <i>italic</i> work reliably in HTML mode. Markdown mode silently ignores improperly escaped characters. Prefer HTML for dynamic content.


3 workflow patterns

Pattern 1: Approval bot

Webhook (form submission / external event)
  → Telegram (Send Message with inline keyboard: Approve / Reject buttons)
  → [wait for callback_query trigger]
  → Switch (route on callback data: approve_* / reject_*)
  → Telegram (Edit Message: update to "✅ Approved by @username")
  → Notion / Airtable / Spreadsheet (update record status)
  → Telegram (Send confirmation to submitter chat)
Enter fullscreen mode Exit fullscreen mode

Use case: expense approvals, content moderation, PR review notifications — any human-in-the-loop decision you want to route through chat.

Pattern 2: Scheduled digest bot

Schedule Trigger (every morning 08:00)
  → HTTP Request / Spreadsheet / Notion (fetch today's data)
  → Code node (format as HTML: bold headers, list items)
  → Telegram Send Message (Parse Mode: HTML)
    Chat ID: -100[group_id] or personal chat
Enter fullscreen mode Exit fullscreen mode

Use case: daily metrics digest, top-of-morning task list, monitoring alert summary.

Pattern 3: Command handler bot

Telegram Trigger (message events)
  → IF node: {{ $json.message.text.startsWith('/') }} = true
  → Switch node (route on command: /status, /report, /help)
    → Branch /status: fetch system metrics → Telegram reply
    → Branch /report: generate PDF → Telegram Send Document
    → Branch /help: Telegram reply with command list
Enter fullscreen mode Exit fullscreen mode

Use case: ops bots, internal tooling bots, personal automation command center.


Free workflow JSON

Here's a starter approval-bot workflow — webhook receives a request, bot sends an inline keyboard to a Telegram chat, and the callback triggers an update:

{
  "name": "Telegram Approval Bot Starter",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "approval-request",
        "responseMode": "lastNode"
      },
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [240, 300]
    },
    {
      "parameters": {
        "chatId": "={{ $json.body.chatId }}",
        "text": "New approval request from {{ $json.body.requester }}:\n\n{{ $json.body.description }}",
        "additionalFields": {
          "parseMode": "HTML",
          "replyMarkup": "inlineKeyboard",
          "inlineKeyboard": {
            "rows": [
              {
                "row": {
                  "buttons": [
                    { "text": "✅ Approve", "callbackData": "approve_{{ $json.body.requestId }}" },
                    { "text": "❌ Reject", "callbackData": "reject_{{ $json.body.requestId }}" }
                  ]
                }
              }
            ]
          }
        }
      },
      "name": "Send Approval Request",
      "type": "n8n-nodes-base.telegram",
      "position": [460, 300]
    },
    {
      "parameters": {
        "updates": ["callback_query"]
      },
      "name": "Telegram Trigger",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [240, 500]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.callback_query.data }}",
              "operation": "startsWith",
              "value2": "approve_"
            }
          ]
        }
      },
      "name": "Approved?",
      "type": "n8n-nodes-base.if",
      "position": [460, 500]
    },
    {
      "parameters": {
        "callbackQueryId": "={{ $json.callback_query.id }}",
        "text": "Decision recorded!"
      },
      "name": "Answer Callback",
      "type": "n8n-nodes-base.telegram",
      "position": [680, 500]
    }
  ],
  "connections": {
    "Webhook": { "main": [[{ "node": "Send Approval Request", "type": "main", "index": 0 }]] },
    "Telegram Trigger": { "main": [[{ "node": "Approved?", "type": "main", "index": 0 }]] },
    "Approved?": { "main": [[{ "node": "Answer Callback", "type": "main", "index": 0 }]] }
  }
}
Enter fullscreen mode Exit fullscreen mode

Import via Settings → Import from URL/Clipboard.


Next steps


What are you building with the Telegram node? Drop your bot idea or pattern in the comments.

Top comments (1)

Collapse
 
pirateprentice profile image
Pirate Prentice

Are you using the Telegram node to build approval bots, scheduled digest bots, or command handlers in n8n? Drop your use case in the comments — would love to see what people are routing through Telegram.