# Telegram

Source: https://docs.openclaw.ai/channels/telegram

[Skip to main content](#content-area)OpenClaw home pageEnglishSearch...⌘KSearch...NavigationMessaging platformsTelegramGet startedInstallChannelsAgentsToolsModelsPlatformsGateway & OpsReferenceHelpOverview
Chat Channels
Messaging platforms
WhatsAppTelegramDiscordIRCSlackFeishuGoogle ChatMattermostSignaliMessageMicrosoft TeamsLINEMatrixZaloZalo Personal
Configuration
PairingGroup MessagesGroupsBroadcast GroupsChannel RoutingChannel Location ParsingChannel Troubleshooting
On this page
- [Telegram (Bot API)](#telegram-bot-api)
- [Quick setup](#quick-setup)
- [Telegram side settings](#telegram-side-settings)
- [Access control and activation](#access-control-and-activation)
- [Runtime behavior](#runtime-behavior)
- [Feature reference](#feature-reference)
- [Troubleshooting](#troubleshooting)
- [Telegram config reference pointers](#telegram-config-reference-pointers)
- [Related](#related)

​Telegram (Bot API)
Status: production-ready for bot DMs + groups via grammY. Long polling is the default mode; webhook mode is optional.
## Pairing

Default DM policy for Telegram is pairing.## Channel troubleshooting

Cross-channel diagnostics and repair playbooks.## Gateway configuration

Full channel config patterns and examples.
​Quick setup
1Create the bot token in BotFather

Open Telegram and chat with **@BotFather** (confirm the handle is exactly `@BotFather`).Run `/newbot`, follow prompts, and save the token.2Configure token and DM policy

Copy```
{
  channels: {
    telegram: {
      enabled: true,
      botToken: "123:abc",
      dmPolicy: "pairing",
      groups: { "*": { requireMention: true } },
    },
  },
}

```

Env fallback: `TELEGRAM_BOT_TOKEN=...` (default account only).3Start gateway and approve first DM

Copy```
openclaw gateway
openclaw pairing list telegram
openclaw pairing approve telegram <CODE>

```

Pairing codes expire after 1 hour.4Add the bot to a group

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
Privacy mode and group visibility

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.Group permissions

Admin status is controlled in Telegram group settings.Admin bots receive all group messages, which is useful for always-on group behavior.Helpful BotFather toggles

- `/setjoingroups` to allow/deny group adds

- `/setprivacy` for group visibility behavior

​Access control and activation

 DM policy Group policy and allowlists Mention behavior
`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 IDSafer (no third-party bot):

- DM your bot.

- Run `openclaw logs --follow`.

- Read `from.id`.

Official Bot API method:Copy```
curl "https://api.telegram.org/bot<bot_token>/getUpdates"

```

Third-party method (less private): `@userinfobot` or `@getidsbot`.There are two independent controls:

**Which groups are allowed** (`channels.telegram.groups`)

- no `groups` config: all groups allowed

- `groups` configured: acts as allowlist (explicit IDs or `"*"`)

**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:Copy```
{
  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:Copy```
{
  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:<threadId>` 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
Live stream preview (message edits)

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

Formatting and HTML fallback

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`.Native commands and custom commands

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:Copy```
{
  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:

- `/pair` generates setup code

- paste code in iOS app

- `/pair approve` approves latest pending request

More details: [Pairing](/channels/pairing#pair-via-telegram-recommended-for-ios).Inline buttons

Configure inline keyboard scope:Copy```
{
  channels: {
    telegram: {
      capabilities: {
        inlineButtons: "allowlist",
      },
    },
  },
}

```

Per-account override:Copy```
{
  channels: {
    telegram: {
      accounts: {
        main: {
          capabilities: {
            inlineButtons: "allowlist",
          },
        },
      },
    },
  },
}

```

Scopes:

- `off`

- `dm`

- `group`

- `all`

- `allowlist` (default)

Legacy `capabilities: ["inlineButtons"]` maps to `inlineButtons: "all"`.Message action example:Copy```
{
  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: <value>`Telegram message actions for agents and automation

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)Reply threading tags

Telegram supports explicit reply threading tags in generated output:

- `[[reply_to_current]]` replies to the triggering message

- `[[reply_to:<id>]]` 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 topics and thread behavior

Forum supergroups:

- topic session keys append `:topic:<threadId>`

- replies and typing target the topic thread

- topic config path:
`channels.telegram.groups.<chatId>.topics.<threadId>`

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, video, and stickers

​Audio messagesTelegram 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:Copy```
{
  action: "send",
  channel: "telegram",
  to: "123456789",
  media: "https://example.com/voice.ogg",
  asVoice: true,
}

```

​Video messagesTelegram distinguishes video files vs video notes.Message action example:Copy```
{
  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.​StickersInbound sticker handling:

- static WEBP: downloaded and processed (placeholder `<media:sticker>`)

- 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:Copy```
{
  channels: {
    telegram: {
      actions: {
        sticker: true,
      },
    },
  },
}

```

Send sticker action:Copy```
{
  action: "sticker",
  channel: "telegram",
  to: "123456789",
  fileId: "CAACAgIAAxkBAAI...",
}

```

Search cached stickers:Copy```
{
  action: "sticker-search",
  channel: "telegram",
  query: "cat waving",
  limit: 5,
}

```

Reaction notifications

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.Ack reactions

`ackReaction` sends an acknowledgement emoji while OpenClaw is processing an inbound message.Resolution order:

- `channels.telegram.accounts.<accountId>.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.

Config writes from Telegram events and commands

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:Copy```
{
  channels: {
    telegram: {
      configWrites: false,
    },
  },
}

```

Long polling vs webhook

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.Limits, retry, and CLI targets

- `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["<user_id>"].historyLimit`

- outbound Telegram API retries are configurable via `channels.telegram.retry`.

CLI send target can be numeric chat ID or username:Copy```
openclaw message send --channel telegram --target 123456789 --message "hi"
openclaw message send --channel telegram --target @name --message "hi"

```

​Troubleshooting
Bot does not respond to non mention group messages

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`.

Bot not seeing group messages at all

- when `channels.telegram.groups` exists, group must be listed (or include `"*"`)

- verify bot membership in group

- review logs: `openclaw logs --follow` for skip reasons

Commands work partially or not at all

- 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`

Polling or network instability

- 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:

Copy```
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.<id>.groupPolicy`: per-group override for groupPolicy (`open | allowlist | disabled`).

- `channels.telegram.groups.<id>.requireMention`: mention gating default.

- `channels.telegram.groups.<id>.skills`: skill filter (omit = all skills, empty = none).

- `channels.telegram.groups.<id>.allowFrom`: per-group sender allowlist override.

- `channels.telegram.groups.<id>.systemPrompt`: extra system prompt for the group.

- `channels.telegram.groups.<id>.enabled`: disable the group when `false`.

- `channels.telegram.groups.<id>.topics.<threadId>.*`: per-topic overrides (same fields as group).

- `channels.telegram.groups.<id>.topics.<threadId>.groupPolicy`: per-topic override for groupPolicy (`open | allowlist | disabled`).

- `channels.telegram.groups.<id>.topics.<threadId>.requireMention`: per-topic mention gating override.

`channels.telegram.capabilities.inlineButtons`: `off | dm | group | all | allowlist` (default: allowlist).

`channels.telegram.accounts.<account>.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)

- [Troubleshooting](/channels/troubleshooting)

WhatsAppDiscord⌘I