Guide 02

How to build a Telegram bot: tokens, webhooks, keyboards and limits

Telegram has the friendliest bot platform: a free HTTP API, no review process and features that range from simple commands to full web apps.

Guide3 min readUpdated

Create the bot

  1. Open a chat with @BotFather (check the blue verification mark).
  2. Send /newbot, choose a display name and a username ending in bot.
  3. BotFather replies with a token like 123456789:AA.... Treat it as a password.
  4. Optional but recommended: /setdescription, /setabouttext, /setuserpic and /setcommands so users understand what the bot does.

Test the token with the Telegram API builder using the getMe method, or:

curl -s "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getMe"

How the Bot API works

Every call is an HTTPS request to https://api.telegram.org/bot<token>/<method>. Responses are JSON with ok, and either result or error_code with description. Incoming events are updates: each has an increasing update_id and one payload such as message, edited_message, callback_query, inline_query or my_chat_member.

Receiving updates: polling or webhook

Long polling with getUpdates is perfect for development. Pass offset as the last update_id + 1 to confirm earlier updates, and timeout (for example 30) so the request waits for new events.

Webhooks are better for production. Register an HTTPS URL once:

curl -s "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://bot.example.com/telegram","secret_token":"'"$TG_WEBHOOK_SECRET"'","allowed_updates":["message","callback_query"],"drop_pending_updates":true}'
  • Telegram sends the secret_token in the X-Telegram-Bot-Api-Secret-Token header. Reject any request without the exact value.
  • Supported ports are 443, 80, 88 and 8443, with a valid TLS certificate.
  • Return 200 quickly. If you answer with an error or time out, Telegram retries and pending updates accumulate. Inspect them with getWebhookInfo, which also shows last_error_message.
  • You cannot use getUpdates while a webhook is set.

A minimal bot with grammY

import { Bot, InlineKeyboard } from "grammy";

const bot = new Bot(process.env.TELEGRAM_BOT_TOKEN!);

bot.command("start", (ctx) =>
  ctx.reply("Hi! I can remind you about things. Try /remind 10m stretch", {
    reply_markup: new InlineKeyboard().text("How it works", "help"),
  })
);

bot.callbackQuery("help", async (ctx) => {
  await ctx.answerCallbackQuery();             // always answer, or the button spins
  await ctx.reply("Send /remind <minutes>m <text>. Use /stop to delete your data.");
});

bot.on("message:text", (ctx) => ctx.reply("Unknown command. Try /help."));
bot.catch((err) => console.error("handler error", err.error));
bot.start();                                   // long polling for development

The same library runs as a webhook on Node.js, Deno or Cloudflare Workers.

Commands, keyboards and formatting

  • Commands start with /. In groups they may arrive as /start@your_bot; parse with the bot_command entity instead of a regex when possible.
  • Reply keyboards replace the user's keyboard with buttons that send text. Inline keyboards attach buttons to a message: url buttons open links, callback_data buttons (up to 64 bytes) send a callback_query you must answer with answerCallbackQuery.
  • Formatting: use parse_mode HTML or MarkdownV2. MarkdownV2 requires escaping characters such as _ * [ ] ( ) ~ > # + - = | { } . !. HTML is usually easier: escape <, > and &. The API builder includes an escaper.
  • Messages are limited to 4096 characters; captions to 1024.

Groups, privacy mode and admin rights

By default bots have privacy mode on in groups: they only see commands, replies to their own messages and mentions. That is the right default. Turn it off in BotFather only if the bot must read every message (for example moderation), and say so in its description. Check permissions with getChatMember before running admin commands, and authorize by numeric user ID, never by username.

Files, payments and Mini Apps

  • Download user files with getFile, then https://api.telegram.org/file/bot<token>/<file_path>. Bots can download files up to 20 MB through the cloud API.
  • Digital goods sold inside Telegram use Telegram Stars; physical goods can use supported payment providers.
  • Mini Apps are web pages opened inside Telegram. Always validate initData on your server: compute secret_key = HMAC_SHA256(key="WebAppData", data=bot_token), then compare HMAC_SHA256(secret_key, data_check_string) with the hash field, and check auth_date is recent.

Limits to respect

Telegram publishes soft limits: avoid sending more than about one message per second to the same chat, about 20 messages per minute to the same group, and around 30 messages per second overall for broadcasts. Exceed them and you receive 429 Too Many Requests with parameters.retry_after; wait that many seconds. For newsletters, queue messages and send at a steady rate. See reliability and rate limits.

Good citizenship

  • Only message users who started the bot. Respect /stop: when a user blocks the bot you receive a my_chat_member update with status kicked; delete their data.
  • Do not ask for phone numbers or locations unless the feature needs them, and explain why.
  • Read the Telegram bot developer terms and publish a privacy policy link in the bot description.

Check your understanding

1. Which header carries the Telegram webhook secret_token?
2. What must you do after receiving a callback_query?