OpenClawSkills
GitHub
Channels β€’ TutorialHeader.readTime

Telegram

Telegram bot support status, capabilities, and configuration.

Status: Production-ready. Supports bot DM and group chat via grammY. Uses long-polling by default; also supports webhook.

Tutorial.step

Quick Setup for Beginners

1. Create a bot with ''@BotFather'' (''direct link''). Make sure the handle is ''@BotFather'', then copy the bot token.

2. Configure token:

- Environment variable: ''TELEGRAM_BOT_TOKEN=...''

- Or config: ''channels.telegram.botToken: "..."''.

- When both are set, config takes precedence (env only serves as default account fallback).

3. Start Gateway.

4. DM enables pairing by default; first contact receives a pairing code, messages are processed only after approval.

Minimum config:

Json5
{
  channels: {
    telegram: {
      enabled: true,
      botToken: "123:abc",
      dmPolicy: "pairing",
    },
  },
}
Tutorial.step

What It Is

- Telegram Bot API channel managed by Gateway.

- Deterministic routing: replies only go back to Telegram, model doesn't choose channel.

- DM uses agent's main session by default; group chats are isolated as ''agent:<agentId>:telegram:group:<chatId>''.

Tutorial.step

Setup (Quick Path)

#

Tutorial.step

1) Create Bot Token (BotFather)

1. Open Telegram, chat with ''@BotFather'' (''direct link''), confirm handle is ''@BotFather''.

2. Run ''/newbot'', follow prompts (name + username ending with ''bot'').

3. Copy token and keep it safe.

Optional settings:

- ''/setjoingroups'' β€” allow/disallow bot to join groups

- ''/setprivacy'' β€” control whether bot can see all group messages

Optional settings:

- ''/setjoingroups'' β€” allow/disallow bot to join groups

- ''/setprivacy'' β€” control whether bot can see all group messages

#

Tutorial.step

2) Configure Token (env or config)

Example:

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

Environment variable: ''TELEGRAM_BOT_TOKEN=...'' (only affects default account). When both env and config exist, config takes precedence.

Multi-account: Use ''channels.telegram.accounts'' to configure each account's token (optional ''name''). For shared structure, see ''/gateway/configuration''.

3. Start Gateway: Telegram channel starts when token is resolvable (config priority, env fallback).

4. DM defaults to pairing: first contact gives pairing code, messages processed only after approval.

5. Group chat: add bot to group; decide BotFather privacy/admin policy (see below); then use ''channels.telegram.groups'' to control mention gating and allowlist.

Tutorial.step

Telegram Side: Token / Privacy / Permissions

#

Tutorial.step

Token (BotFather)

- ''/newbot'' creates bot and returns token (keep secret).

- If leaked, revoke/reset token in @BotFather and update your config.

#

Tutorial.step

Group Message Visibility (Privacy Mode)

Telegram bot enables Privacy Mode by default, which limits the range of group messages it can receive. If you need bot to see all messages in a group, there are two ways:

- Use ''/setprivacy'' to disable privacy mode, ''or''

- Set bot as group admin (admin bots can receive all messages).

Note: After switching privacy mode, you need to remove bot from group and re-add it for settings to take effect.

#

Tutorial.step

Group Permissions (Admin)

Admin permissions are set in group UI. Admin bots receive all group messages; only do this when you truly need "full visibility".

Tutorial.step

How It Works (Behavior)

- Inbound messages are normalized to generic channel envelope (including reply context and media placeholders).

- Group chat requires mention to reply by default (native @mention or ''agents.list[].groupChat.mentionPatterns'' / ''messages.groupChat.mentionPatterns'' match).

- With multiple agents, you can override per-agent in ''agents.list[].groupChat.mentionPatterns''.

- Replies always go back to the Telegram chat that triggered them.

- long-polling uses grammY runner and processes sequentially by chat; overall concurrency limited by ''agents.defaults.maxConcurrent''.

- Telegram Bot API has no read receipts, so no ''sendReadReceipts''.

Tutorial.step

Draft Streaming

OpenClaw can use ''sendMessageDraft'' to stream partial updates in Telegram DM.

Requirements:

- Enable Threaded Mode (forum topic mode) for bot in @BotFather.

- DM threads only (Telegram includes ''message_thread_id'' in inbound messages).

- ''channels.telegram.streamMode'' is not ''"off"'' (default ''"partial"''; ''"block"'' does chunked draft updates).

Draft streaming only supports DM; Telegram doesn't support this mechanism in groups/channels.

Tutorial.step

Formatting (Telegram HTML)

- Outbound Telegram text uses ''parse_mode: "HTML"'' (subset of tags supported by Telegram).

- Markdown-ish input renders as Telegram-safe HTML (bold/italic/strikethrough/code/links); block-level elements are flattened to text with line breaks/bullets.

- Raw HTML from model is escaped to avoid Telegram parse errors.

- If Telegram rejects HTML payload, OpenClaw retries same message with plain text.

Tutorial.step

Commands (Native + Custom)

OpenClaw registers native commands to Telegram bot menu at startup (like ''/status'', ''/reset'', ''/model'').

You can also add custom commands to menu via config:

Notes:

- Custom commands are just menu entries; OpenClaw won't automatically implement them unless you handle them elsewhere.

- Command names are normalized (remove leading ''/'', lowercase), can only contain ''a-z'', ''0-9'', ''_'' (length 1–32).

- Custom commands can't override native commands; conflicts are ignored and logged.

- If ''commands.native'' is disabled, only custom commands are registered (or menu cleared if no custom commands).

Tutorial.step

Troubleshooting

- ''setMyCommands failed'' in logs usually means HTTPS/DNS outbound to ''api.telegram.org'' is blocked.

- When seeing ''sendMessage'' or ''sendChatAction'' failures, prioritize checking IPv6 routing and DNS.

More: ''/channels/troubleshooting''.

Tutorial.step

Limits

- Outbound text is chunked by ''channels.telegram.textChunkLimit'' (default 4000).

- Optional chunk by blank lines first: ''channels.telegram.chunkMode="newline"'' (paragraph boundaries) then by length.

- Media download/upload limit: ''channels.telegram.mediaMaxMb'' (default 5MB).

- Telegram Bot API request timeout: ''channels.telegram.timeoutSeconds'' (default 500, grammY). Recommend setting smaller to avoid long hangs.

- Group history context: ''channels.telegram.historyLimit'' (or ''channels.telegram.accounts.*.historyLimit''), falls back to ''messages.groupChat.historyLimit''. Set to ''0'' to disable (default 50).

- DM history limit: ''channels.telegram.dmHistoryLimit'' (counted by user turns). Override per user: ''channels.telegram.dms["''"].historyLimit''.

Tutorial.step

Group Chat Trigger Mode

By default, bot only replies in groups when mentioned (''@botname'' or ''agents.list[].groupChat.mentionPatterns'' match). To adjust behavior:

#

Tutorial.step

Via Config (Recommended)

Json5
{
  channels: {
    telegram: {
      groups: {
        "-1001234567890": { requireMention: false }, // This group always responds
      },
    },
  },
}

''Important:'' Once ''channels.telegram.groups'' is set, it becomes ''group allowlist'': only listed groups (or ''"*"'') are accepted.

Forum topics inherit parent group config (allowFrom, requireMention, skills, prompts) by default, unless you write topic-level overrides in ''channels.telegram.groups.''.topics.''''.

Allow all groups and always reply:

Json5
{
  channels: {
    telegram: {
      groups: {
        "*": { requireMention: false },
      },
    },
  },
}

Keep all groups require mention (default behavior):

Json5
{
  channels: {
    telegram: {
      groups: {
        "*": { requireMention: true }, // Or omit groups entirely
      },
    },
  },
}

#

Tutorial.step

Via Commands (Current Session Only)

Send in group:

- ''/activation always'' β€” reply to all messages

- ''/activation mention'' β€” require mention (default)

Note: This command only changes session state. To persist behavior after restart, use config.

#

Tutorial.step

Get Group Chat ID

Forward any message from group to ''@userinfobot'' or ''@getidsbot'' to see chat ID (usually a negative number like ''-1001234567890'').

Privacy note: ''@userinfobot'' is a third-party bot. If you don't want to use third-party, add bot to group, send a message, then use ''openclaw logs --follow'' to read ''chat.id'', or use Bot API's ''getUpdates''.

Tutorial.step

Config Writes

By default, Telegram allows config updates triggered by channel events or ''/config set|unset'' to be written back to config file.

Typical scenarios:

- Group upgraded to supergroup, Telegram emits ''migrate_to_chat_id'' (chat ID changes); OpenClaw can automatically migrate ''channels.telegram.groups''.

- You run ''/config set'' or ''/config unset'' in Telegram chat (requires ''commands.config: true'').

Disable:

Json5
{
  channels: { telegram: { configWrites: false } },
}
Tutorial.step

Topics (Forum Supergroup)

Telegram forum topics carry ''message_thread_id'' with each message. OpenClaw will:

Tutorial.step

Inline Buttons

Telegram supports inline keyboard (callback buttons).

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

Per-account config:

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

Scopes:

- ''off'' β€” disabled

- ''dm'' β€” DM only (group targets blocked)

- ''group'' β€” group only (DM targets blocked)

- ''all'' β€” DM + group

- ''allowlist'' β€” DM + group, but only allow senders permitted by ''allowFrom''/''groupAllowFrom'' (consistent with control commands)

Default: ''allowlist''. Old syntax: ''capabilities: ["inlineButtons"]'' equals ''inlineButtons: "all"''.

#

Tutorial.step

Send Buttons

Use message tool to pass ''buttons'' parameter:

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" }],
  ],
}

After user clicks button, callback data is sent back to agent as message:

''callback_data: value''

#

Tutorial.step

Config Hierarchy

Telegram capabilities can be configured at two levels (above example uses object form; old string array is still supported):

- ''channels.telegram.capabilities'': global default, applied to all Telegram accounts (unless overridden)

- ''channels.telegram.accounts.''.capabilities'': per-account override

Tutorial.step

Access Control (DM + Group)

#

Tutorial.step

DM Access

- Default: ''channels.telegram.dmPolicy = "pairing"''. Unknown senders receive pairing code; processed only after approval (1 hour expiry).

- Approve:

- ''openclaw pairing list telegram''

- ''openclaw pairing approve telegram ''''

- pairing is default token exchange for Telegram DM. See ''Pairing'' for details.

- ''channels.telegram.allowFrom'' recommends using numeric user id, also supports ''@username''. Note it refers to human sender's ID, not bot's username. Wizard resolves ''@username'' to numeric id when possible.

#

Tutorial.step

How to Get Your Telegram User ID

Safer (no third-party bot dependency):

1. Start gateway, send a DM to your bot.

2. Run ''openclaw logs --follow'', find ''from.id''.

Official Bot API (more direct):

1. Send DM to bot.

2. Use token to call ''getUpdates'' and read ''message.from.id'':

''''`bash", "p8": "curl "https://api.telegram.org/bot''/getUpdates"", "p9": "''''`

curl "https://api.telegram.org/bot''/getUpdates"

''''`

Third-party (less privacy):

- DM ''@userinfobot'' or ''@getidsbot''.

#

Tutorial.step

Group Access

Group chat has two independent controls:

''1) Which groups to allow'' (''channels.telegram.groups'' as group allowlist):

- Don't write ''groups'': allow all groups

- Write ''groups'': only allow listed groups or ''"*"''

- Example: "groups": { "-1001234567890": {'}, "*": {'} }' means allow all groups (while writing overrides for specific groups)

''2) Which senders to allow'' (''channels.telegram.groupPolicy'' controls group sender filtering):

- ''"open"'': allow all senders in group

- ''"allowlist"'': only allow senders in ''channels.telegram.groupAllowFrom''

- ''"disabled"'': completely reject group messages

Default is ''groupPolicy: "allowlist"'' (i.e., block by default when ''groupAllowFrom'' not configured)

Most users want: ''groupPolicy: "allowlist"'' + ''groupAllowFrom'' + list allowed groups in ''channels.telegram.groups''.

Tutorial.step

Long-polling vs Webhook

- Default: long-polling (no public URL needed).

- Webhook: set ''channels.telegram.webhookUrl'' and ''channels.telegram.webhookSecret'' (optional ''channels.telegram.webhookPath'').

- Local listen binds ''0.0.0.0:8787'' by default, default path ''POST /telegram-webhook''.

- If your public URL is different, use reverse proxy and point ''channels.telegram.webhookUrl'' to public endpoint.

Tutorial.step

Reply Threading

Telegram supports optional "reply to trigger message" capability (based on tags):

- ''[[reply_to_current]]'' β€” reply to trigger message

- ''[[reply_to:'']]'' β€” reply to specified message id

Control via ''channels.telegram.replyToMode'':

- ''first'' (default), ''all'', ''off''.

Tutorial.step

Audio Messages (Voice Notes vs Audio Files)

Telegram distinguishes voice notes (round bubble) from audio files (with metadata card). For compatibility with old behavior, OpenClaw sends audio files by default.

To force sending voice notes in agent replies, add anywhere in reply:

- ''[[audio_as_voice]]'' β€” send audio as voice note

This tag won't appear in final delivered text; other channels ignore it.

Use message tool to send voice note: set ''asVoice: true'' and provide voice-compatible audio ''media'' URL (can omit ''message''):

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