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
- Open Telegram and message @botfather.
- Run
/newbot, follow the prompts, and copy your bot token (format:1234567890:ABCdef...). - In n8n: Settings → Credentials → New → Telegram API.
- Paste the token. Save.
Getting the chat ID:
- For a private chat: message your bot, then call
https://api.telegram.org/bot<TOKEN>/getUpdates— thechat.idis 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 }}
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 } }];
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
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 }}
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)
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
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)
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
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
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 }]] }
}
}
Import via Settings → Import from URL/Clipboard.
Next steps
- Pair with the n8n OpenAI node to build AI-powered Telegram bots
- Use the n8n Schedule Trigger for timed digest bots
- Add n8n Error Trigger to get Telegram alerts on workflow failures
- Browse the full n8n Workflow Starter Pack for pre-built automation patterns
What are you building with the Telegram node? Drop your bot idea or pattern in the comments.
Top comments (1)
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.