--- summary: "Telegram bot support status, capabilities, and configuration" read_when: - Working on Telegram features or webhooks --- # Telegram (Bot API) Status: production-ready for bot DMs + groups via grammY. Long-polling by default; webhook optional. ## Quick setup (beginner) 1) Create a bot with **@BotFather** and copy the token. 2) Set the token: - Env: `TELEGRAM_BOT_TOKEN=...` - Or config: `telegram.botToken: "..."`. 3) Start the gateway. 4) DM access is pairing by default; approve the pairing code on first contact. Minimal config: ```json5 { telegram: { enabled: true, botToken: "123:abc", dmPolicy: "pairing" } } ``` ## What it is - A Telegram Bot API provider owned by the Gateway. - Deterministic routing: replies go back to Telegram; the model never chooses providers. - DMs share the agent's main session; groups stay isolated (`agent::telegram:group:`). ## Setup (fast path) ### 1) Create a bot token (BotFather) 1) Open Telegram and chat with **@BotFather**. 2) Run `/newbot`, then follow the prompts (name + username ending in `bot`). 3) 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) Example: ```json5 { telegram: { enabled: true, botToken: "123:abc", dmPolicy: "pairing", groups: { "*": { requireMention: true } } } } ``` Env option: `TELEGRAM_BOT_TOKEN=...` (works for the default account). Multi-account support: use `telegram.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern. 3) Start the gateway. Telegram starts when a token is resolved (env or config). 4) DM access defaults to pairing. Approve the code when the bot is first contacted. 5) For groups: add the bot, decide privacy/admin behavior (below), then set `telegram.groups` to control mention gating + allowlists. ## Token + privacy + permissions (Telegram side) ### Token creation (BotFather) - `/newbot` creates 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) 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 `/setprivacy` **or** - 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) 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) - Inbound messages are normalized into the shared provider 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`. ## 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, Clawdbot retries the same message as plain text. ## Limits - Outbound text is chunked to `telegram.textChunkLimit` (default 4000). - Media downloads/uploads are capped by `telegram.mediaMaxMb` (default 5). - Group history context uses `telegram.historyLimit` (or `telegram.accounts.*.historyLimit`), falling back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50). ## 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) ```json5 { telegram: { groups: { "-1001234567890": { requireMention: false } // always respond in this group } } } ``` **Important:** Setting `telegram.groups` creates an **allowlist** - only listed groups (or `"*"`) will be accepted. To allow all groups with always-respond: ```json5 { telegram: { groups: { "*": { requireMention: false } // all groups, always respond } } } ``` To keep mention-only for all groups (default behavior): ```json5 { telegram: { groups: { "*": { requireMention: true } // or omit groups entirely } } } ``` ### 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 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, use gateway logs (`clawdbot logs`) or Telegram developer tools to find user/chat IDs. ## Topics (forum supergroups) Telegram forum topics include a `message_thread_id` per message. Clawdbot: - Appends `:topic:` to the Telegram group session key so each topic is isolated. - Sends typing indicators and replies with `message_thread_id` so responses stay in the topic. - Exposes `MessageThreadId` + `IsForum` in template context for routing/templating. - Topic-specific configuration is available under `telegram.groups..topics.` (skills, allowlists, auto-reply, system prompts, disable). Private chats can include `message_thread_id` in some edge cases. Clawdbot keeps the DM session key unchanged, but still uses the thread id for replies/draft streaming when it is present. ## Access control (DMs + groups) ### DM access - Default: `telegram.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour). - Approve via: - `clawdbot pairing list telegram` - `clawdbot pairing approve telegram ` - Pairing is the default token exchange used for Telegram DMs. Details: [Pairing](/start/pairing) - `telegram.allowFrom` accepts numeric user IDs (recommended) or `@username` entries. ### Group access Two independent controls: **1. Which groups are allowed** (group allowlist via `telegram.groups`): - No `groups` config = all groups allowed - With `groups` config = only listed groups or `"*"` are allowed - Example: `"groups": { "-1001234567890": {}, "*": {} }` allows all groups **2. Which senders are allowed** (sender filtering via `telegram.groupPolicy`): - `"open"` (default) = all senders in allowed groups can message - `"allowlist"` = only senders in `telegram.groupAllowFrom` can message - `"disabled"` = no group messages accepted at all Most users want: `groupPolicy: "open"` + specific groups listed in `telegram.groups` ## Long-polling vs webhook - Default: long-polling (no public URL required). - Webhook mode: set `telegram.webhookUrl` (optionally `telegram.webhookSecret` + `telegram.webhookPath`). - The local listener binds to `0.0.0.0:8787` and serves `POST /telegram-webhook` by default. - If your public URL is different, use a reverse proxy and point `telegram.webhookUrl` at the public endpoint. ## Reply threading Telegram supports optional threaded replies via tags: - `[[reply_to_current]]` -- reply to the triggering message. - `[[reply_to:]]` -- reply to a specific message id. Controlled by `telegram.replyToMode`: - `first` (default), `all`, `off`. ## Audio messages (voice vs file) Telegram distinguishes **voice notes** (round bubble) from **audio files** (metadata card). Clawdbot 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 providers ignore this tag. ## Streaming (drafts) Telegram can stream **draft bubbles** while the agent is generating a response. Clawdbot 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: - `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"`): - `telegram.draftChunk: { minChars?, maxChars?, breakPreference? }` - defaults: `minChars: 200`, `maxChars: 800`, `breakPreference: "paragraph"` (clamped to `telegram.textChunkLimit`). Note: draft streaming is separate from **block streaming** (provider messages). Block streaming is off by default and requires `telegram.blockStreaming: true` if you want early Telegram messages instead of draft updates. Reasoning stream (Telegram only): - `/reasoning stream` streams reasoning into the draft bubble while the reply is generating, then sends the final answer without reasoning. - If `telegram.streamMode` is `off`, reasoning stream is disabled. More context: [Streaming + chunking](/concepts/streaming). ## Retry policy Outbound Telegram API calls retry on transient network/429 errors with exponential backoff and jitter. Configure via `telegram.retry`. See [Retry policy](/concepts/retry). ## Agent tool (messages + reactions) - Tool: `telegram` with `sendMessage` action (`to`, `content`, optional `mediaUrl`, `replyToMessageId`, `messageThreadId`). - Tool: `telegram` with `react` action (`chatId`, `messageId`, `emoji`). - Reaction removal semantics: see [/tools/reactions](/tools/reactions). - Tool gating: `telegram.actions.reactions` and `telegram.actions.sendMessage` (default: enabled). ## Delivery targets (CLI/cron) - Use a chat id (`123456789`) or a username (`@name`) as the target. - Example: `clawdbot message send --provider telegram --to 123456789 --message "hi"`. ## Troubleshooting **Bot doesn’t respond to non-mention messages in a group:** - If you set `telegram.groups.*.requireMention=false`, Telegram’s Bot API **privacy mode** must be disabled. - BotFather: `/setprivacy` → **Disable** (then remove + re-add the bot to the group) - `clawdbot providers status` shows a warning when config expects unmentioned group messages. - `clawdbot providers status --probe` can additionally check membership for explicit numeric group IDs (it can’t audit wildcard `"*"` rules). - Quick test: `/activation always` (session-only; use config for persistence) **Bot not seeing group messages at all:** - If `telegram.groups` is set, the group must be listed or use `"*"` - Check Privacy Settings in @BotFather → "Group Privacy" should be **OFF** - Verify bot is actually a member (not just an admin with no read access) - Check gateway logs: `clawdbot logs --follow` (look for "skipping group message") **Bot responds to mentions but not `/activation always`:** - The `/activation` command updates session state but doesn't persist to config - For persistent behavior, add group to `telegram.groups` with `requireMention: false` **Commands like `/status` don't work:** - Make sure your Telegram user ID is authorized (via pairing or `telegram.allowFrom`) - Commands require authorization even in groups with `groupPolicy: "open"` ## Configuration reference (Telegram) Full configuration: [Configuration](/gateway/configuration) Provider options: - `telegram.enabled`: enable/disable provider startup. - `telegram.botToken`: bot token (BotFather). - `telegram.tokenFile`: read token from file path. - `telegram.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing). - `telegram.allowFrom`: DM allowlist (ids/usernames). `open` requires `"*"`. - `telegram.groupPolicy`: `open | allowlist | disabled` (default: open). - `telegram.groupAllowFrom`: group sender allowlist (ids/usernames). - `telegram.groups`: per-group defaults + allowlist (use `"*"` for global defaults). - `telegram.groups..requireMention`: mention gating default. - `telegram.groups..skills`: skill filter (omit = all skills, empty = none). - `telegram.groups..allowFrom`: per-group sender allowlist override. - `telegram.groups..systemPrompt`: extra system prompt for the group. - `telegram.groups..enabled`: disable the group when `false`. - `telegram.groups..topics..*`: per-topic overrides (same fields as group). - `telegram.groups..topics..requireMention`: per-topic mention gating override. - `telegram.replyToMode`: `off | first | all` (default: `first`). - `telegram.textChunkLimit`: outbound chunk size (chars). - `telegram.streamMode`: `off | partial | block` (draft streaming). - `telegram.mediaMaxMb`: inbound/outbound media cap (MB). - `telegram.retry`: retry policy for outbound Telegram API calls (attempts, minDelayMs, maxDelayMs, jitter). - `telegram.proxy`: proxy URL for Bot API calls (SOCKS/HTTP). - `telegram.webhookUrl`: enable webhook mode. - `telegram.webhookSecret`: webhook secret (optional). - `telegram.webhookPath`: local webhook path (default `/telegram-webhook`). - `telegram.actions.reactions`: gate Telegram tool reactions. - `telegram.actions.sendMessage`: gate Telegram tool message sends. Related global options: - `agents.list[].groupChat.mentionPatterns` (mention gating patterns). - `messages.groupChat.mentionPatterns` (global fallback). - `commands.native`, `commands.text`, `commands.useAccessGroups` (command behavior). - `messages.responsePrefix`, `messages.ackReaction`, `messages.ackReactionScope`, `messages.removeAckAfterReply`.