Telegram
Telegram (Bot API)
Section titled “Telegram (Bot API)”Status: production-ready for bot DMs + groups via grammY. Long-polling by default; webhook optional.
Quick setup (beginner)
Section titled “Quick setup (beginner)”- Create a bot with @BotFather and copy the token.
- Set the token:
- Env:
TELEGRAM_BOT_TOKEN=... - Or config:
channels.telegram.botToken: "...". - If both are set, config takes precedence (env fallback is default-account only).
- Env:
- Start the gateway.
- DM access is pairing by default; approve the pairing code on first contact.
Minimal config:
{ channels: { telegram: { enabled: true, botToken: "123:abc", dmPolicy: "pairing" } }}What it is
Section titled “What it is”- A Telegram Bot API channel owned by the Gateway.
- Deterministic routing: replies go back to Telegram; the model never chooses channels.
- DMs share the agent’s main session; groups stay isolated (
agent:<agentId>:telegram:group:<chatId>).
Setup (fast path)
Section titled “Setup (fast path)”1) Create a bot token (BotFather)
Section titled “1) Create a bot token (BotFather)”- Open Telegram and chat with @BotFather.
- Run
/newbot, then follow the prompts (name + username ending inbot). - Copy the token and store it safely.
Optional BotFather settings:
/setjoingroups— allow/deny adding the bot to groups./setprivacy— control whether the bot sees all group messages.
2) Configure the token (env or config)
Section titled “2) Configure the token (env or config)”Example:
{ channels: { telegram: { enabled: true, botToken: "123:abc", dmPolicy: "pairing", groups: { "*": { requireMention: true } } } }}Env option: TELEGRAM_BOT_TOKEN=... (works for the default account). If both env and config are set, config takes precedence.
Multi-account support: use channels.telegram.accounts with per-account tokens and optional name. See gateway/configuration for the shared pattern.
- Start the gateway. Telegram starts when a token is resolved (config first, env fallback).
- DM access defaults to pairing. Approve the code when the bot is first contacted.
- For groups: add the bot, decide privacy/admin behavior (below), then set
channels.telegram.groupsto control mention gating + allowlists.
Token + privacy + permissions (Telegram side)
Section titled “Token + privacy + permissions (Telegram side)”Token creation (BotFather)
Section titled “Token creation (BotFather)”/newbotcreates the bot and returns the token (keep it secret).- If a token leaks, revoke/regenerate it via @BotFather and update your config.
Group message visibility (Privacy Mode)
Section titled “Group message visibility (Privacy Mode)”Telegram bots default to Privacy Mode, which limits which group messages they receive. If your bot must see all group messages, you have two options:
- Disable privacy mode with
/setprivacyor - Add the bot as a group admin (admin bots receive all messages).
Note: When you toggle privacy mode, Telegram requires removing + re‑adding the bot to each group for the change to take effect.
Group permissions (admin rights)
Section titled “Group permissions (admin rights)”Admin status is set inside the group (Telegram UI). Admin bots always receive all group messages, so use admin if you need full visibility.
How it works (behavior)
Section titled “How it works (behavior)”- Inbound messages are normalized into the shared channel envelope with reply context and media placeholders.
- Group replies require a mention by default (native @mention or
agents.list[].groupChat.mentionPatterns/messages.groupChat.mentionPatterns). - Multi-agent override: set per-agent patterns on
agents.list[].groupChat.mentionPatterns. - Replies always route back to the same Telegram chat.
- Long-polling uses grammY runner with per-chat sequencing; overall concurrency is capped by
agents.defaults.maxConcurrent. - Telegram Bot API does not support read receipts; there is no
sendReadReceiptsoption.
Formatting (Telegram HTML)
Section titled “Formatting (Telegram HTML)”- Outbound Telegram text uses
parse_mode: "HTML"(Telegram’s supported tag subset). - Markdown-ish input is rendered into Telegram-safe HTML (bold/italic/strike/code/links); block elements are flattened to text with newlines/bullets.
- Raw HTML from models is escaped to avoid Telegram parse errors.
- If Telegram rejects the HTML payload, OpenClaw retries the same message as plain text.
Commands (native + custom)
Section titled “Commands (native + custom)”OpenClaw registers native commands (like /status, /reset, /model) with Telegram’s bot menu on startup. You can add custom commands to the menu via config:
{ channels: { telegram: { customCommands: [ { command: "backup", description: "Git backup" }, { command: "generate", description: "Create an image" } ] } }}Troubleshooting
Section titled “Troubleshooting”setMyCommands failedin logs usually means outbound HTTPS/DNS is blocked toapi.telegram.org.- If you see
sendMessageorsendChatActionfailures, check IPv6 routing and DNS.
More help: Channel troubleshooting.
Notes:
- Custom commands are menu entries only; OpenClaw does not implement them unless you handle them elsewhere.
- Command names are normalized (leading
/stripped, lowercased) and must matcha-z,0-9,_(1–32 chars). - Custom commands cannot override native commands. Conflicts are ignored and logged.
- If
commands.nativeis disabled, only custom commands are registered (or cleared if none).
Limits
Section titled “Limits”- Outbound text is chunked to
channels.telegram.textChunkLimit(default 4000). - Optional newline chunking: set
channels.telegram.chunkMode="newline"to split on blank lines (paragraph boundaries) before length chunking. - Media downloads/uploads are capped by
channels.telegram.mediaMaxMb(default 5). - Telegram Bot API requests time out after
channels.telegram.timeoutSeconds(default 500 via grammY). Set lower to avoid long hangs. - Group history context uses
channels.telegram.historyLimit(orchannels.telegram.accounts.*.historyLimit), falling back tomessages.groupChat.historyLimit. Set0to disable (default 50). - DM history can be limited with
channels.telegram.dmHistoryLimit(user turns). Per-user overrides:channels.telegram.dms["<user_id>"].historyLimit.
Group activation modes
Section titled “Group activation modes”By default, the bot only responds to mentions in groups (@botname or patterns in agents.list[].groupChat.mentionPatterns). To change this behavior:
Via config (recommended)
Section titled “Via config (recommended)”{ channels: { telegram: { groups: { "-1001234567890": { requireMention: false } // always respond in this group } } }}Important: Setting channels.telegram.groups creates an allowlist - only listed groups (or "*") will be accepted. Forum topics inherit their parent group config (allowFrom, requireMention, skills, prompts) unless you add per-topic overrides under channels.telegram.groups.<groupId>.topics.<topicId>.
To allow all groups with always-respond:
{ channels: { telegram: { groups: { "*": { requireMention: false } // all groups, always respond } } }}To keep mention-only for all groups (default behavior):
{ channels: { telegram: { groups: { "*": { requireMention: true } // or omit groups entirely } } }}Via command (session-level)
Section titled “Via command (session-level)”Send in the group:
/activation always- respond to all messages/activation mention- require mentions (default)
Note: Commands update session state only. For persistent behavior across restarts, use config.
Getting the group chat ID
Section titled “Getting the group chat ID”Forward any message from the group to @userinfobot or @getidsbot on Telegram to see the chat ID (negative number like -1001234567890).
Tip: For your own user ID, DM the bot and it will reply with your user ID (pairing message), or use /whoami once commands are enabled.
Privacy note: @userinfobot is a third-party bot. If you prefer, add the bot to the group, send a message, and use openclaw logs --follow to read chat.id, or use the Bot API getUpdates.
Config writes
Section titled “Config writes”By default, Telegram is allowed to write config updates triggered by channel events or /config set|unset.
This happens when:
- A group is upgraded to a supergroup and Telegram emits
migrate_to_chat_id(chat ID changes). OpenClaw can migratechannels.telegram.groupsautomatically. - You run
/config setor/config unsetin a Telegram chat (requirescommands.config: true).
Disable with:
{ channels: { telegram: { configWrites: false } }}Topics (forum supergroups)
Section titled “Topics (forum supergroups)”Telegram forum topics include a message_thread_id per message. OpenClaw:
- Appends
:topic:<threadId>to the Telegram group session key so each topic is isolated. - Sends typing indicators and replies with
message_thread_idso responses stay in the topic. - General topic (thread id
1) is special: message sends omitmessage_thread_id(Telegram rejects it), but typing indicators still include it. - Exposes
MessageThreadId+IsForumin template context for routing/templating. - Topic-specific configuration is available under
channels.telegram.groups.<chatId>.topics.<threadId>(skills, allowlists, auto-reply, system prompts, disable). - Topic configs inherit group settings (requireMention, allowlists, skills, prompts, enabled) unless overridden per topic.
Private chats can include message_thread_id in some edge cases. OpenClaw keeps the DM session key unchanged, but still uses the thread id for replies/draft streaming when it is present.
Inline Buttons
Section titled “Inline Buttons”Telegram supports inline keyboards with callback buttons.
{ "channels": { "telegram": { "capabilities": { "inlineButtons": "allowlist" } } }}For per-account configuration:
{ "channels": { "telegram": { "accounts": { "main": { "capabilities": { "inlineButtons": "allowlist" } } } } }}Scopes:
off— inline buttons disableddm— only DMs (group targets blocked)group— only groups (DM targets blocked)all— DMs + groupsallowlist— DMs + groups, but only senders allowed byallowFrom/groupAllowFrom(same rules as control commands)
Default: allowlist. Legacy: capabilities: ["inlineButtons"] = inlineButtons: "all".
Sending buttons
Section titled “Sending buttons”Use the message tool with the buttons parameter:
{ "action": "send", "channel": "telegram", "to": "123456789", "message": "Choose an option:", "buttons": [ [ {"text": "Yes", "callback_data": "yes"}, {"text": "No", "callback_data": "no"} ], [ {"text": "Cancel", "callback_data": "cancel"} ] ]}When a user clicks a button, the callback data is sent back to the agent as a message with the format: callback_data: value
Configuration options
Section titled “Configuration options”Telegram capabilities can be configured at two levels (object form shown above; legacy string arrays still supported):
channels.telegram.capabilities: Global default capability config applied to all Telegram accounts unless overridden.channels.telegram.accounts.<account>.capabilities: Per-account capabilities that override the global defaults for that specific account.
Use the global setting when all Telegram bots/accounts should behave the same. Use per-account configuration when different bots need different behaviors (for example, one account only handles DMs while another is allowed in groups).
Access control (DMs + groups)
Section titled “Access control (DMs + groups)”DM access
Section titled “DM access”- Default:
channels.telegram.dmPolicy = "pairing". Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour). - Approve via:
openclaw pairing list telegramopenclaw pairing approve telegram <CODE>
- Pairing is the default token exchange used for Telegram DMs. Details: Pairing
channels.telegram.allowFromaccepts numeric user IDs (recommended) or@usernameentries. It is not the bot username; use the human sender’s ID. The wizard accepts@usernameand resolves it to the numeric ID when possible.
Finding your Telegram user ID
Section titled “Finding your Telegram user ID”Safer (no third-party bot):
- Start the gateway and DM your bot.
- Run
openclaw logs --followand look forfrom.id.
Alternate (official Bot API):
- DM your bot.
- Fetch updates with your bot token and read
message.from.id:Terminal window curl "https://api.telegram.org/bot<bot_token>/getUpdates"
Third-party (less private):
- DM
@userinfobotor@getidsbotand use the returned user id.
Group access
Section titled “Group access”Two independent controls:
1. Which groups are allowed (group allowlist via channels.telegram.groups):
- No
groupsconfig = all groups allowed - With
groupsconfig = only listed groups or"*"are allowed - Example:
"groups": { "-1001234567890": {}, "*": {} }allows all groups
2. Which senders are allowed (sender filtering via channels.telegram.groupPolicy):
"open"= all senders in allowed groups can message"allowlist"= only senders inchannels.telegram.groupAllowFromcan message"disabled"= no group messages accepted at all Default isgroupPolicy: "allowlist"(blocked unless you addgroupAllowFrom).
Most users want: groupPolicy: "allowlist" + groupAllowFrom + specific groups listed in channels.telegram.groups
Long-polling vs webhook
Section titled “Long-polling vs webhook”- Default: long-polling (no public URL required).
- Webhook mode: set
channels.telegram.webhookUrl(optionallychannels.telegram.webhookSecret+channels.telegram.webhookPath).- The local listener binds to
0.0.0.0:8787and servesPOST /telegram-webhookby default. - If your public URL is different, use a reverse proxy and point
channels.telegram.webhookUrlat the public endpoint.
- The local listener binds to
Reply threading
Section titled “Reply threading”Telegram supports optional threaded replies via tags:
[[reply_to_current]]— reply to the triggering message.[[reply_to:<id>]]— reply to a specific message id.
Controlled by channels.telegram.replyToMode:
first(default),all,off.
Audio messages (voice vs file)
Section titled “Audio messages (voice vs file)”Telegram distinguishes voice notes (round bubble) from audio files (metadata card). OpenClaw defaults to audio files for backward compatibility.
To force a voice note bubble in agent replies, include this tag anywhere in the reply:
[[audio_as_voice]]— send audio as a voice note instead of a file.
The tag is stripped from the delivered text. Other channels ignore this tag.
For message tool sends, set asVoice: true with a voice-compatible audio media URL (message is optional when media is present):
{ "action": "send", "channel": "telegram", "to": "123456789", "media": "https://example.com/voice.ogg", "asVoice": true}Stickers
Section titled “Stickers”OpenClaw supports receiving and sending Telegram stickers with intelligent caching.
Receiving stickers
Section titled “Receiving stickers”When a user sends a sticker, OpenClaw handles it based on the sticker type:
- Static stickers (WEBP): Downloaded and processed through vision. The sticker appears as a
<media:sticker>placeholder in the message content. - Animated stickers (TGS): Skipped (Lottie format not supported for processing).
- Video stickers (WEBM): Skipped (video format not supported for processing).
Template context field available when receiving stickers:
Sticker— object with:emoji— emoji associated with the stickersetName— name of the sticker setfileId— Telegram file ID (send the same sticker back)fileUniqueId— stable ID for cache lookupcachedDescription— cached vision description when available
Sticker cache
Section titled “Sticker cache”Stickers are processed through the AI’s vision capabilities to generate descriptions. Since the same stickers are often sent repeatedly, OpenClaw caches these descriptions to avoid redundant API calls.
How it works:
- First encounter: The sticker image is sent to the AI for vision analysis. The AI generates a description (e.g., “A cartoon cat waving enthusiastically”).
- Cache storage: The description is saved along with the sticker’s file ID, emoji, and set name.
- Subsequent encounters: When the same sticker is seen again, the cached description is used directly. The image is not sent to the AI.
Cache location: ~/.openclaw/telegram/sticker-cache.json
Cache entry format:
{ "fileId": "CAACAgIAAxkBAAI...", "fileUniqueId": "AgADBAADb6cxG2Y", "emoji": "👋", "setName": "CoolCats", "description": "A cartoon cat waving enthusiastically", "cachedAt": "2026-01-15T10:30:00.000Z"}Benefits:
- Reduces API costs by avoiding repeated vision calls for the same sticker
- Faster response times for cached stickers (no vision processing delay)
- Enables sticker search functionality based on cached descriptions
The cache is populated automatically as stickers are received. There is no manual cache management required.
Sending stickers
Section titled “Sending stickers”The agent can send and search stickers using the sticker and sticker-search actions. These are disabled by default and must be enabled in config:
{ channels: { telegram: { actions: { sticker: true } } }}Send a sticker:
{ "action": "sticker", "channel": "telegram", "to": "123456789", "fileId": "CAACAgIAAxkBAAI..."}Parameters:
fileId(required) — the Telegram file ID of the sticker. Obtain this fromSticker.fileIdwhen receiving a sticker, or from asticker-searchresult.replyTo(optional) — message ID to reply to.threadId(optional) — message thread ID for forum topics.
Search for stickers:
The agent can search cached stickers by description, emoji, or set name:
{ "action": "sticker-search", "channel": "telegram", "query": "cat waving", "limit": 5}Returns matching stickers from the cache:
{ "ok": true, "count": 2, "stickers": [ { "fileId": "CAACAgIAAxkBAAI...", "emoji": "👋", "description": "A cartoon cat waving enthusiastically", "setName": "CoolCats" } ]}The search uses fuzzy matching across description text, emoji characters, and set names.
Example with threading:
{ "action": "sticker", "channel": "telegram", "to": "-1001234567890", "fileId": "CAACAgIAAxkBAAI...", "replyTo": 42, "threadId": 123}Streaming (drafts)
Section titled “Streaming (drafts)”Telegram can stream draft bubbles while the agent is generating a response. OpenClaw uses Bot API sendMessageDraft (not real messages) and then sends the final reply as a normal message.
Requirements (Telegram Bot API 9.3+):
- Private chats with topics enabled (forum topic mode for the bot).
- Incoming messages must include
message_thread_id(private topic thread). - Streaming is ignored for groups/supergroups/channels.
Config:
channels.telegram.streamMode: "off" | "partial" | "block"(default:partial)partial: update the draft bubble with the latest streaming text.block: update the draft bubble in larger blocks (chunked).off: disable draft streaming.
- Optional (only for
streamMode: "block"):channels.telegram.draftChunk: { minChars?, maxChars?, breakPreference? }- defaults:
minChars: 200,maxChars: 800,breakPreference: "paragraph"(clamped tochannels.telegram.textChunkLimit).
- defaults:
Note: draft streaming is separate from block streaming (channel messages). Block streaming is off by default and requires channels.telegram.blockStreaming: true if you want early Telegram messages instead of draft updates.
Reasoning stream (Telegram only):
/reasoning streamstreams reasoning into the draft bubble while the reply is generating, then sends the final answer without reasoning.- If
channels.telegram.streamModeisoff, reasoning stream is disabled. More context: Streaming + chunking.
Retry policy
Section titled “Retry policy”Outbound Telegram API calls retry on transient network/429 errors with exponential backoff and jitter. Configure via channels.telegram.retry. See Retry policy.
Agent tool (messages + reactions)
Section titled “Agent tool (messages + reactions)”- Tool:
telegramwithsendMessageaction (to,content, optionalmediaUrl,replyToMessageId,messageThreadId). - Tool:
telegramwithreactaction (chatId,messageId,emoji). - Tool:
telegramwithdeleteMessageaction (chatId,messageId).