Production-ready for bot DMs and groups via grammY. Long polling is the default mode; webhook mode is optional.Documentation Index
Fetch the complete documentation index at: https://docs.openclaw.ai/llms.txt
Use this file to discover all available pages before exploring further.
Pairing
Channel troubleshooting
Gateway configuration
Quick setup
Create the bot token in BotFather
@BotFather).Run /newbot, follow prompts, and save the token.Configure token and DM policy
TELEGRAM_BOT_TOKEN=... (default account only).
Telegram does not use openclaw channels login telegram; configure token in config/env, then start gateway.TELEGRAM_BOT_TOKEN only applies to the default account.Telegram side settings
Privacy mode and group visibility
Privacy mode and group visibility
- disable privacy mode via
/setprivacy, or - make the bot a group admin.
Group permissions
Group permissions
Helpful BotFather toggles
Helpful BotFather toggles
/setjoingroupsto allow/deny group adds/setprivacyfor 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(requires at least one sender ID inallowFrom)open(requiresallowFromto include"*")disabled
dmPolicy: "open" with allowFrom: ["*"] lets any Telegram account that finds or guesses the bot username command the bot. Use it only for intentionally public bots with tightly restricted tools; one-owner bots should use allowlist with numeric user IDs.channels.telegram.allowFrom accepts numeric Telegram user IDs. telegram: / tg: prefixes are accepted and normalized.
dmPolicy: "allowlist" with empty allowFrom blocks all DMs and is rejected by config validation.
Setup asks for numeric user IDs only.
If you upgraded and your config contains @username allowlist entries, run openclaw doctor --fix to resolve them (best-effort; requires a Telegram bot token).
If you previously relied on pairing-store allowlist files, openclaw doctor --fix can recover entries into channels.telegram.allowFrom in allowlist flows (for example when dmPolicy: "allowlist" has no explicit IDs yet).For one-owner bots, prefer dmPolicy: "allowlist" with explicit numeric allowFrom IDs to keep access policy durable in config (instead of depending on previous pairing approvals).Common confusion: DM pairing approval does not mean “this sender is authorized everywhere”.
Pairing grants DM access. If no command owner exists yet, the first approved pairing also sets commands.ownerAllowFrom so owner-only commands and exec approvals have an explicit operator account.
Group sender authorization still comes from explicit config allowlists.
If you want “I am authorized once and both DMs and group commands work”, put your numeric Telegram user ID in channels.telegram.allowFrom; for owner-only commands, make sure commands.ownerAllowFrom contains telegram:<your user id>.Finding your Telegram user ID
Safer (no third-party bot):- DM your bot.
- Run
openclaw logs --follow. - Read
from.id.
@userinfobot or @getidsbot.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. - Long polling is guarded inside each gateway process so only one active poller can use a bot token at a time. If you still see
getUpdates409 conflicts, another OpenClaw gateway, script, or external poller is likely using the same token. - Long-polling watchdog restarts trigger after 120 seconds without completed
getUpdatesliveness by default. Increasechannels.telegram.pollingStallThresholdMsonly if your deployment still sees false polling-stall restarts during long-running work. The value is in milliseconds and is allowed from30000to600000; per-account overrides are supported. - Telegram Bot API has no read-receipt support (
sendReadReceiptsdoes not apply).
Feature reference
Live stream preview (message edits)
Live stream preview (message edits)
- direct chats: preview message +
editMessageText - groups/topics: preview message +
editMessageText
channels.telegram.streamingisoff | partial | block | progress(default:partial)progressmaps topartialon Telegram (compat with cross-channel naming)streaming.preview.toolProgresscontrols whether tool/progress updates reuse the same edited preview message (default:truewhen preview streaming is active)- legacy
channels.telegram.streamModeand booleanstreamingvalues are detected; runopenclaw doctor --fixto migrate them tochannels.telegram.streaming.mode
v2026.4.22 and later. To keep the edited preview for answer text but hide tool-progress lines, set:streaming.mode: "off" only when you want final-only delivery: Telegram preview edits are disabled and generic tool/progress chatter is suppressed instead of being sent as standalone “Working…” messages. Approval prompts, media payloads, and errors still route through normal final delivery. Use streaming.preview.toolProgress: false when you only want to keep answer preview edits while hiding the tool-progress status lines.For text-only replies:- short DM/group/topic previews: OpenClaw keeps the same preview message and performs a final edit in place
- previews older than about one minute: OpenClaw sends the completed reply as a fresh final message and then cleans up the preview, so Telegram’s visible timestamp reflects completion time instead of the preview creation time
sendMessage + editMessageText.Telegram-only reasoning stream:/reasoning streamsends reasoning to the live preview while generating- final answer is sent without reasoning text
Formatting and HTML fallback
Formatting and HTML fallback
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.
channels.telegram.linkPreview: false.Native commands and custom commands
Native commands and custom commands
setMyCommands.Native command defaults:commands.native: "auto"enables native commands for Telegram
- names are normalized (strip leading
/, lowercase) - valid pattern:
a-z,0-9,_, length1..32 - custom commands cannot override native commands
- conflicts/duplicates are skipped and logged
- 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
setMyCommands failedwithBOT_COMMANDS_TOO_MUCHmeans the Telegram menu still overflowed after trimming; reduce plugin/skill/custom commands or disablechannels.telegram.commands.native.deleteWebhook,deleteMyCommands, orsetMyCommandsfailing with404: Not Foundwhile direct Bot API curl commands work can meanchannels.telegram.apiRootwas set to the full/bot<TOKEN>endpoint.apiRootmust be only the Bot API root, andopenclaw doctor --fixremoves an accidental trailing/bot<TOKEN>.getMe returned 401means Telegram rejected the configured bot token. UpdatebotToken,tokenFile, orTELEGRAM_BOT_TOKENwith the current BotFather token; OpenClaw stops before polling so this is not reported as a webhook cleanup failure.setMyCommands failedwith network/fetch errors usually means outbound DNS/HTTPS toapi.telegram.orgis blocked.
Device pairing commands (device-pair plugin)
When the device-pair plugin is installed:/pairgenerates setup code- paste code in iOS app
/pair pendinglists pending requests (including role/scopes)- approve the request:
/pair approve <requestId>for explicit approval/pair approvewhen there is only one pending request/pair approve latestfor most recent
scopes: []; any handed-off operator token stays bounded to operator.approvals, operator.read, operator.talk.secrets, and operator.write. Bootstrap scope checks are role-prefixed, so that operator allowlist only satisfies operator requests; non-operator roles still need scopes under their own role prefix.If a device retries with changed auth details (for example role/scopes/public key), the previous pending request is superseded and the new request uses a different requestId. Re-run /pair pending before approving.More details: Pairing.Inline buttons
Inline buttons
Telegram message actions for agents and automation
Telegram message actions for agents and automation
sendMessage(to,content, optionalmediaUrl,replyToMessageId,messageThreadId)react(chatId,messageId,emoji)deleteMessage(chatId,messageId)editMessage(chatId,messageId,content)createForumTopic(chatId,name, optionaliconColor,iconCustomEmojiId)
send, react, delete, edit, sticker, sticker-search, topic-create).Gating controls:channels.telegram.actions.sendMessagechannels.telegram.actions.deleteMessagechannels.telegram.actions.reactionschannels.telegram.actions.sticker(default: disabled)
edit and topic-create are currently enabled by default and do not have separate channels.telegram.actions.* toggles.
Runtime sends use the active config/secrets snapshot (startup/reload), so action paths do not perform ad-hoc SecretRef re-resolution per send.Reaction removal semantics: /tools/reactionsReply threading tags
Reply threading tags
Forum topics and thread behavior
Forum topics and thread behavior
- topic session keys append
:topic:<threadId> - replies and typing target the topic thread
- topic config path:
channels.telegram.groups.<chatId>.topics.<threadId>
threadId=1) special-case:- message sends omit
message_thread_id(Telegram rejectssendMessage(...thread_id=1)) - typing actions still include
message_thread_id
requireMention, allowFrom, skills, systemPrompt, enabled, groupPolicy).
agentId is topic-only and does not inherit from group defaults.Per-topic agent routing: Each topic can route to a different agent by setting agentId in the topic config. This gives each topic its own isolated workspace, memory, and session. Example:agent:zu:telegram:group:-1001234567890:topic:3Persistent ACP topic binding: Forum topics can pin ACP harness sessions through top-level typed ACP bindings (bindings[] with type: "acp" and match.channel: "telegram", peer.kind: "group", and a topic-qualified id like -1001234567890:topic:42). Currently scoped to forum topics in groups/supergroups. See ACP Agents.Thread-bound ACP spawn from chat: /acp spawn <agent> --thread here|auto binds the current topic to a new ACP session; follow-ups route there directly. OpenClaw pins the spawn confirmation in-topic. Requires channels.telegram.threadBindings.spawnAcpSessions=true.Template context exposes MessageThreadId and IsForum. DM chats with message_thread_id keep DM routing but use thread-aware session keys.Audio, video, and stickers
Audio, video, and stickers
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 - inbound voice-note transcripts are framed as machine-generated, untrusted text in the agent context; mention detection still uses the raw transcript so mention-gated voice messages continue to work.
Video messages
Telegram distinguishes video files vs video notes.Message action example:Stickers
Inbound sticker handling:- static WEBP: downloaded and processed (placeholder
<media:sticker>) - animated TGS: skipped
- video WEBM: skipped
Sticker.emojiSticker.setNameSticker.fileIdSticker.fileUniqueIdSticker.cachedDescription
~/.openclaw/telegram/sticker-cache.json
Reaction notifications
Reaction notifications
message_reaction updates (separate from message payloads).When enabled, OpenClaw enqueues system events like:Telegram reaction added: 👍 by Alice (@alice) on msg 42
channels.telegram.reactionNotifications:off | own | all(default:own)channels.telegram.reactionLevel:off | ack | minimal | extensive(default:minimal)
ownmeans user reactions to bot-sent messages only (best-effort via sent-message cache).- Reaction events still respect Telegram access controls (
dmPolicy,allowFrom,groupPolicy,groupAllowFrom); unauthorized senders are dropped. - 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
Ack reactions
ackReaction sends an acknowledgement emoji while OpenClaw is processing an inbound message.Resolution order:channels.telegram.accounts.<accountId>.ackReactionchannels.telegram.ackReactionmessages.ackReaction- agent identity emoji fallback (
agents.list[].identity.emoji, else ”👀”)
- Telegram expects unicode emoji (for example ”👀”).
- Use
""to disable the reaction for a channel or account.
Config writes from Telegram events and commands
Config writes from Telegram events and commands
configWrites !== false).Telegram-triggered writes include:- group migration events (
migrate_to_chat_id) to updatechannels.telegram.groups /config setand/config unset(requires command enablement)
Long polling vs webhook
Long polling vs webhook
channels.telegram.webhookUrl and channels.telegram.webhookSecret; optional webhookPath, webhookHost, webhookPort (defaults /telegram-webhook, 127.0.0.1, 8787).The local listener binds to 127.0.0.1:8787. For public ingress, either put a reverse proxy in front of the local port or set webhookHost: "0.0.0.0" intentionally.Webhook mode validates request guards, the Telegram secret token, and the JSON body before returning 200 to Telegram.
OpenClaw then processes the update asynchronously through the same per-chat/per-topic bot lanes used by long polling, so slow agent turns do not hold Telegram’s delivery ACK.Limits, retry, and CLI targets
Limits, retry, and CLI targets
channels.telegram.textChunkLimitdefault is 4000.channels.telegram.chunkMode="newline"prefers paragraph boundaries (blank lines) before length splitting.channels.telegram.mediaMaxMb(default 100) caps inbound and outbound Telegram media size.channels.telegram.timeoutSecondsoverrides Telegram API client timeout (if unset, grammY default applies).channels.telegram.pollingStallThresholdMsdefaults to120000; tune between30000and600000only for false-positive polling-stall restarts.- group context history uses
channels.telegram.historyLimitormessages.groupChat.historyLimit(default 50);0disables. - reply/quote/forward supplemental context is currently passed as received.
- Telegram allowlists primarily gate who can trigger the agent, not a full supplemental-context redaction boundary.
- DM history controls:
channels.telegram.dmHistoryLimitchannels.telegram.dms["<user_id>"].historyLimit
channels.telegram.retryconfig applies to Telegram send helpers (CLI/tools/actions) for recoverable outbound API errors.
openclaw message poll and support forum topics:--poll-duration-seconds(5-600)--poll-anonymous--poll-public--thread-idfor forum topics (or use a:topic:target)
--presentationwithbuttonsblocks for inline keyboards whenchannels.telegram.capabilities.inlineButtonsallows it--pinor--delivery '{"pin":true}'to request pinned delivery when the bot can pin in that chat--force-documentto send outbound images and GIFs as documents instead of compressed photo or animated-media uploads
channels.telegram.actions.sendMessage=falsedisables outbound Telegram messages, including pollschannels.telegram.actions.poll=falsedisables Telegram poll creation while leaving regular sends enabled
Exec approvals in Telegram
Exec approvals in Telegram
channels.telegram.execApprovals.enabled(auto-enables when at least one approver is resolvable)channels.telegram.execApprovals.approvers(falls back to numeric owner IDs fromcommands.ownerAllowFrom)channels.telegram.execApprovals.target:dm(default) |channel|bothagentFilter,sessionFilter
channels.telegram.allowFrom, groupAllowFrom, and defaultTo control who can talk to the bot and where it sends normal replies. They do not make someone an exec approver. The first approved DM pairing bootstraps commands.ownerAllowFrom when no command owner exists yet, so the one-owner setup still works without duplicating IDs under execApprovals.approvers.Channel delivery shows the command text in the chat; only enable channel or both in trusted groups/topics. When the prompt lands in a forum topic, OpenClaw preserves the topic for the approval prompt and the follow-up. Exec approvals expire after 30 minutes by default.Inline approval buttons also require channels.telegram.capabilities.inlineButtons to allow the target surface (dm, group, or all). Approval IDs prefixed with plugin: resolve through plugin approvals; others resolve through exec approvals first.See Exec approvals.Error reply controls
When the agent encounters a delivery or provider error, Telegram can either reply with the error text or suppress it. Two config keys control this behavior:| Key | Values | Default | Description |
|---|---|---|---|
channels.telegram.errorPolicy | reply, silent | reply | reply sends a friendly error message to the chat. silent suppresses error replies entirely. |
channels.telegram.errorCooldownMs | number (ms) | 60000 | Minimum time between error replies to the same chat. Prevents error spam during outages. |
Troubleshooting
Bot does not respond to non mention group messages
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
- BotFather:
openclaw channels statuswarns when config expects unmentioned group messages.openclaw channels status --probecan check explicit numeric group IDs; wildcard"*"cannot be membership-probed.- quick session test:
/activation always.
Bot not seeing group messages at all
Bot not seeing group messages at all
- when
channels.telegram.groupsexists, group must be listed (or include"*") - verify bot membership in group
- review logs:
openclaw logs --followfor skip reasons
Commands work partially or not at all
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 failedwithBOT_COMMANDS_TOO_MUCHmeans the native menu has too many entries; reduce plugin/skill/custom commands or disable native menussetMyCommands failedwith network/fetch errors usually indicates DNS/HTTPS reachability issues toapi.telegram.org
Startup reports unauthorized token
Startup reports unauthorized token
Polling or network instability
Polling or network instability
- Node 22+ + custom fetch/proxy can trigger immediate abort behavior if AbortSignal types mismatch.
- Some hosts resolve
api.telegram.orgto IPv6 first; broken IPv6 egress can cause intermittent Telegram API failures. - If logs include
TypeError: fetch failedorNetwork request for 'getUpdates' failed!, OpenClaw now retries these as recoverable network errors. - If logs include
Polling stall detected, OpenClaw restarts polling and rebuilds the Telegram transport after 120 seconds without completed long-poll liveness by default. - Increase
channels.telegram.pollingStallThresholdMsonly when long-runninggetUpdatescalls are healthy but your host still reports false polling-stall restarts. Persistent stalls usually point to proxy, DNS, IPv6, or TLS egress issues between the host andapi.telegram.org. - On VPS hosts with unstable direct egress/TLS, route Telegram API calls through
channels.telegram.proxy:
- Node 22+ defaults to
autoSelectFamily=true(except WSL2) anddnsResultOrder=ipv4first. - If your host is WSL2 or explicitly works better with IPv4-only behavior, force family selection:
- RFC 2544 benchmark-range answers (
198.18.0.0/15) are already allowed for Telegram media downloads by default. If a trusted fake-IP or transparent proxy rewritesapi.telegram.orgto some other private/internal/special-use address during media downloads, you can opt in to the Telegram-only bypass:
- The same opt-in is available per account at
channels.telegram.accounts.<accountId>.network.dangerouslyAllowPrivateNetwork. - If your proxy resolves Telegram media hosts into
198.18.x.x, leave the dangerous flag off first. Telegram media already allows the RFC 2544 benchmark range by default.
- Environment overrides (temporary):
OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY=1OPENCLAW_TELEGRAM_ENABLE_AUTO_SELECT_FAMILY=1OPENCLAW_TELEGRAM_DNS_RESULT_ORDER=ipv4first
- Validate DNS answers:
Configuration reference
Primary reference: Configuration reference - Telegram.High-signal Telegram fields
High-signal Telegram fields
- startup/auth:
enabled,botToken,tokenFile,accounts.*(tokenFilemust point to a regular file; symlinks are rejected) - access control:
dmPolicy,allowFrom,groupPolicy,groupAllowFrom,groups,groups.*.topics.*, top-levelbindings[](type: "acp") - exec approvals:
execApprovals,accounts.*.execApprovals - command/menu:
commands.native,commands.nativeSkills,customCommands - threading/replies:
replyToMode - streaming:
streaming(preview),streaming.preview.toolProgress,blockStreaming - formatting/delivery:
textChunkLimit,chunkMode,linkPreview,responsePrefix - media/network:
mediaMaxMb,timeoutSeconds,pollingStallThresholdMs,retry,network.autoSelectFamily,network.dangerouslyAllowPrivateNetwork,proxy - custom API root:
apiRoot(Bot API root only; do not include/bot<TOKEN>) - webhook:
webhookUrl,webhookSecret,webhookPath,webhookHost - actions/capabilities:
capabilities.inlineButtons,actions.sendMessage|editMessage|deleteMessage|reactions|sticker - reactions:
reactionNotifications,reactionLevel - errors:
errorPolicy,errorCooldownMs - writes/history:
configWrites,historyLimit,dmHistoryLimit,dms.*.historyLimit
channels.telegram.defaultAccount (or include channels.telegram.accounts.default) to make default routing explicit. Otherwise OpenClaw falls back to the first normalized account ID and openclaw doctor warns. Named accounts inherit channels.telegram.allowFrom / groupAllowFrom, but not accounts.default.* values.