> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wednesdayai.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Telegram

> Connect a Telegram bot to WednesdayAI: bot setup, access policies, groups, streaming, webhook mode, and troubleshooting.

# Telegram

WednesdayAI connects to Telegram via the Bot API using long polling (default) or webhooks. Setup requires a bot token from BotFather — there is no QR scanning or phone-number linking. The channel runs via grammY with per-chat sequencing.

## Prerequisites

* A Telegram bot token from BotFather
* Node.js 24+ on the gateway host
* Outbound HTTPS access to `api.telegram.org` from the gateway

## Quick setup

<Steps>
  <Step title="Create a bot in BotFather">
    Open Telegram and message `@BotFather`. Run `/newbot`, follow the prompts, and save the bot token (format: `123456789:AABBCCxyz...`).
  </Step>

  <Step title="Configure the bot token and access policy">
    ```json5 theme={"dark"}
    // ~/.openclaw/openclaw.json
    {
      channels: {
        telegram: {
          enabled: true,
          botToken: "123456789:AABBCCxyz...",
          dmPolicy: "pairing",           // pairing | allowlist | open | disabled
          allowFrom: ["123456789"],       // numeric Telegram user IDs
          groupPolicy: "allowlist",
          groups: {
            "*": { requireMention: true },
          },
        },
      },
    }
    ```

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

    To read the token from a file instead: `channels.telegram.tokenFile: "/run/secrets/telegram-token"`.
  </Step>

  <Step title="Start the gateway">
    ```bash theme={"dark"}
    openclaw gateway run
    ```

    Long polling starts automatically. No webhook URL is needed for the default mode.
  </Step>

  <Step title="Approve the first pairing request">
    ```bash theme={"dark"}
    openclaw pairing list telegram
    openclaw pairing approve telegram <CODE>
    ```

    Pairing codes expire after 1 hour.
  </Step>
</Steps>

## Finding Telegram user IDs

Allowlists require **numeric Telegram user IDs**, not usernames.

```bash theme={"dark"}
# Method 1: DM your bot and watch the logs
openclaw logs --follow
# Look for "from.id" in the output

# Method 2: Bot API
curl "https://api.telegram.org/bot<TOKEN>/getUpdates"

# Method 3: third-party bots
# @userinfobot or @getidsbot (less private)
```

`@username` entries in `allowFrom` or `groupAllowFrom` are not valid at runtime. If your config has them, run `openclaw doctor --fix` to resolve them to numeric IDs (requires a live bot token). `openclaw doctor --fix` can also recover allowlist entries from pairing-store files when migrating to `allowlist` mode.

## Access control

### DM policy

| `dmPolicy`    | Effect                                                                        |
| ------------- | ----------------------------------------------------------------------------- |
| `"pairing"`   | New senders receive a one-time pairing code; ignored until approved (default) |
| `"allowlist"` | Only IDs in `allowFrom` can DM (must have at least one entry)                 |
| `"open"`      | Any sender allowed (requires `allowFrom: ["*"]`)                              |
| `"disabled"`  | Bot ignores all DMs                                                           |

`dmPolicy: "allowlist"` with empty `allowFrom` is a config validation error — it would silently block all DMs.

Per-account override: `channels.telegram.accounts.<id>.dmPolicy` and `channels.telegram.accounts.<id>.allowFrom` override channel-level defaults for that account. Named accounts do **not** inherit `accounts.default.allowFrom` — they inherit `channels.telegram.allowFrom` when account-level values are unset.

### Group policy

```json5 theme={"dark"}
{
  channels: {
    telegram: {
      groupPolicy: "allowlist",            // open | allowlist | disabled
      groupAllowFrom: ["123456789"],        // numeric user IDs
      groups: {
        "*": { requireMention: true },
        "-1001234567890": {
          requireMention: false,
          groupPolicy: "open",
        },
      },
    },
  },
}
```

Two controls apply together:

1. **Group membership** (`channels.telegram.groups`) — if present, acts as an allowlist; `"*"` allows all groups. If absent with `groupPolicy: "open"`, all groups pass.
2. **Sender policy** (`groupPolicy` + `groupAllowFrom`) — if `groupAllowFrom` is unset, falls back to `allowFrom`.

<Warning>
  Group sender auth does **not** inherit DM pairing-store approvals (since v2026.2.25). Pairing is DM-only. For groups, set `groupAllowFrom` or per-group `allowFrom` explicitly.
</Warning>

<Warning>
  If `channels.telegram` is completely absent from config, the runtime group-policy fallback is `"allowlist"` (fail-closed), even if `channels.defaults.groupPolicy` is set.
</Warning>

### Mention gating

Group messages require a mention by default. To disable per group or globally:

```json5 theme={"dark"}
{ channels: { telegram: { groups: { "*": { requireMention: false } } } } }
```

Mention sources: native `@botusername`, configured `mentionPatterns`, or reply-to-bot. Session-level toggle (owner-gated): `/activation always` or `/activation mention`.

## BotFather settings

<AccordionGroup>
  <Accordion title="Privacy mode (group visibility)">
    Telegram bots default to **Privacy Mode** — they only see messages that mention them or start with `/`. If the bot must see all group messages:

    * BotFather: `/setprivacy` → select your bot → **Disable**
    * Or make the bot a group admin

    Remove and re-add the bot in each group after changing privacy mode. `openclaw channels status` warns when Privacy Mode may be causing missed messages.
  </Accordion>

  <Accordion title="Helpful BotFather commands">
    * `/setjoingroups` — allow or deny the bot from being added to groups
    * `/setprivacy` — toggle group message visibility
    * `/setcommands` — the gateway sets this automatically when `commands.native` is enabled
  </Accordion>
</AccordionGroup>

## Native and custom commands

```json5 theme={"dark"}
{
  commands: {
    native: "auto",   // enable native command menu for Telegram
  },
  channels: {
    telegram: {
      customCommands: [
        { command: "briefing", description: "Daily briefing" },
        { command: "status", description: "Check status" },
      ],
    },
  },
}
```

Custom commands are menu entries only — they do not auto-implement behavior. Plugin and skill commands can still be used when typed even if not in the Telegram menu. Command names must be lowercase `a-z`, `0-9`, `_`, length 1–32.

`setMyCommands failed` at startup means outbound DNS/HTTPS to `api.telegram.org` is blocked.

## Live streaming

```json5 theme={"dark"}
{
  channels: {
    telegram: {
      streaming: "partial",   // off | partial | block | progress
    },
  },
}
```

| Mode                | Behavior                                                                                      |
| ------------------- | --------------------------------------------------------------------------------------------- |
| `partial` (default) | DMs: native draft streaming via `sendMessageDraft`. Groups: preview message + in-place edits. |
| `block`             | Legacy preview mode (full block send with edits).                                             |
| `progress`          | Maps to `partial` on Telegram.                                                                |
| `off`               | No streaming; final reply only.                                                               |

Telegram native draft streaming (`sendMessageDraft`) is available for all bots since Bot API 9.5 (March 2026). If it is unavailable or rejected, WednesdayAI falls back to `sendMessage` + `editMessageText` automatically.

## Webhook mode

Default is long polling. To use webhooks:

```json5 theme={"dark"}
{
  channels: {
    telegram: {
      webhookUrl: "https://your-public-domain.com/telegram-webhook",
      webhookSecret: "a-long-random-secret",      // required
      webhookPath: "/telegram-webhook",            // default
      webhookHost: "127.0.0.1",                    // default; set 0.0.0.0 for direct ingress
      webhookPort: 8787,                           // default
    },
  },
}
```

The local webhook listener binds to `webhookHost:webhookPort`. If your public endpoint differs, place a reverse proxy in front and point `webhookUrl` at the public URL.

Restart the gateway for webhook changes to take effect.

## Multi-account setup

```json5 theme={"dark"}
{
  channels: {
    telegram: {
      dmPolicy: "allowlist",
      accounts: {
        personal: { botToken: "111:AAA", allowFrom: ["123456789"] },
        work:     { botToken: "222:BBB", allowFrom: ["987654321"] },
      },
    },
  },
}
```

Account selection default: `default` if present, otherwise the first configured account id (sorted). Per-account `dmPolicy`, `allowFrom`, `groupAllowFrom`, and `capabilities` overrides are supported. Named accounts inherit `channels.telegram.allowFrom` (not `accounts.default.allowFrom`) when unset.

## Inline buttons

```json5 theme={"dark"}
{
  channels: {
    telegram: {
      capabilities: {
        inlineButtons: "allowlist",  // off | dm | group | all | allowlist (default)
      },
    },
  },
}
```

Callback button clicks are delivered to the agent as text: `callback_data: <value>`.

## Reaction notifications

```json5 theme={"dark"}
{
  channels: {
    telegram: {
      reactionNotifications: "own",   // off | own | all (default: own)
      reactionLevel: "minimal",       // off | ack | minimal | extensive (default: minimal)
    },
  },
}
```

`own` = reactions to messages the bot sent (best-effort via sent-message cache). Reaction events still respect `dmPolicy`/`allowFrom`/`groupPolicy` — unauthorized senders are dropped.

## Actions and config writes

```json5 theme={"dark"}
{
  channels: {
    telegram: {
      actions: {
        reactions: true,
        sendMessage: true,
        deleteMessage: true,
        sticker: false,               // default: false
      },
      configWrites: true,             // default: true — allow group migration writes etc.
    },
  },
}
```

The `editMessage` and `createForumTopic` actions default to `true` but can be disabled: `actions: { editMessage: false, createForumTopic: false }`

## Delivery settings

```json5 theme={"dark"}
{
  channels: {
    telegram: {
      textChunkLimit: 4000,           // chars per outbound chunk
      chunkMode: "length",            // length | newline (paragraph-aware)
      mediaMaxMb: 5,                  // inbound media cap (MB)
      linkPreview: true,              // enable link previews
      timeoutSeconds: 30,             // Telegram API client timeout
      replyToMode: "off",             // off | first | all
      retry: {
        attempts: 3,
        minDelayMs: 500,
        maxDelayMs: 5000,
      },
    },
  },
}
```

DM and group history injection:

```json5 theme={"dark"}
{
  channels: {
    telegram: {
      historyLimit: 50,               // group context history (0 = disabled)
      dmHistoryLimit: 0,              // DM history injection
    },
  },
}
```

## Network and proxy

```json5 theme={"dark"}
{
  channels: {
    telegram: {
      proxy: "socks5://user:pass@proxy-host:1080",   // route Bot API calls through proxy
      network: {
        autoSelectFamily: true,        // Node 22+ default; set false on WSL2
        dnsResultOrder: "ipv4first",   // ipv4first | verbatim
      },
    },
  },
}
```

Environment overrides (temporary): `OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY=1`, `OPENCLAW_TELEGRAM_DNS_RESULT_ORDER=ipv4first`.

## Disabling without removing config

```json5 theme={"dark"}
{ channels: { telegram: { enabled: false } } }
```

Restart the gateway: `systemctl --user restart openclaw-gateway`.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Bot does not see group messages">
    1. Check BotFather: `/setprivacy` — must say **Disabled** (or make the bot a group admin)
    2. Remove and re-add the bot in the group after changing privacy mode
    3. `channels.telegram.groups` must include the group ID (or `"*"`)
    4. `requireMention: false` if you want responses without mentions
  </Accordion>

  <Accordion title="Group messages silently ignored">
    Check `channels.telegram.groups` — the group must be listed or `"*"` used. Get the group ID from `openclaw logs --follow` (field `chat.id`). Run `openclaw doctor` for duplicate key issues in `openclaw.json`.
  </Accordion>

  <Accordion title="Commands work partially or not at all">
    Confirm the sender's numeric ID is in `allowFrom`. Command authorization applies even when `groupPolicy: "open"`. `setMyCommands failed` means DNS/HTTPS to `api.telegram.org` is blocked.
  </Accordion>

  <Accordion title="Network instability or polling drops">
    Check for IPv6 routing issues: `dig +short api.telegram.org AAAA`. If broken, force IPv4:

    ```json5 theme={"dark"}
    { channels: { telegram: { network: { dnsResultOrder: "ipv4first" } } } }
    ```

    On VPS hosts with unstable egress, route through a proxy:

    ```json5 theme={"dark"}
    { channels: { telegram: { proxy: "socks5://user:pass@proxy-host:1080" } } }
    ```
  </Accordion>

  <Accordion title="Forum topic messages going to wrong session">
    Forum topics use session key `agent:<id>:telegram:group:<chatId>:topic:<threadId>`. Check that `replyToMode` is set correctly and that the topic has the right per-topic overrides under `groups.<chatId>.topics.<threadId>`.
  </Accordion>
</AccordionGroup>

## Configuration reference

Full field index for `channels.telegram.*`:

* Auth: `enabled`, `botToken`, `tokenFile`, `accounts.*`

* Access: `dmPolicy`, `allowFrom`, `groupPolicy`, `groupAllowFrom`, `groups`, `groups.*.topics.*`

* Commands: `commands.native`, `commands.nativeSkills`, `customCommands`

* Streaming: `streaming`

* Delivery: `textChunkLimit`, `chunkMode`, `linkPreview`, `mediaMaxMb`, `timeoutSeconds`, `replyToMode`, `retry.*`

* History: `historyLimit`, `dmHistoryLimit`, `dms.<id>.historyLimit`

* Actions: `actions.reactions`, `actions.sendMessage`, `actions.deleteMessage`, `actions.sticker`

* Capabilities: `capabilities.inlineButtons`

* Reactions: `reactionNotifications`, `reactionLevel`

* Network: `proxy`, `network.autoSelectFamily`, `network.dnsResultOrder`

* Webhook: `webhookUrl`, `webhookSecret`, `webhookPath`, `webhookHost`, `webhookPort`

* Misc: `configWrites`, `defaultTo`

* [Reference: config](/reference/config)

## Related

* [Channel setup](/admin/channels/index)
* [Channel troubleshooting](/admin/troubleshooting)
* [Doctor](/admin/doctor)
