Installation
npm install @chat-adapter/telegram
Environment Variables
Create a bot using @BotFather on Telegram to get your bot token.
Variable Required Description TELEGRAM_BOT_TOKENYes Bot token from @BotFather TELEGRAM_WEBHOOK_SECRET_TOKENOptional Secret token for webhook verification (recommended) TELEGRAM_BOT_USERNAMEOptional Bot username (without @) TELEGRAM_API_BASE_URLOptional Custom 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
Auto Mode (Recommended)
Webhook Mode
Polling Mode
Adapter automatically chooses webhook or polling based on environment: import { Chat } from 'chat' ;
import { createTelegramAdapter } from '@chat-adapter/telegram' ;
import { MemoryState } from '@chat-adapter/state-memory' ;
const chat = new Chat ({
userName: 'my_bot' ,
adapters: {
telegram: createTelegramAdapter ({
botToken: process . env . TELEGRAM_BOT_TOKEN ! ,
secretToken: process . env . TELEGRAM_WEBHOOK_SECRET_TOKEN ,
mode: 'auto' , // Default
}),
},
state: new MemoryState (),
});
await chat . initialize ();
Auto mode logic:
If webhook is configured → use webhook mode
If no webhook and not serverless → use polling mode
If no webhook and serverless → keep webhook mode (manual setup required)
For serverless platforms (Vercel, AWS Lambda, etc.): import { Chat } from 'chat' ;
import { createTelegramAdapter } from '@chat-adapter/telegram' ;
import { MemoryState } from '@chat-adapter/state-memory' ;
const chat = new Chat ({
userName: 'my_bot' ,
adapters: {
telegram: createTelegramAdapter ({
botToken: process . env . TELEGRAM_BOT_TOKEN ! ,
secretToken: process . env . TELEGRAM_WEBHOOK_SECRET_TOKEN ,
mode: 'webhook' ,
}),
},
state: new MemoryState (),
});
await chat . initialize ();
Set webhook URL using Telegram Bot API: curl -X POST https://api.telegram.org/bot < TOKE N > /setWebhook \
-d "url=https://your-app.com/webhooks/telegram" \
-d "secret_token=<SECRET>"
For long-running servers (good for development): import { Chat } from 'chat' ;
import { createTelegramAdapter } from '@chat-adapter/telegram' ;
import { MemoryState } from '@chat-adapter/state-memory' ;
const chat = new Chat ({
userName: 'my_bot' ,
adapters: {
telegram: createTelegramAdapter ({
botToken: process . env . TELEGRAM_BOT_TOKEN ! ,
mode: 'polling' ,
longPolling: {
timeout: 30 ,
limit: 100 ,
// Optional: filter update types
allowedUpdates: [ 'message' , 'callback_query' , 'message_reaction' ],
},
}),
},
state: new MemoryState (),
});
await chat . initialize ();
// Polling starts automatically in initialize()
Polling mode automatically deletes any configured webhook. The bot will continuously poll for updates.
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' );
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.
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.
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
Message Handler
Mention Handler
Button Handler
Reaction Handler
Inline Keyboard
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
Webhook verification fails
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
Bot doesn't respond in groups
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