All systems operational Your IP: 216.73.217.113 info@cloudhosting.lv +371 66 66 29 69 Client area

← All questions

OpenClaw Telegram Bot Setup: Token, allowFrom and Errors

Updated:

To connect OpenClaw to Telegram you create a bot in @BotFather, put its token into ~/.openclaw/.env, add a channels.telegram block to ~/.openclaw/openclaw.json, and lock the bot to your own numeric Telegram user id with allowFrom. The gateway uses long polling by default, so the server needs no public port and no domain. Most failures come down to three things: a mistyped token (401 from getMe), a wrong id in allowFrom (a pairing code or silence instead of an answer), or Telegram privacy mode hiding group messages.

Every key and command below is traced to the official OpenClaw docs (2026.9.x) and Telegram's Bot API pages; where they are silent, we say so.

What you need before you start

  • A running OpenClaw gateway: the openclaw CLI on a plain host, or the ghcr.io/openclaw/openclaw Docker image with the host's ~/.openclaw mounted at /home/node/.openclaw. Not installed yet? See our install guide.
  • An LLM provider key already configured (OpenAI, Anthropic, an OpenAI-compatible gateway, or Ollama).
  • A Telegram account. The docs say to start with Telegram: it "needs a bot token and no plugin install".

Step 1: Create the bot in BotFather

Open Telegram, start a chat with @BotFather and send /newbot. It asks for a display name and a username ending in bot, then answers with a token like 4839574812:AAFD39kkdpWt3ywyRZergyOLMaJhac60qc. Telegram's tutorial is blunt: treat it like a password and share it with nobody.

Leave /setprivacy and /setjoingroups at their defaults for a private one-owner bot; privacy mode comes back below.

Step 2: Store the token in .env, not in the config file

OpenClaw reads environment variables from the parent process and from ~/.openclaw/.env as a global fallback that never overrides a variable already set in the shell. Put the token there:

TELEGRAM_BOT_TOKEN=4839574812:AAFD39kkdpWt3ywyRZergyOLMaJhac60qc

Both documented ways to hand it to the channel work:

  1. Env fallback. If channels.telegram.botToken is absent, the gateway uses TELEGRAM_BOT_TOKEN (default account only; named accounts need botToken or tokenFile).
  2. Explicit reference. Write "botToken": "${TELEGRAM_BOT_TOKEN}". Substitution works in any config string, only uppercase names expand, and missing variables "stay visibly unresolved" and emit a warning. We prefer this form; the config shows where the secret lives.

Precedence, quoted from the setup page: "tokenFile beats botToken, and botToken beats env." So when a rotated token does not take, look for a stale botToken or tokenFile. tokenFile must be a regular file; symlinks are rejected.

Step 3: The channels.telegram block in openclaw.json

The docs describe ~/.openclaw/openclaw.json as JSON5 (comments and trailing commas tolerated); we keep ours strict JSON. A minimal first block:

{
  "channels": {
    "telegram": {
      "enabled": true,
      "botToken": "${TELEGRAM_BOT_TOKEN}",
      "dmPolicy": "pairing"
    }
  }
}

The CLI shortcut openclaw channels add --channel telegram --token <bot-token> writes the token straight into the config file; on servers we prefer the env reference above.

dmPolicy decides who may talk to the bot in a private chat. The four documented values:

  • pairing (default): an unknown sender gets an 8-character code instead of an answer, and nothing is processed until you approve it from the CLI. Codes expire after one hour, at most three pending per channel account.
  • allowlist: only ids in allowFrom are served, no pairing flow. Needs at least one id.
  • open: requires allowFrom to contain "*". The docs: use it "only for intentionally public bots with tightly restricted tools".
  • disabled: no direct messages.

The gateway watches the config file and applies most changes automatically (the docs' hot-reload page lists what still needs a restart); on Docker, docker compose up -d openclaw-gateway recreates the container.

Step 4: Find your Telegram user id and set allowFrom

This is where most setups go wrong. allowFrom takes numeric Telegram user ids, quoted as strings: "not a phone number, username, chat/group ID, or the bot's ID". Prefixes telegram: and tg: are accepted and normalized.

Three documented ways to learn your id:

  1. Start the gateway with the default pairing policy, message your bot, and read Your Telegram user id in its pairing reply.
  2. Run openclaw logs --follow and read senderUserId in the telegram pairing request entry.
  3. Once the bot answers you, send /whoami@<bot_username>; it confirms your user id and, in an allowed group, the group id.

Now lock the bot to yourself. The docs' owner-only example keeps pairing and adds allowFrom: you are pre-approved, strangers only see a code you can ignore.

{
  "channels": {
    "telegram": {
      "enabled": true,
      "botToken": "${TELEGRAM_BOT_TOKEN}",
      "dmPolicy": "pairing",
      "allowFrom": ["123456789"]
    }
  }
}

For no pairing prompts at all, set dmPolicy to allowlist with the same allowFrom.

Step 5: Start, verify, and send the first message

openclaw gateway start
openclaw channels status --probe
openclaw logs --follow

channels status --probe confirms with Telegram that the channel is ready, so a wrong token shows up here first. On Docker, reuse the install page's wrapper docker compose exec openclaw-gateway sh -lc 'node dist/index.js gateway health' with the subcommand swapped for channels status --probe.

Message your bot. If you kept pairing without allowFrom, you get a code; approve it with:

openclaw pairing list telegram
openclaw pairing approve telegram <CODE>

Approved senders are stored in ~/.openclaw/state/openclaw.sqlite and survive restarts. Approval "grants DM access only", never group access. To test a token without OpenClaw, call getMe: curl -s "https://api.telegram.org/bot<TOKEN>/getMe" on the server rather than in a browser, so the token stays out of history.

Private chat versus groups, and privacy mode

The docs treat groups as supported behind mention gating, but for a one-owner assistant the private chat is safer: every other member's message is untrusted content the model reads, the docs' prompt-injection case. If you need a group, three things must line up.

1. The group must be allowlisted. groupPolicy defaults to allowlist, so the chat id must appear under channels.telegram.groups (or a "*" wildcard). Supergroup ids are negative and start with -100; read one from openclaw logs --follow, a forwarded-id bot or getUpdates.

2. Decide who may trigger it. groupAllowFrom takes numeric user ids, same rules as allowFrom; if unset, it falls back to allowFrom. Put only your own id there.

3. Mentions and privacy mode. With requireMention: true the bot answers only when mentioned, the mode the setup page pairs with Telegram's privacy mode (on by default), under which a bot receives only commands addressed to it, replies to its own messages and service messages. For requireMention: false, or if mentions do not reach the bot, disable privacy mode with /setprivacy in BotFather, then remove and re-add the bot; Telegram applies the change only on re-join. Group admins receive all messages regardless.

{
  "channels": {
    "telegram": {
      "enabled": true,
      "botToken": "${TELEGRAM_BOT_TOKEN}",
      "dmPolicy": "allowlist",
      "allowFrom": ["123456789"],
      "groupPolicy": "allowlist",
      "groupAllowFrom": ["123456789"],
      "groups": {
        "-1001234567890": { "requireMention": true }
      }
    }
  }
}

Polling or webhook

"Long polling is the default." The gateway calls Telegram's getUpdates in a loop and needs outbound HTTPS only: no open port, no domain, no certificate. It is what we run.

Webhook mode needs channels.telegram.webhookUrl and channels.telegram.webhookSecret; the listener defaults to webhookHost 127.0.0.1, webhookPort 8787 and webhookPath /telegram-webhook (/healthz is reserved). It binds to loopback, so you put a reverse proxy with a real certificate in front (or set webhookCertPath for a self-signed certificate on a bare IP); Telegram delivers only to HTTPS on ports 443, 80, 88 or 8443.

One Bot API rule bites people who switch back and forth: getUpdates and webhooks are mutually exclusive. OpenClaw calls deleteWebhook when polling starts; if that fails on a transient network error, the leftover webhook surfaces as a getUpdates conflict and the gateway rebuilds the transport and retries. Only if that keeps failing do you call deleteWebhook yourself.

Common errors and what they mean

SymptomLikely causeFix
Startup logs getMe returned 401Token mistyped or revoked, or a stale botToken/tokenFile overriding .envRegenerate the token in BotFather, update botToken or TELEGRAM_BOT_TOKEN, rerun channels status --probe
Bot is online but never answers your DMPairing request pending, or allowFrom holds a username, phone number, group id or the bot's own idopenclaw pairing list telegram; read senderUserId in the logs; fix allowFrom; openclaw doctor --fix for legacy entries
Commands partly workSender not authorized, or setMyCommands failed with BOT_COMMANDS_TOO_MUCHAuthorize the sender; reduce custom commands or disable native menus
Bot ignores non-mention group messagesTelegram privacy mode is on while requireMention is false/setprivacy Disable in BotFather, then remove and re-add the bot
Bot sees nothing in a groupGroup not under channels.telegram.groups, no "*" entry, or bot not a memberAdd the -100... id; channels status --probe checks membership; logs show the skip reason
Polling stall detectedNo completed long-poll liveness for 120 seconds; often IPv6 DNS or unstable egressRestarts itself; if it repeats, check dig +short api.telegram.org AAAA, force IPv4 (channels.telegram.network.autoSelectFamily: false or NODE_OPTIONS=--dns-result-order=ipv4first) or set channels.telegram.proxy
Text works, attachments fail with getaddrinfo EAI_AGAIN or ENOTFOUNDMedia downloads still use local DNS even with proxy environment variables setSet channels.telegram.proxy (HTTP(S) or SOCKS5) so it resolves media hostnames

Security: one owner, one token, rotate on leak

OpenClaw is remote code execution by design: whoever can message the bot can drive its tools. The security page wants "one trust boundary per gateway: a single operator, or a team whose members trust each other", and the prompt-injection page limits high-risk tools (exec, browser, web_fetch, web_search) to trusted agents or explicit allowlists. In practice:

  • Exactly one id in allowFrom, yours. No "*", no shared bot with exec enabled.
  • Restrict tools for anyone who is not you. The docs' example denies write and edit for the wildcard sender and leaves the owner unrestricted:
    "direct": {
      "*": { "tools": { "deny": ["write", "edit"] } },
      "123456789": { "tools": {} }
    }
  • Never paste the token into a chat, ticket or screenshot. If it leaks, /token in BotFather issues a replacement; update .env and rerun channels status --probe.
  • Keep the gateway on loopback. The Control UI listens on http://127.0.0.1:18789/ by default; reach it over an SSH tunnel or Tailscale, never a published port. Telegram does not need it, and CVE-2026-25253 (one-click RCE via token theft) is why we insist.
  • Run openclaw security audit after every config change; the docs call it the one command that tells you if you have drifted.

Running it on a server

A Telegram assistant must be online when you message it, so a laptop will not do; any small Linux VM with outbound HTTPS is enough. Our VPS catalogue covers that range; if you would rather skip the install, the ready OpenClaw server from 9.35 EUR per month ships Ubuntu 24.04 with the gateway already in Docker: add your LLM key, connect the bot as above, and the agent answers only you. Weighing a hosted model against your own? See this comparison.

Questions

Do I need a domain or an open port for the Telegram bot?

No. Long polling is the default and works over outbound HTTPS only. A domain, a certificate and an inbound port are needed only if you switch to webhook mode.

Why does my bot answer with a code instead of a reply?

That is the default pairing policy: your id is not approved yet. Run openclaw pairing approve telegram <CODE> or add your numeric user id to allowFrom. Codes expire after one hour.

Can I use my @username in allowFrom?

No. allowFrom and groupAllowFrom accept numeric Telegram user ids only. Older configs with @username entries are converted by openclaw doctor --fix.

The bot is in my group but reads nothing. Why?

Two gates apply: the group id must be listed under channels.telegram.groups (or a "*" entry), and Telegram privacy mode must let the bot see the messages. Keep requireMention: true, or disable privacy mode via /setprivacy and re-add the bot, or make the bot a group admin.

Want this running privately?
An AI assistant on your own server in the EU, with your data staying where you put it.
See what it costs

Ready to start?

Deploy in minutes or talk to an engineer about what fits your project.