--- summary: "Telegram bot support status, capabilities, and configuration" read_when: - Working on Telegram features or webhooks title: "Telegram" --- # Telegram (Bot API) Status: production-ready for bot DMs + groups via grammY. Long polling is the default mode; webhook mode is optional. Default DM policy for Telegram is pairing. Cross-channel diagnostics and repair playbooks. Full channel config patterns and examples. ## Quick setup Open Telegram and chat with **@BotFather** (confirm the handle is exactly `@BotFather`). Run `/newbot`, follow prompts, and save the token. ```json5 { channels: { telegram: { enabled: true, botToken: "123:abc", dmPolicy: "pairing", groups: { "*": { requireMention: true } }, }, }, } ``` Env fallback: `TELEGRAM_BOT_TOKEN=...` (default account only). ```bash openclaw gateway openclaw pairing list telegram openclaw pairing approve telegram ``` Pairing codes expire after 1 hour. Add the bot to your group, then set `channels.telegram.groups` and `groupPolicy` to match your access model. Token resolution order is account-aware. In practice, config values win over env fallback, and `TELEGRAM_BOT_TOKEN` only applies to the default account. ## Telegram side settings Telegram bots default to **Privacy Mode**, which limits what group messages they receive. If the bot must see all group messages, either: - disable privacy mode via `/setprivacy`, or - make the bot a group admin. When toggling privacy mode, remove + re-add the bot in each group so Telegram applies the change. Admin status is controlled in Telegram group settings. Admin bots receive all group messages, which is useful for always-on group behavior. - `/setjoingroups` to allow/deny group adds - `/setprivacy` for group visibility behavior ## Access control and activation `channels.telegram.dmPolicy` controls direct message access: - `pairing` (default) - `allowlist` - `open` (requires `allowFrom` to include `"*"`) - `disabled` `channels.telegram.allowFrom` accepts numeric Telegram user IDs. `telegram:` / `tg:` prefixes are accepted and normalized. The onboarding wizard accepts `@username` input and resolves it to numeric IDs. If you upgraded and your config contains `@username` allowlist entries, run `openclaw doctor --fix` to resolve them (best-effort; requires a Telegram bot token). ### Finding your Telegram user ID Safer (no third-party bot): 1. DM your bot. 2. Run `openclaw logs --follow`. 3. Read `from.id`. Official Bot API method: ```bash curl "https://api.telegram.org/bot/getUpdates" ``` Third-party method (less private): `@userinfobot` or `@getidsbot`. There are two independent controls: 1. **Which groups are allowed** (`channels.telegram.groups`) - no `groups` config: all groups allowed - `groups` configured: acts as allowlist (explicit IDs or `"*"`) 2. **Which senders are allowed in groups** (`channels.telegram.groupPolicy`) - `open` - `allowlist` (default) - `disabled` `groupAllowFrom` is used for group sender filtering. If not set, Telegram falls back to `allowFrom`. `groupAllowFrom` entries must be numeric Telegram user IDs. Example: allow any member in one specific group: ```json5 { channels: { telegram: { groups: { "-1001234567890": { groupPolicy: "open", requireMention: false, }, }, }, }, } ``` Group replies require mention by default. Mention can come from: - native `@botusername` mention, or - mention patterns in: - `agents.list[].groupChat.mentionPatterns` - `messages.groupChat.mentionPatterns` Session-level command toggles: - `/activation always` - `/activation mention` These update session state only. Use config for persistence. Persistent config example: ```json5 { channels: { telegram: { groups: { "*": { requireMention: false }, }, }, }, } ``` Getting the group chat ID: - forward a group message to `@userinfobot` / `@getidsbot` - or read `chat.id` from `openclaw logs --follow` - or inspect Bot API `getUpdates` ## Runtime behavior - Telegram is owned by the gateway process. - Routing is deterministic: Telegram inbound replies back to Telegram (the model does not pick channels). - Inbound messages normalize into the shared channel envelope with reply metadata and media placeholders. - Group sessions are isolated by group ID. Forum topics append `:topic:` to keep topics isolated. - DM messages can carry `message_thread_id`; OpenClaw routes them with thread-aware session keys and preserves thread ID for replies. - Long polling uses grammY runner with per-chat/per-thread sequencing. Overall runner sink concurrency uses `agents.defaults.maxConcurrent`. - Telegram Bot API has no read-receipt support (`sendReadReceipts` does not apply). ## Feature reference OpenClaw can stream partial replies by sending a temporary Telegram message and editing it as text arrives. Requirement: - `channels.telegram.streamMode` is not `"off"` (default: `"partial"`) Modes: - `off`: no live preview - `partial`: frequent preview updates from partial text - `block`: chunked preview updates using `channels.telegram.draftChunk` `draftChunk` defaults for `streamMode: "block"`: - `minChars: 200` - `maxChars: 800` - `breakPreference: "paragraph"` `maxChars` is clamped by `channels.telegram.textChunkLimit`. This works in direct chats and groups/topics. For text-only replies, OpenClaw keeps the same preview message and performs a final edit in place (no second message). For complex replies (for example media payloads), OpenClaw falls back to normal final delivery and then cleans up the preview message. `streamMode` is separate from block streaming. When block streaming is explicitly enabled for Telegram, OpenClaw skips the preview stream to avoid double-streaming. Telegram-only reasoning stream: - `/reasoning stream` sends reasoning to the live preview while generating - final answer is sent without reasoning text Outbound text uses Telegram `parse_mode: "HTML"`. - Markdown-ish text is rendered to Telegram-safe HTML. - Raw model HTML is escaped to reduce Telegram parse failures. - If Telegram rejects parsed HTML, OpenClaw retries as plain text. Link previews are enabled by default and can be disabled with `channels.telegram.linkPreview: false`. Telegram command menu registration is handled at startup with `setMyCommands`. Native command defaults: - `commands.native: "auto"` enables native commands for Telegram Add custom command menu entries: ```json5 { channels: { telegram: { customCommands: [ { command: "backup", description: "Git backup" }, { command: "generate", description: "Create an image" }, ], }, }, } ``` Rules: - names are normalized (strip leading `/`, lowercase) - valid pattern: `a-z`, `0-9`, `_`, length `1..32` - custom commands cannot override native commands - conflicts/duplicates are skipped and logged Notes: - custom commands are menu entries only; they do not auto-implement behavior - plugin/skill commands can still work when typed even if not shown in Telegram menu If native commands are disabled, built-ins are removed. Custom/plugin commands may still register if configured. Common setup failure: - `setMyCommands failed` usually means outbound DNS/HTTPS to `api.telegram.org` is blocked. ### Device pairing commands (`device-pair` plugin) When the `device-pair` plugin is installed: 1. `/pair` generates setup code 2. paste code in iOS app 3. `/pair approve` approves latest pending request More details: [Pairing](/channels/pairing#pair-via-telegram-recommended-for-ios). Configure inline keyboard scope: ```json5 { channels: { telegram: { capabilities: { inlineButtons: "allowlist", }, }, }, } ``` Per-account override: ```json5 { channels: { telegram: { accounts: { main: { capabilities: { inlineButtons: "allowlist", }, }, }, }, }, } ``` Scopes: - `off` - `dm` - `group` - `all` - `allowlist` (default) Legacy `capabilities: ["inlineButtons"]` maps to `inlineButtons: "all"`. Message action example: ```json5 { 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" }], ], } ``` Callback clicks are passed to the agent as text: `callback_data: ` Telegram tool actions include: - `sendMessage` (`to`, `content`, optional `mediaUrl`, `replyToMessageId`, `messageThreadId`) - `react` (`chatId`, `messageId`, `emoji`) - `deleteMessage` (`chatId`, `messageId`) - `editMessage` (`chatId`, `messageId`, `content`) Channel message actions expose ergonomic aliases (`send`, `react`, `delete`, `edit`, `sticker`, `sticker-search`). Gating controls: - `channels.telegram.actions.sendMessage` - `channels.telegram.actions.editMessage` - `channels.telegram.actions.deleteMessage` - `channels.telegram.actions.reactions` - `channels.telegram.actions.sticker` (default: disabled) Reaction removal semantics: [/tools/reactions](/tools/reactions) Telegram supports explicit reply threading tags in generated output: - `[[reply_to_current]]` replies to the triggering message - `[[reply_to:]]` replies to a specific Telegram message ID `channels.telegram.replyToMode` controls handling: - `off` (default) - `first` - `all` Note: `off` disables implicit reply threading. Explicit `[[reply_to_*]]` tags are still honored. Forum supergroups: - topic session keys append `:topic:` - replies and typing target the topic thread - topic config path: `channels.telegram.groups..topics.` General topic (`threadId=1`) special-case: - message sends omit `message_thread_id` (Telegram rejects `sendMessage(...thread_id=1)`) - typing actions still include `message_thread_id` Topic inheritance: topic entries inherit group settings unless overridden (`requireMention`, `allowFrom`, `skills`, `systemPrompt`, `enabled`, `groupPolicy`). Template context includes: - `MessageThreadId` - `IsForum` DM thread behavior: - private chats with `message_thread_id` keep DM routing but use thread-aware session keys/reply targets. ### Audio messages Telegram distinguishes voice notes vs audio files. - default: audio file behavior - tag `[[audio_as_voice]]` in agent reply to force voice-note send Message action example: ```json5 { action: "send", channel: "telegram", to: "123456789", media: "https://example.com/voice.ogg", asVoice: true, } ``` ### Video messages Telegram distinguishes video files vs video notes. Message action example: ```json5 { action: "send", channel: "telegram", to: "123456789", media: "https://example.com/video.mp4", asVideoNote: true, } ``` Video notes do not support captions; provided message text is sent separately. ### Stickers Inbound sticker handling: - static WEBP: downloaded and processed (placeholder ``) - animated TGS: skipped - video WEBM: skipped Sticker context fields: - `Sticker.emoji` - `Sticker.setName` - `Sticker.fileId` - `Sticker.fileUniqueId` - `Sticker.cachedDescription` Sticker cache file: - `~/.openclaw/telegram/sticker-cache.json` Stickers are described once (when possible) and cached to reduce repeated vision calls. Enable sticker actions: ```json5 { channels: { telegram: { actions: { sticker: true, }, }, }, } ``` Send sticker action: ```json5 { action: "sticker", channel: "telegram", to: "123456789", fileId: "CAACAgIAAxkBAAI...", } ``` Search cached stickers: ```json5 { action: "sticker-search", channel: "telegram", query: "cat waving", limit: 5, } ``` Telegram reactions arrive as `message_reaction` updates (separate from message payloads). When enabled, OpenClaw enqueues system events like: - `Telegram reaction added: 👍 by Alice (@alice) on msg 42` Config: - `channels.telegram.reactionNotifications`: `off | own | all` (default: `own`) - `channels.telegram.reactionLevel`: `off | ack | minimal | extensive` (default: `minimal`) Notes: - `own` means user reactions to bot-sent messages only (best-effort via sent-message cache). - Telegram does not provide thread IDs in reaction updates. - non-forum groups route to group chat session - forum groups route to the group general-topic session (`:topic:1`), not the exact originating topic `allowed_updates` for polling/webhook include `message_reaction` automatically. `ackReaction` sends an acknowledgement emoji while OpenClaw is processing an inbound message. Resolution order: - `channels.telegram.accounts..ackReaction` - `channels.telegram.ackReaction` - `messages.ackReaction` - agent identity emoji fallback (`agents.list[].identity.emoji`, else "👀") Notes: - Telegram expects unicode emoji (for example "👀"). - Use `""` to disable the reaction for a channel or account. Channel config writes are enabled by default (`configWrites !== false`). Telegram-triggered writes include: - group migration events (`migrate_to_chat_id`) to update `channels.telegram.groups` - `/config set` and `/config unset` (requires command enablement) Disable: ```json5 { channels: { telegram: { configWrites: false, }, }, } ``` Default: long polling. Webhook mode: - set `channels.telegram.webhookUrl` - set `channels.telegram.webhookSecret` (required when webhook URL is set) - optional `channels.telegram.webhookPath` (default `/telegram-webhook`) - optional `channels.telegram.webhookHost` (default `127.0.0.1`) Default local listener for webhook mode binds to `127.0.0.1:8787`. If your public endpoint differs, place a reverse proxy in front and point `webhookUrl` at the public URL. Set `webhookHost` (for example `0.0.0.0`) when you intentionally need external ingress. - `channels.telegram.textChunkLimit` default is 4000. - `channels.telegram.chunkMode="newline"` prefers paragraph boundaries (blank lines) before length splitting. - `channels.telegram.mediaMaxMb` (default 5) caps inbound Telegram media download/processing size. - `channels.telegram.timeoutSeconds` overrides Telegram API client timeout (if unset, grammY default applies). - group context history uses `channels.telegram.historyLimit` or `messages.groupChat.historyLimit` (default 50); `0` disables. - DM history controls: - `channels.telegram.dmHistoryLimit` - `channels.telegram.dms[""].historyLimit` - outbound Telegram API retries are configurable via `channels.telegram.retry`. CLI send target can be numeric chat ID or username: ```bash openclaw message send --channel telegram --target 123456789 --message "hi" openclaw message send --channel telegram --target @name --message "hi" ``` ## Troubleshooting - If `requireMention=false`, Telegram privacy mode must allow full visibility. - BotFather: `/setprivacy` -> Disable - then remove + re-add bot to group - `openclaw channels status` warns when config expects unmentioned group messages. - `openclaw channels status --probe` can check explicit numeric group IDs; wildcard `"*"` cannot be membership-probed. - quick session test: `/activation always`. - when `channels.telegram.groups` exists, group must be listed (or include `"*"`) - verify bot membership in group - review logs: `openclaw logs --follow` for skip reasons - authorize your sender identity (pairing and/or numeric `allowFrom`) - command authorization still applies even when group policy is `open` - `setMyCommands failed` usually indicates DNS/HTTPS reachability issues to `api.telegram.org` - Node 22+ + custom fetch/proxy can trigger immediate abort behavior if AbortSignal types mismatch. - Some hosts resolve `api.telegram.org` to IPv6 first; broken IPv6 egress can cause intermittent Telegram API failures. - Validate DNS answers: ```bash dig +short api.telegram.org A dig +short api.telegram.org AAAA ``` More help: [Channel troubleshooting](/channels/troubleshooting). ## Telegram config reference pointers Primary reference: - `channels.telegram.enabled`: enable/disable channel startup. - `channels.telegram.botToken`: bot token (BotFather). - `channels.telegram.tokenFile`: read token from file path. - `channels.telegram.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing). - `channels.telegram.allowFrom`: DM allowlist (numeric Telegram user IDs). `open` requires `"*"`. `openclaw doctor --fix` can resolve legacy `@username` entries to IDs. - `channels.telegram.groupPolicy`: `open | allowlist | disabled` (default: allowlist). - `channels.telegram.groupAllowFrom`: group sender allowlist (numeric Telegram user IDs). `openclaw doctor --fix` can resolve legacy `@username` entries to IDs. - `channels.telegram.groups`: per-group defaults + allowlist (use `"*"` for global defaults). - `channels.telegram.groups..groupPolicy`: per-group override for groupPolicy (`open | allowlist | disabled`). - `channels.telegram.groups..requireMention`: mention gating default. - `channels.telegram.groups..skills`: skill filter (omit = all skills, empty = none). - `channels.telegram.groups..allowFrom`: per-group sender allowlist override. - `channels.telegram.groups..systemPrompt`: extra system prompt for the group. - `channels.telegram.groups..enabled`: disable the group when `false`. - `channels.telegram.groups..topics..*`: per-topic overrides (same fields as group). - `channels.telegram.groups..topics..groupPolicy`: per-topic override for groupPolicy (`open | allowlist | disabled`). - `channels.telegram.groups..topics..requireMention`: per-topic mention gating override. - `channels.telegram.capabilities.inlineButtons`: `off | dm | group | all | allowlist` (default: allowlist). - `channels.telegram.accounts..capabilities.inlineButtons`: per-account override. - `channels.telegram.replyToMode`: `off | first | all` (default: `off`). - `channels.telegram.textChunkLimit`: outbound chunk size (chars). - `channels.telegram.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking. - `channels.telegram.linkPreview`: toggle link previews for outbound messages (default: true). - `channels.telegram.streamMode`: `off | partial | block` (live stream preview). - `channels.telegram.mediaMaxMb`: inbound/outbound media cap (MB). - `channels.telegram.retry`: retry policy for outbound Telegram API calls (attempts, minDelayMs, maxDelayMs, jitter). - `channels.telegram.network.autoSelectFamily`: override Node autoSelectFamily (true=enable, false=disable). Defaults to disabled on Node 22 to avoid Happy Eyeballs timeouts. - `channels.telegram.proxy`: proxy URL for Bot API calls (SOCKS/HTTP). - `channels.telegram.webhookUrl`: enable webhook mode (requires `channels.telegram.webhookSecret`). - `channels.telegram.webhookSecret`: webhook secret (required when webhookUrl is set). - `channels.telegram.webhookPath`: local webhook path (default `/telegram-webhook`). - `channels.telegram.webhookHost`: local webhook bind host (default `127.0.0.1`). - `channels.telegram.actions.reactions`: gate Telegram tool reactions. - `channels.telegram.actions.sendMessage`: gate Telegram tool message sends. - `channels.telegram.actions.deleteMessage`: gate Telegram tool message deletes. - `channels.telegram.actions.sticker`: gate Telegram sticker actions — send and search (default: false). - `channels.telegram.reactionNotifications`: `off | own | all` — control which reactions trigger system events (default: `own` when not set). - `channels.telegram.reactionLevel`: `off | ack | minimal | extensive` — control agent's reaction capability (default: `minimal` when not set). - [Configuration reference - Telegram](/gateway/configuration-reference#telegram) Telegram-specific high-signal fields: - startup/auth: `enabled`, `botToken`, `tokenFile`, `accounts.*` - access control: `dmPolicy`, `allowFrom`, `groupPolicy`, `groupAllowFrom`, `groups`, `groups.*.topics.*` - command/menu: `commands.native`, `customCommands` - threading/replies: `replyToMode` - streaming: `streamMode` (preview), `draftChunk`, `blockStreaming` - formatting/delivery: `textChunkLimit`, `chunkMode`, `linkPreview`, `responsePrefix` - media/network: `mediaMaxMb`, `timeoutSeconds`, `retry`, `network.autoSelectFamily`, `proxy` - webhook: `webhookUrl`, `webhookSecret`, `webhookPath`, `webhookHost` - actions/capabilities: `capabilities.inlineButtons`, `actions.sendMessage|editMessage|deleteMessage|reactions|sticker` - reactions: `reactionNotifications`, `reactionLevel` - writes/history: `configWrites`, `historyLimit`, `dmHistoryLimit`, `dms.*.historyLimit` ## Related - [Pairing](/channels/pairing) - [Channel routing](/channels/channel-routing) - [Multi-agent routing](/concepts/multi-agent) - [Troubleshooting](/channels/troubleshooting)