n8n Telegram Bot: Build One That Sends and Receives Messages (2026)
Build a Telegram bot with n8n — BotFather setup, finding your chat ID, sending formatted messages, and the one-webhook-per-bot rule that breaks most first attempts.
By Ali Ilyas · · Updated July 28, 2026
Building a Telegram bot in n8n takes about five minutes if all you want is outbound messages — notifications, alerts, a daily digest. Making the bot listen and respond is a different job, and it's where most first attempts stall.
The reason is a Telegram rule that has nothing to do with n8n: a bot can have exactly one webhook. n8n uses one URL when you're testing a workflow and a different one once it's active, so the two modes silently overwrite each other. Your bot works in the editor, then stops when you activate it — or the reverse — and nothing in the error explains why.
This guide covers the fast outbound path first, then the trigger, then that constraint and how to work around it properly.
What's on this page
- What you'll need
- Create the bot with BotFather
- Add the credential in n8n
- Send your first message
- Finding your chat ID
- Formatting messages without breaking them
- Receiving messages with the Telegram Trigger
- The one-webhook-per-bot problem
- Buttons, replies, and inline keyboards
- Sending photos and files
- Common errors and fixes
- Frequently asked questions
What you'll need
- A Telegram account.
- A running n8n instance. Outbound messages work anywhere, including a local instance on
localhost. Receiving messages requires a public HTTPS URL — Telegram will not deliver updates tolocalhostor to plainhttp://. If you're running locally, n8n in Docker is the quickest start; for the trigger you'll need a real domain with HTTPS.
That split is worth internalizing before you begin. Half of this guide works on a laptop; the other half doesn't, and no amount of configuration changes that.
Create the bot with BotFather
BotFather is Telegram's official bot for creating bots. Open a chat with @BotFather and send:
/newbot
It asks for two things:
Name — the display name people see. Changeable later.
Username — the permanent identifier. The rules: 5 to 32 characters, not case sensitive, Latin characters, numbers and underscores only, and it must end in bot (tetris_bot, my_alerts_bot). This one cannot be changed afterward, so pick something you won't regret.
BotFather then gives you an access token that looks roughly like 8123456789:AAF.... Treat it like a password — anyone holding it controls your bot completely.
Add the credential in n8n
In n8n, create a new Telegram API credential and paste the token into the Access Token field. Save.
That's the entire credential setup. No OAuth flow, no consent screen, no Google Cloud project — a pleasant contrast to connecting Google Sheets, where credentials are most of the work.
Send your first message
Add a Telegram node, set Resource to Message and Operation to Send Message. Two fields matter:
Chat ID — where the message goes. For a public channel you can use @channelusername. For anything else you need the numeric ID, which is the next section.
Text — up to 4096 characters after entity parsing. Longer messages are rejected rather than truncated, so if you're piping in generated or scraped content, split it before it reaches this node.
Before you run it: a bot cannot message you first. Telegram requires the user to initiate contact. Open your bot's chat and send it /start, or the send will fail no matter how correct your configuration is. For a group or channel, add the bot as a member — and as an administrator if it needs to post to a channel.
One setting you'll want immediately. By default n8n appends its own attribution line to outgoing messages. To remove it, open Additional Fields → Add Field and turn off Append n8n Attribution. Most people don't notice this until a client asks why the bot is advertising software.
Finding your chat ID
The most common blocker on this integration, and there are three ways through it.
Use the Telegram Trigger. Add a Telegram Trigger node, execute it, and message your bot. The output contains the chat object, and chat.id is what you want. This is the most reliable method because it gives you the exact value Telegram will use. It needs a publicly reachable instance, though — see the trigger section below.
Read it from Telegram Web. Open the group in a browser. The chat ID is the run of digits after the letter g in the URL. Prefix it with - when you enter it in n8n — group IDs are negative, and omitting the minus sign is a frequent, confusing failure.
Invite @RawDataBot. Add Telegram's @RawDataBot to the group. It immediately posts a JSON dump; the id inside the chat object is your chat ID. Remove the bot afterward.
Note that @channelusername only works for public channels. Private groups have no username, so there the numeric ID is the only option.
Formatting messages without breaking them
Under Additional Fields, Parse Mode offers three choices: HTML (the default), Markdown (Legacy), and MarkdownV2.
Use HTML unless you have a specific reason not to. MarkdownV2 looks like the modern option and is the biggest source of avoidable errors on this node, because it requires escaping a long list of reserved characters — including the period. A message ending in a normal sentence full stop can fail to parse. The resulting Bad Request: can't parse entities error points at Telegram's parser, not at your text, so the cause is rarely obvious.
HTML needs only <b>, <i>, <code>, <pre>, and <a href="">, and it doesn't blow up on punctuation:
<b>Deploy finished</b>
Build <code>a1b2c3d</code> is live.
<a href="https://example.com/logs">View logs</a>
Two other fields worth knowing:
- Disable Notification — delivers silently. Right for high-frequency or low-priority alerts.
- Disable WebPage Preview — suppresses the link preview card, which otherwise dominates short messages containing a URL.
Receiving messages with the Telegram Trigger
The Telegram Trigger is a separate node that starts a workflow when something happens in Telegram. It offers a long list of update types — Message, Edited Message, Channel Post, Callback Query, Inline Query, Poll Answer, Chat Member, Chat Join Request, and more, plus * for all updates, which is the default.
Set this deliberately. Leaving it on * means your workflow fires on events you never intended to handle, including your own bot's status changes, and you'll burn executions working out why.
Three options are worth setting:
Download Images/Files — fetches attachments and hands them to the next node as binary data rather than giving you a file reference you'd have to resolve yourself. Image Size controls which resolution you get.
Restrict to Chat IDs and Restrict to User IDs — comma-separated allowlists. For a bot that isn't meant to be public, set at least one of these. A Telegram bot's username is discoverable, and without a restriction anyone who finds it can trigger your workflow.
Unlike the Google Sheets Trigger, which polls on a schedule, this one is a genuine webhook — Telegram pushes updates to n8n the moment they happen. That's why it's instant, and also why it has the constraint in the next section.
The one-webhook-per-bot problem
This is the part that costs people an evening.
n8n gives a workflow two webhook URLs: a test URL used while you're clicking Execute workflow in the editor, and a production URL used when the workflow is active. Every other trigger handles this fine.
Telegram doesn't, because Telegram only allows a single registered webhook per bot. Whichever mode registered last wins. So:
- You test in the editor. n8n registers the test URL. It works.
- You activate the workflow. n8n registers the production URL, overwriting the test one.
- You go back to the editor to debug. The test registration overwrites production — and your live bot goes quiet.
Nothing errors. The bot simply stops responding in the mode you're not currently using, which is exactly the symptom that sends people hunting through their workflow logic for a bug that isn't there.
There are two clean fixes:
Create a second bot for development. Run /newbot again, make a separate n8n credential, and point your development copy of the workflow at it. This is the right answer for anything real — you get an isolated test environment and your live bot is never interrupted.
Or deactivate the workflow while testing, then reactivate it. Free, but it means your bot is down whenever you're working on it, and it's easy to forget the reactivation step.
If you run more than one workflow off the same bot, the same rule applies: they compete for the single webhook slot. Route all updates into one trigger and branch with a Switch node, rather than running several triggers on one token.
This isn't unique to Telegram — Slack has the same one-request-URL-per-app rule, with the same symptom and the same fix. It's worth recognizing the pattern, because it shows up wherever a platform tracks a single callback address per integration.
Buttons, replies, and inline keyboards
Reply Markup on the Send Message operation turns a message into something interactive. Four types:
- Inline Keyboard — buttons attached under the message itself. The usual choice.
- Reply Keyboard — replaces the user's keyboard with preset options.
- Reply Keyboard Remove — dismisses that keyboard.
- Force Reply — makes the client open a reply box automatically.
Inline keyboard buttons emit a Callback Query rather than a message, so your Telegram Trigger must include that update type or the taps go nowhere.
One requirement people miss: after handling a callback you should run the Callback → Answer Query operation. Until you do, Telegram shows a loading spinner on the button in the user's client. The action succeeds, but the interface looks broken.
To reply in the same thread rather than as a new message, set Reply To Message ID from the incoming message. In forum supergroups, Message Thread ID keeps replies inside the right topic.
Sending photos and files
Message operations cover Send Photo, Send Document, Send Video, Send Audio, Send Animation, Send Sticker, Send Media Group, and Send Location.
Each takes either binary data from a previous node — enable Binary File — or a reference. For references you have two options: an HTTP URL that Telegram fetches itself, or a file_id from a file Telegram already has. Prefer file_id when resending something the bot has seen before; it's instant and skips the upload entirely.
Going the other way, File → Get File retrieves a file a user sent to your bot.
Two limits worth planning around: Send Animation is documented for GIFs and soundless H.264/MPEG-4 up to 50 MB, and Telegram's Bot API is far more restrictive about uploads than the regular Telegram client. If you're moving large files, check the current Bot API limits rather than assuming client behavior applies.
Common errors and fixes
Bad request: bad webhook: An HTTPS URL must be provided for webhook
Telegram won't send updates to anything but a public HTTPS address. This appears when n8n sits behind a reverse proxy that hasn't been told its own public URL. Set the WEBHOOK_URL environment variable to your public HTTPS address and terminate TLS in the proxy. That variable is covered in the Docker setup guide, and the full proxy configuration in the self-hosting walkthrough.
Error: Forbidden: bot is not a participant of the channel
The bot isn't in the channel. Add it through the channel's settings by searching its username — and make it an administrator, which channels require for posting.
The bot never responds, and there's no error at all. Almost always the webhook collision above. Check whether the workflow is active, and whether you last executed it from the editor.
The execution hangs, waiting for a trigger event. On self-hosted instances this is typically a reverse proxy without websocket support. Enable websocket proxying in Nginx, Caddy, Apache, or Traefik.
Bad Request: can't parse entities
Your Parse Mode doesn't match your text. Switch to HTML, or escape every reserved character MarkdownV2 requires.
Messages stop sending during a bulk run. Telegram's API caps sending at roughly 30 messages per second. Batch with a Loop Over Items node and add a delay between batches rather than firing a node once per recipient.
Messages arrive with an n8n advert appended. Turn off Append n8n Attribution under Additional Fields.
Chat ID is rejected as invalid.
For groups, you're probably missing the leading -. Group IDs are negative numbers.
Frequently asked questions
Can my bot message someone who hasn't contacted it first? No. Telegram requires the user to start the conversation, which is a deliberate anti-spam rule. For groups and channels, the bot has to be a member.
Do I need a server to build a Telegram bot in n8n? Only for receiving. Sending works from a local instance. The trigger needs a publicly reachable HTTPS URL because Telegram pushes updates to you.
Can two workflows use the same bot? Not reliably — they compete for the bot's single webhook registration. Use one trigger and branch internally, or create a second bot.
Is a Telegram bot free? Yes. Telegram's Bot API has no cost. Your only expense is wherever n8n runs.
How do I log messages to a spreadsheet? Chain a Google Sheets node onto the Telegram Trigger and append a row per message — the pattern is in the Google Sheets integration guide.
Can the bot respond to slash commands like /status?
Yes. Commands arrive as ordinary message text, so branch on the text with a Switch node. Register the command list with BotFather via /setcommands so it autocompletes for users.
Official references worth bookmarking: n8n's Telegram node documentation and Telegram's own Bot API reference.
Want it prebuilt? Importable Telegram workflows — alerts, a command-driven bot, and message logging — are on our templates page. Need the public HTTPS URL the trigger depends on? That's the self-hosting guide, and n8n in Docker if you're starting from scratch.