Skip to content
Innovialabs

How to Connect ChatGPT or Claude to a Telegram Bot

Practice4 min

In short

The bot's backend receives a message, loads that user's recent history, calls the LLM API with a key stored only on the server, streams the answer by editing one message, and enforces per-user limits and input checks.

How the setup works

The flow is simple: Telegram → your backend → LLM API → back to Telegram. The backend receives an update via webhook or polling, adds the user's message to their history, sends the history to the OpenAI or Anthropic API and returns the answer to the chat.

Everything that matters for quality, cost and safety happens in that middle layer: context storage, streaming, formatting, limits and moderation.

API keys stay on the backend

  • Keep the key in an environment variable or a secrets manager, never in the repository or a Mini App frontend.
  • Use separate keys for development and production, so a leaked test key does not expose the live account.
  • Set spending limits and alerts in the provider's console.
  • If a key leaks, revoke it and issue a new one immediately; do not wait to see whether it gets used.

Dialog context per user

LLM APIs are stateless: each request must include the conversation so far. You store it yourself.

  • Keep messages in Redis or a database, keyed by chat_id (in groups, by chat and user).
  • Trim the history: keep the last N messages or stay within a token budget. Older parts can be replaced with a short summary.
  • Add a /new command so users can reset the context when they change topics.
  • Tell users that conversations are stored and for how long, and delete old data on schedule.

Long histories are the main reason costs grow quietly: every new message re-sends everything before it.

Streaming by editing messages

Waiting for a long answer with no feedback feels broken. The usual approach: send a placeholder, then edit it as text arrives.

import time
from aiogram.exceptions import TelegramBadRequest

async def stream_reply(message, history):
    sent = await message.answer("...")
    text, last_edit = "", 0.0
    async for chunk in llm_stream(history):  # your wrapper over the SDK
        text += chunk
        if time.monotonic() - last_edit > 1.5:
            await safe_edit(sent, text)
            last_edit = time.monotonic()
    await safe_edit(sent, text)
    return text

async def safe_edit(sent, text):
    try:
        await sent.edit_text(text[:4096])
    except TelegramBadRequest:
        pass  # e.g. "message is not modified"

Key rules:

  • Edit every second or two, not on every token. Telegram rate-limits edits, and too many requests return a 429 error with a retry_after value.
  • A message holds up to 4096 characters. When the answer grows past that, finish the current message and continue in a new one.
  • Stream as plain text and apply formatting only to the final version.
  • Newer Bot API versions add native draft streaming in private chats; check the official docs for what your library supports.

Markdown formatting pitfalls

Models write standard Markdown: **bold**, # headings, tables. Telegram's MarkdownV2 is a different dialect: bold is *text*, and characters such as _ * [ ] ( ) ~ > # + - = | { } . ! must be escaped. One unescaped dot or unclosed asterisk and the API returns "can't parse entities".

A reliable approach:

  1. Convert the model's Markdown to Telegram HTML (<b>, <i>, <code>, <pre>, <a>), escaping <, > and & in the text.
  2. Turn headings into bold lines and tables into lists or a code block, since Telegram has no tables.
  3. If sending still fails, fall back to plain text instead of losing the answer.
  4. Ask the model in the system prompt to avoid tables and deep nesting.

Rate and cost limits

LimitWhy
Requests per user per minuteStops spam and scripts
Daily token budget per userCaps the cost of one heavy user
Max output tokens per answerPrevents runaway responses
Max input lengthBlocks pasted novels
One active generation per userAvoids parallel requests from double taps
Global monthly cap with alertsProtects the whole budget

Count usage from the usage field in API responses rather than estimating, and show users a clear message when they hit a limit.

Moderating user input

  • Check length and obvious spam before calling the model.
  • Run input through a moderation endpoint or a cheap classifier model if the bot is public.
  • Treat prompt injection as a given: users will try to override the system prompt. Do not put secrets in it, and if the model can call tools, validate every action on the server.
  • Log flagged messages, let admins block users, and check outputs too if the bot works in public groups.

FAQ

Both APIs fit this setup in the same way. Compare answer quality on your real tasks, price and data policies, and keep a thin wrapper so you can switch providers without rewriting the bot.

Read next

Have an idea or a task? Tell us about it

We'll reply within one business day and propose a solution. The consultation is free.