Skip to content

OpenClaw Telegram Bot: Setup, Config and Features

Status: production-ready for bot DMs + groups via grammY. Long polling is the default mode; webhook mode is optional.

Open Telegram and chat with @BotFather (confirm the handle is exactly @BotFather).

Run /newbot, follow prompts, and save the token.

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

Env fallback: TELEGRAM_BOT_TOKEN=... (default account only).

Terminal window
openclaw gateway
openclaw pairing list telegram
openclaw pairing approve telegram <CODE>

Pairing codes expire after 1 hour.

Add the bot to your group, then set channels.telegram.groups and groupPolicy to match your access model.

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

channels.telegram.dmPolicy controls direct message access:

  • pairing (default)

  • allowlist

  • open (requires allowFrom to include "*")

  • disabled

    channels.telegram.allowFrom accepts numeric IDs and usernames. telegram: / tg: prefixes are accepted and normalized.

Safer (no third-party bot):

  1. DM your bot.
  2. Run openclaw logs --follow.
  3. Read from.id.

Official Bot API method:

Terminal window
curl "https://api.telegram.org/bot<bot_token>/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 "*")

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

    Example: allow any member in one specific group:

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

{
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
  • 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).

OpenClaw can stream partial replies with Telegram draft bubbles (sendMessageDraft).

Requirements:

  • channels.telegram.streamMode is not "off" (default: "partial")

  • private chat

  • inbound update includes message_thread_id

  • bot topics are enabled (getMe().has_topics_enabled)

    Modes:

  • off: no draft streaming

  • partial: frequent draft updates from partial text

  • block: chunked draft updates using channels.telegram.draftChunk

    draftChunk defaults for block mode:

  • minChars: 200

  • maxChars: 800

  • breakPreference: "paragraph"

    maxChars is clamped by channels.telegram.textChunkLimit.

    Draft streaming is DM-only; groups/channels do not use draft bubbles.

    If you want early real Telegram messages instead of draft updates, use block streaming (channels.telegram.blockStreaming: true).

    Telegram-only reasoning stream:

  • /reasoning stream sends reasoning to the draft bubble 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:

{
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)

Section titled “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.

Configure inline keyboard scope:

{
channels: {
telegram: {
capabilities: {
inlineButtons: "allowlist",
},
},
},
}

Per-account override:

{
channels: {
telegram: {
accounts: {
main: {
capabilities: {
inlineButtons: "allowlist",
},
},
},
},
},
}

Scopes:

  • off

  • dm

  • group

  • all

  • allowlist (default)

    Legacy capabilities: ["inlineButtons"] maps to inlineButtons: "all".

    Message action example:

{
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

Section titled “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

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:

  • first (default)

  • all

  • off

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.

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:

{
action: "send",
channel: "telegram",
to: "123456789",
media: "https://example.com/voice.ogg",
asVoice: true,
}

Telegram distinguishes video files vs video notes.

Message action example:

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

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

{
channels: {
telegram: {
actions: {
sticker: true,
},
},
},
}

Send sticker action:

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

Search cached stickers:

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

Config writes from Telegram events and commands

Section titled “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:

{
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)

    Default local listener for webhook mode binds to 0.0.0.0:8787.

    If your public endpoint differs, place a reverse proxy in front and point webhookUrl at the public URL.

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

Terminal window
openclaw message send --channel telegram --target 123456789 --message "hi"
openclaw message send --channel telegram --target @name --message "hi"

Bot does not respond to non mention group messages

Section titled “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.
  • 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 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:
Terminal window
dig +short api.telegram.org A
dig +short api.telegram.org AAAA

More help: Channel troubleshooting.

Primary reference:

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, draftChunk, blockStreaming
  • formatting/delivery: textChunkLimit, chunkMode, linkPreview, responsePrefix
  • media/network: mediaMaxMb, timeoutSeconds, retry, network.autoSelectFamily, proxy
  • webhook: webhookUrl, webhookSecret, webhookPath
  • actions/capabilities: capabilities.inlineButtons, actions.sendMessage|editMessage|deleteMessage|reactions|sticker
  • reactions: reactionNotifications, reactionLevel
  • writes/history: configWrites, historyLimit, dmHistoryLimit, dms.*.historyLimit
OpenClaw

OpenClaw Expert

Still stuck?

If this page didn't answer your case, ask OpenClaw Expert for step-by-step guidance.