Skip to main content

Installation

npm install @chat-adapter/telegram

Environment Variables

Create a bot using @BotFather on Telegram to get your bot token.
VariableRequiredDescription
TELEGRAM_BOT_TOKENYesBot token from @BotFather
TELEGRAM_WEBHOOK_SECRET_TOKENOptionalSecret token for webhook verification (recommended)
TELEGRAM_BOT_USERNAMEOptionalBot username (without @)
TELEGRAM_API_BASE_URLOptionalCustom Bot API server URL (default: https://api.telegram.org)

Configuration Options

interface TelegramAdapterConfig {
  /** Bot token from @BotFather */
  botToken: string;
  /** Custom Bot API server URL (optional) */
  apiBaseUrl?: string;
  /** Operating mode: 'auto' | 'webhook' | 'polling' */
  mode?: TelegramAdapterMode;
  /** Long polling configuration (for polling mode) */
  longPolling?: TelegramLongPollingConfig;
  /** Secret token for webhook verification */
  secretToken?: string;
  /** Logger instance */
  logger: Logger;
  /** Override bot username */
  userName?: string;
}

interface TelegramLongPollingConfig {
  /** Delete webhook before starting polling (default: true) */
  deleteWebhook?: boolean;
  /** Drop pending updates (default: false) */
  dropPendingUpdates?: boolean;
  /** Max updates per request (1-100, default: 100) */
  limit?: number;
  /** Long polling timeout in seconds (0-300, default: 30) */
  timeout?: number;
  /** Retry delay in ms on error (default: 1000) */
  retryDelayMs?: number;
  /** Allowed update types (default: all) */
  allowedUpdates?: string[];
}

Setup

Webhook Handler

app.post('/webhooks/telegram', async (req, res) => {
  const response = await telegram.handleWebhook(req, {
    waitUntil: (promise) => {/* handle async work */},
  });
  res.status(response.status).send(await response.text());
});

Features

Supported Events

  • message - Regular messages in chats/groups
  • edited_message - Edited messages
  • channel_post - Channel posts
  • callback_query - Inline button clicks
  • message_reaction - Reactions added/removed

Message Types

Telegram supports:
  • Private chats - 1:1 conversations
  • Groups - Multi-user chats
  • Supergroups - Large groups with threads/topics
  • Channels - Broadcast channels

Topic Threads (Supergroups)

For groups with topics enabled:
// Thread ID includes topic ID:
// telegram:{chatId}:{messageThreadId}

// Messages automatically route to the correct topic
await thread.post('Reply in this topic');

Inline Keyboards (Buttons)

Create messages with inline buttons:
import { Card, Section, Button } from 'chat/cards';

await thread.post(
  <Card title="Confirmation">
    <Section text="Proceed with this action?" />
    <Button actionId="confirm" style="primary">Yes</Button>
    <Button actionId="cancel" style="danger">No</Button>
  </Card>
);
Handle button clicks:
chat.onAction('confirm', async (event) => {
  await event.thread.post('Confirmed!');
});
Telegram automatically acknowledges button clicks. The adapter calls answerCallbackQuery for you.

Reactions

Add/remove reactions:
// Unicode emoji
await thread.addReaction(messageId, '👍');
await thread.removeReaction(messageId, '👍');

// Emoji names (auto-converted to unicode)
await thread.addReaction(messageId, { name: 'thumbs_up' });

// Custom emoji (Telegram Premium)
await thread.addReaction(messageId, 'custom:5368324170671202286');

File Uploads

Send files with messages:
import { readFileSync } from 'fs';

await thread.post({
  text: 'Monthly report',
  files: [{
    filename: 'report.pdf',
    data: readFileSync('./report.pdf'),
    mimeType: 'application/pdf',
  }],
});
Telegram adapter supports one file per message. Multiple files will throw a validation error.

Markdown Formatting

Telegram supports markdown in messages:
await thread.post('**Bold** and *italic* text');

Thread IDs

Telegram thread IDs encode chat ID and optional topic:
telegram:{chatId}:{messageThreadId}
Examples:
  • Private chat: telegram:123456789
  • Group: telegram:-987654321
  • Group with topic: telegram:-987654321:42
Chat IDs are negative for groups/supergroups, positive for users.

Opening DMs

Create a private chat with a user:
const dmThreadId = await telegram.openDM(userId);
await chat.getThread(dmThreadId).post('Hello!');
User must have started a conversation with the bot first (via /start command or deep link).

Message History

Message history is cached in-memory:
const { messages, nextCursor } = await thread.fetchMessages({
  limit: 50,
  direction: 'backward',
});
Telegram Bot API doesn’t provide a message history endpoint. The adapter caches messages it receives. For persistent history, use a custom StateAdapter.

Platform Limits

  • Message length: 4,096 characters
  • Caption length: 1,024 characters (for files)
  • File size: 50 MB (20 MB via Bot API by default, 50 MB with local Bot API server)
  • Rate limits: 30 messages/second globally, 1 message/second per chat
  • Inline keyboard buttons: 100 buttons max
See Telegram limits for details.

Code Examples

chat.onNewMessage(async (event) => {
  if (event.message.text.startsWith('/start')) {
    await event.thread.post('Welcome! How can I help?');
  }
});

Polling Control

Manually start/stop polling:
// Start polling
await telegram.startPolling({
  timeout: 30,
  limit: 50,
  dropPendingUpdates: true, // Ignore old updates
});

// Check status
console.log('Polling active:', telegram.isPolling);
console.log('Runtime mode:', telegram.runtimeMode); // 'polling' or 'webhook'

// Stop polling
await telegram.stopPolling();

Troubleshooting

  • Set secretToken in adapter config and webhook URL
  • Verify secret token matches in both places
  • Check that header x-telegram-bot-api-secret-token is present
  • Disable Privacy Mode via @BotFather
  • Or add bot as admin to receive all messages
  • Groups require /setprivacy disabled to receive non-command messages
  • Only one polling instance can run at a time
  • Use telegram.stopPolling() before starting a new instance
  • Check telegram.isPolling before starting
  • Verify file size is under 50 MB
  • Only one file per message is supported
  • Use proper MIME types (optional but recommended)

Bot Commands

Register commands via @BotFather:
/setcommands

start - Start the bot
help - Show help
status - Check status
Handle commands in code:
chat.onNewMessage(async (event) => {
  const text = event.message.text;
  
  if (text === '/start') {
    await event.thread.post('Bot started!');
  }
  
  if (text === '/help') {
    await event.thread.post('Available commands: /start, /help, /status');
  }
});

Next Steps

Message Handling

Process messages and build conversation flows

Inline Keyboards

Create interactive buttons with cards

Telegram Bot API

Learn about Telegram’s Bot API

State Management

Persist data across requests