AI Agent Telegram Integration Setup — A Practical Guide

Square

I set up my first Telegram bot in 2022 and it took about six hours because the documentation I was reading was two API versions out of date and the webhook kept returning 502 errors from a misconfigured nginx reverse proxy. That’s the kind of detail nobody puts in a tutorial.

So here’s the actual guide. Not the theoretical one.

Telegram’s Bot API is genuinely one of the better-documented messaging APIs out there. It’s free. No per-message fees, no verification beyond typing /newbot into a chat with @BotFather. But “free and well-documented” and “easy to build something production-grade” are not the same thing, and most projects learn that distinction around day three when the bot goes silent and the logs just say connection reset by peer.

What You Actually Need

Before writing a single line of code, get these four things. Not three. Four.

First: a Telegram bot token. Open Telegram, search @BotFather, send /newbot, answer the two prompts (display name, username ending in “bot”), and BotFather drops a token like 123456789:ABCdefGhIJKlmnOPqrStUvWxYz. Treat it like a root password — anyone with it can post as your bot.

Second: a public HTTPS endpoint or a machine that can poll Telegram’s servers. This fork in the road defines your whole architecture.

Third: an LLM API key (OpenAI, Anthropic, OpenRouter — whatever you’re using). Keep it separate from your bot token. Different .env variable, different mental compartment.

Fourth: some kind of state store. SQLite counts. But you need something because Telegram doesn’t give you conversation history. The chat scroll is the user’s, not yours. I learned this the embarrassing way — built an onboarding flow that broke because the bot couldn’t remember step one by step three.

Step-by-Step Setup

1. Create the Bot

After /newbot, run these secondary commands too. /setdescription gives users a one-liner when they open the chat. /setcommands populates the slash-command menu — start - Begin, help - Show usage. /setprivacy to Disabled if your bot lives in group chats and needs to read all messages, not just mentions.

One thing that catches every beginner: a Telegram bot cannot initiate a conversation. The user messages first, always. So during development, message your bot once before testing anything that expects to reply, or you’ll spend twenty minutes debugging code that’s perfectly fine.

2. Polling vs Webhooks

Two options. This decision shapes everything.

Long polling: Your bot calls getUpdates in a loop, Telegram holds the connection open for up to 50 seconds, and the call returns with new messages or empty. Dead simple — no SSL, no domain, no DNS. Runs on a Raspberry Pi behind a home router. The tradeoff: one TCP connection per cycle and latency proportional to your timeout. Five-second polling feels sluggish; fifty-second polling consumes resources.

Webhooks: You tell Telegram “here’s my URL, call me when something happens,” and Telegram POSTs updates in near-real-time. Scales better, uses less bandwidth, what every production bot should use. But you need a public HTTPS endpoint with a valid certificate. No self-signed — Telegram checks.

And honestly? Most people poll during development and switch to webhooks before shipping. It’s the sensible path.

3. Polling Setup

python-telegram-bot v22.x makes this nearly trivial:

from telegram.ext import Application, MessageHandler, filters
import os

async def handle_message(update, context):
    await update.message.reply_text(“Got it.”)

app = Application.builder().token(os.environ[“TELEGRAM_BOT_TOKEN”]).build()
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
app.run_polling()

Twelve lines, working chatbot. Swap reply_text for an LLM call and you’re passing messages to GPT-4o. But here’s the kicker: run_polling() blocks. Your laptop sleeps, your bot sleeps. The fix is deployment — a $5/month Hetzner CX22 VPS, or a Raspberry Pi 5 with a UPS. The model runs on the provider’s servers, so hardware requirements are minimal.

4. Webhook Setup

More moving parts, cleaner in production:

curl -F “url=https://your-domain.com/webhook” \
    -F “secret_token=a-random-string” \
    https://api.telegram.org/bot<TOKEN>/setWebhook

That secret_token is optional. Don’t skip it. It adds an X-Telegram-Bot-Api-Secret-Token header to every request and your server verifies it before processing. Without it, anyone who finds your webhook URL can send fake updates. I’ve seen this exploited — a bot approving expense reports suddenly had a very expensive week.

Your webhook handler (FastAPI, Express, doesn’t matter) does three things: verify the secret token, return HTTP 200 immediately, and hand the update to a queue. Never make Telegram wait while you call an LLM. Telegram retries non-200 responses for up to 24 hours with exponential backoff. If your handler takes 8 seconds for GPT-4o, Telegram might already be retrying, and the user gets duplicates.

The pattern: webhook handler → message queue (Redis, SQS) → worker → LLM → worker sends reply. Adds complexity, eliminates a whole class of production bugs.

For local webhook dev, use a tunnel. ngrok is standard — ngrok http 8000 gives you a public HTTPS URL. Pinggy and Tunnelmole are open-source alternatives. Update the webhook URL when the tunnel restarts, or script it.

Webhooks vs Polling: Quick Comparison

Polling is simpler. No domain, no SSL, no queue. But wasteful — calling getUpdates whether there are messages or not. At 10,000+ users this shows in server bills.

Webhooks scale. Event-driven, resource-efficient, Telegram’s recommendation for production. But the infrastructure tax is real: domain, HTTPS cert, reverse proxy, secret validation, idempotency. You’re building a backend service, not a script.

The thing is, neither is wrong. They’re different stages. Polling gets you to “it works” in an afternoon. Webhooks get you to “it works at 3 AM without me waking up.”

Error Handling That Actually Helps

Telegram returns JSON with error_code and description. Most guides say “handle errors gracefully.” Here’s what that means with real codes:

429 / FLOOD_WAIT_X. Rate limited. X is seconds to wait. Don’t retry immediately — the limiter is aggressive. Sleep X+1 seconds. For volume, stay under ~30 req/sec across chats, spread to stay within per-chat limits.

400: chat not found. User blocked your bot, deleted their account, or the chat_id doesn’t exist. Don’t retry. Log, remove from active users, move on.

403: bot was blocked. User opted out. Respect it, clean up their data.

400: message not modified. Edit with identical content. Harmless. Catch and ignore.

400: message to delete not found. Already deleted or older than 48 hours. Log and skip.

Wrap every API call in a retry layer that knows retryable (429, 5xx) vs non-retryable (403, 400 chat-not-found). Python’s tenacity library handles this in about 15 lines.

Real Production Config

Not a tutorial config. A real one:

  • Token management: HashiCorp Vault or AWS Secrets Manager, not .env files in git history. Fine for personal projects, unacceptable for anything touching customer data.
  • Webhook handler: FastAPI behind nginx, 2-second client timeout. Validates secret in under 50ms, drops update on Redis, returns 200.
  • Worker: Separate process pulling from Redis, calling LLM with tool definitions, sending replies.
  • Memory: PostgreSQL keyed by chat_id for last 20 turns, pgvector for semantic search, Redis 24-hour TTL cache.
  • Allowlist: Check update.effective_user.id against ALLOWED_USERS. Without it, your bot is a free API endpoint.
  • Monitoring: Prometheus + Grafana for latency, error rates, queue depth, token spend per user.
  • Idempotency: Store every update_id, skip duplicates. Telegram retries webhooks and duplicates create ghost responses.

What to Watch Out For

Things from actual support threads and production logs. Not hypotheticals.

The bot can’t initiate chat. Users message first. Every onboarding flow that assumes proactive outreach breaks here. Design around it.

Rate limits are per-chat. You can send 30 messages/sec to different chats but ~1/sec to the same chat. Bulk notifications need batching.

editMessageText fails on 48h+ messages. Can’t edit old messages. Send a new one, or update inline keyboard markup without changing the text.

Webhook secrets leak. They will — git commit, log line, error in a dashboard. Have a rotation plan. Telegram supports one secret_token at a time, so your handler must accept both old and new during rotation.

Group privacy mode. Default: bot only sees mentions and slash commands. Disable in BotFather to read everything. But think about whether reading 200-person group chatter is actually what you want. Moderation bot: yes. Personal assistant: probably not.

4096-character limit. Telegram truncates at 4096 chars per message (1024 for captions). Split long LLM output into multiple sendMessage calls or attach as a .txt file.

Anyway. That’s the setup. It’s straightforward once you’ve done it once, confusing the first time, and full of details that don’t seem important until they ruin your Monday morning. Bots that work at 2 PM in development and bots that work at 3 AM on a Saturday are different beasts — and the difference is almost never the AI part.

Leave a Reply

Your email address will not be published. Required fields are marked *