DEV Community

Pirate Prentice
Pirate Prentice

Posted on Edited 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.


Related Articles


Want this built for you? I build production-ready n8n workflows for small businesses — fixed price, 48-hour turnaround, honest refund policy. See the service and get a quote → occelatus.io/automate

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.