Connect a Telegram bot to OpenAB.
| Mode | Description | When to use |
|---|---|---|
| Unified (recommended) | Single OAB binary with embedded webhook server | New deployments, ECS, k8s, Zeabur |
| Standalone Gateway | Separate gateway process, OAB connects via WebSocket | Legacy deployments, custom routing |
The OAB binary embeds the Telegram adapter directly. No separate gateway container needed.
Telegram ──POST──▶ OAB (:8080/webhook/telegram) ──▶ Agent (stdio)
- OAB image with unified features compiled in (default since v0.9.0-beta.4)
- A Telegram bot token (from @BotFather)
- A public HTTPS URL for the webhook
Set environment variables:
| Variable | Required | Description |
|---|---|---|
TELEGRAM_BOT_TOKEN |
Yes | Bot API token from @BotFather |
TELEGRAM_SECRET_TOKEN |
No | Webhook signature validation |
TELEGRAM_BOT_USERNAME |
No | Bot username for @mention gating |
TELEGRAM_RICH_MESSAGES |
No | true (default) for rich formatting |
TELEGRAM_STREAMING |
No | follows TELEGRAM_RICH_MESSAGES |
TELEGRAM_ALLOWED_USERS |
No | Comma-separated Telegram user IDs allowed to interact (empty = deny all) |
TELEGRAM_ALLOW_ALL_USERS |
No | true/false — defaults to false (deny-all). Set true to allow everyone. |
GATEWAY_LISTEN |
No | Listen address (default: 0.0.0.0:8080) |
OAB config (config.toml):
Minimal — bot token via env var, API key passed to agent:
[telegram]
bot_token = "${TELEGRAM_BOT_TOKEN}"
allow_all_users = true
[agent]
env = { KIRO_API_KEY = "${KIRO_API_KEY}" }Recommended — with access control, tuned pool, and streaming:
[telegram]
bot_token = "${TELEGRAM_BOT_TOKEN}"
allowed_users = ["176096071"]
[agent]
env = { KIRO_API_KEY = "${KIRO_API_KEY}" }
[pool]
max_sessions = 3
session_ttl_hours = 1
[reactions]
tool_display = "compact"Table rendering is automatically disabled for Telegram (tables pass through as native markdown for Rich Messages). To force code-block wrapping, set explicitly:
[markdown]
tables = "code"Streaming is enabled by default when Rich Messages are active — replies are streamed live via sendRichMessageDraft with rich formatting, then finalized with sendRichMessage. If TELEGRAM_RICH_MESSAGES=false, streaming is also disabled by default. To override, set TELEGRAM_STREAMING=true or TELEGRAM_STREAMING=false explicitly.
No [gateway] section needed — the unified adapter activates automatically when TELEGRAM_BOT_TOKEN is set, or when the [telegram] section is configured in config.toml.
Instead of (or in addition to) the TELEGRAM_* env vars, you can configure Telegram as a first-class section in config.toml — symmetric with [discord] / [slack]:
Minimal required — only bot_token is needed to activate the adapter:
[telegram]
bot_token = "${TELEGRAM_BOT_TOKEN}" # or use aws-sm:// secret ref (see below)Full example with all available fields:
[telegram]
bot_token = "${TELEGRAM_BOT_TOKEN}" # ${} env expansion supported
secret_token = "${TELEGRAM_SECRET_TOKEN}" # webhook signature validation
trusted_source_only = true # reject requests outside Telegram's IP subnets
rich_messages = true # sendRichMessage rendering (default true)
streaming = true # override; defaults to follow rich_messages
webhook_path = "/webhook/telegram"
allowed_users = ["12345678"] # restrict to specific Telegram user IDs
# allow_all_users = true # set true to allow everyone (default: false)Precedence (per field): [telegram] value (with ${} expansion) → TELEGRAM_* env var → built-in default. This is config-authoritative and matches [discord]/[slack]. Any field you omit falls back to its env var, so existing env-only deployments keep working unchanged.
[telegram] field |
Env fallback | Default |
|---|---|---|
bot_token |
TELEGRAM_BOT_TOKEN |
— |
secret_token |
TELEGRAM_SECRET_TOKEN |
— |
trusted_source_only |
TELEGRAM_TRUSTED_SOURCE_ONLY |
false |
rich_messages |
TELEGRAM_RICH_MESSAGES |
true |
streaming |
TELEGRAM_STREAMING |
follows rich_messages |
webhook_path |
TELEGRAM_WEBHOOK_PATH |
/webhook/telegram |
allowed_users |
TELEGRAM_ALLOWED_USERS (comma-separated) |
[] (deny all if empty) |
allow_all_users |
TELEGRAM_ALLOW_ALL_USERS |
false (deny-all) |
Tip: You can run a pure config-only deployment — no
TELEGRAM_*env vars needed. Just setbot_token = "your-token"directly in[telegram]and the adapter will activate from config alone.
Security hardening: For production deployments, we highly recommend using
aws-sm://secret references instead of hardcoding tokens inconfig.toml. This keeps secrets out of version control and enables rotation and audit:[secrets.refs] tg_token = "aws-sm://openab/prod#telegram_bot_token" tg_secret = "aws-sm://openab/prod#telegram_secret_token" [telegram] bot_token = "${secrets.tg_token}" secret_token = "${secrets.tg_secret}"See secrets-management.md for full documentation.
Restrict which Telegram users can interact with the bot using allowed_users:
[telegram]
bot_token = "${TELEGRAM_BOT_TOKEN}"
allowed_users = ["12345678", "87654321"] # only these user IDs can chatDefault behavior (identity-trust-none):
- No config →
allow_all_usersdefaults tofalse→ bot denies all users - Set
allowed_users = ["12345678"]→ only listed IDs can chat - Set
allow_all_users = true→ open to everyone (opt-in)
Resolution order: config value → TELEGRAM_ALLOWED_USERS env var (comma-separated) → empty (deny all).
Finding your Telegram user ID: Send
/startto @userinfobot or use thegetUpdatesAPI after messaging your bot.
export BOT_TOKEN="your-bot-token"
export WEBHOOK_URL="https://your-public-url"
export SECRET="your-webhook-secret"
curl "https://api.telegram.org/bot${BOT_TOKEN}/setWebhook?url=${WEBHOOK_URL}/webhook/telegram&secret_token=${SECRET}"For deployments that need a separate gateway process (e.g., custom webhook routing, multi-gateway fan-out).
Telegram ──POST──▶ Gateway (:8080) ◀──WebSocket── OAB Pod
(OAB connects out)
- A running OAB instance (with kiro-cli or any ACP agent authenticated)
- Docker or a Kubernetes cluster
- A Telegram bot token (from @BotFather)
- Open @BotFather in Telegram
- Send
/newbot, follow the prompts - Copy the bot token (e.g.
123456:ABC-DEF...) - Optional: send
/setprivacy→Disableso the bot can see all group messages (required for @mention gating in groups)
docker run -d --name openab-gateway \
-e TELEGRAM_BOT_TOKEN="your-bot-token" \
-e TELEGRAM_SECRET_TOKEN="your-webhook-secret" \
-e GATEWAY_WS_TOKEN="your-ws-auth-token" \
-p 8080:8080 \
ghcr.io/openabdev/openab-gateway:0.1.0apiVersion: apps/v1
kind: Deployment
metadata:
name: openab-gateway
spec:
replicas: 1
selector:
matchLabels:
app: openab-gateway
template:
metadata:
labels:
app: openab-gateway
spec:
containers:
- name: gateway
image: ghcr.io/openabdev/openab-gateway:0.1.0
ports:
- containerPort: 8080
env:
- name: TELEGRAM_BOT_TOKEN
valueFrom:
secretKeyRef:
name: openab-gateway
key: telegram-bot-token
- name: TELEGRAM_SECRET_TOKEN
valueFrom:
secretKeyRef:
name: openab-gateway
key: telegram-secret-token
- name: GATEWAY_WS_TOKEN
valueFrom:
secretKeyRef:
name: openab-gateway
key: ws-token
- name: GATEWAY_LISTEN
value: "0.0.0.0:8080"
---
apiVersion: v1
kind: Service
metadata:
name: openab-gateway
spec:
selector:
app: openab-gateway
ports:
- port: 8080
targetPort: 8080Add a [gateway] section to your OAB config.toml:
[gateway]
url = "ws://openab-gateway:8080/ws"
platform = "telegram"
token = "${GATEWAY_WS_TOKEN}"
bot_username = "your_bot_username"
# allowed_users = ["123456789"] # restrict to specific Telegram user IDs
# allowed_channels = ["-1001234567890"] # restrict to specific chat/group IDs
[agent]| Key | Required | Description |
|---|---|---|
url |
Yes | WebSocket URL of the gateway |
platform |
No | Session key namespace (default: telegram) |
token |
No | Shared WS auth token (recommended) |
bot_username |
No | Bot username for @mention gating in groups |
allowed_users |
No | Restrict to listed user IDs (empty = allow all) |
allowed_channels |
No | Restrict to listed chat IDs (empty = allow all) |
The gateway needs a public HTTPS URL for Telegram to send updates to.
cloudflared tunnel --url http://localhost:8080
# Copy the https://xxx.trycloudflare.com URLUse nginx, Caddy, or a cloud load balancer with TLS termination pointing to the gateway's :8080.
export BOT_TOKEN="your-bot-token"
export WEBHOOK_URL="https://your-gateway-host"
export SECRET="your-webhook-secret"
curl "https://api.telegram.org/bot${BOT_TOKEN}/setWebhook?url=${WEBHOOK_URL}/webhook/telegram&secret_token=${SECRET}"Verify:
curl "https://api.telegram.org/bot${BOT_TOKEN}/getWebhookInfo"For forum topic creation (thread isolation like Discord):
- Open the supergroup → Settings → Administrators
- Find the bot → Edit
- Enable Manage Topics
Without this permission, the bot replies in the main chat instead of creating topics.
In groups and supergroups, the bot only responds when @mentioned:
@your_bot explain VPC peering ← triggers agent
explain VPC peering ← ignored in groups
DMs and replies within forum topics always trigger the agent (no @mention needed).
The gateway downloads media from Telegram and stores it locally (~/.openab/media/inbound/<uuid>). Core reads directly from disk — no base64 encoding overhead.
| Type | Handling |
|---|---|
| Images | Downloaded, resized (max 1200px), JPEG compressed, stored to filesystem. Agent sees the image. |
| Documents | Text-based files (.txt, .csv, .rs, .py, etc.) up to 20MB read as UTF-8 and passed to agent. Binary files silently skipped. |
| Audio/Voice | Downloaded and stored. If STT is enabled in Core, automatically transcribed and passed as text. |
Not supported (inbound): video, stickers, animations (silently skipped). Not supported (outbound): bot cannot send images/files back to the user yet.
The bot shows status reactions on your message as the agent works:
| Stage | Emoji |
|---|---|
| Queued | 👀 |
| Thinking | 🤔 |
| Tool use | 🔥 (general), 👨💻 (coding), ⚡ (web) |
| Done | 👍 |
| Error | 😱 |
In supergroups with topics enabled, each new conversation auto-creates a forum topic (like Discord threads). Follow-up messages in the same topic reuse the same agent session.
Agent replies are rendered with Telegram Markdown: bold, code, and code blocks work natively.
With Rich Messages enabled (default, requires Bot API 10.1+), headings (##) and tables render with full formatting via sendRichMessage. Code blocks remain on the legacy path for syntax highlighting and copy-button support. Content exceeding 4096 characters is automatically handled via rich messages (up to 32768 chars).
Note: As of v0.9.0, OAB automatically disables table code-block wrapping for Telegram adapters (both unified and standalone gateway) when Rich Messages are enabled. Tables pass through as raw markdown and render natively. No
[markdown]config is needed. To override this and force code-block wrapping, add:[markdown] tables = "code"Rich Messages requires gateway version v0.6.0-rc.1 or above (
ghcr.io/openabdev/openab-gateway:v0.6.0-rc.1+).
Set TELEGRAM_RICH_MESSAGES=false to disable rich messages and use legacy sendMessage for all replies.
| Variable | Required | Default | Description |
|---|---|---|---|
TELEGRAM_BOT_TOKEN |
Yes | — | Bot API token from @BotFather |
TELEGRAM_SECRET_TOKEN |
No | — | Webhook signature validation |
TELEGRAM_RICH_MESSAGES |
No | true |
Use sendRichMessage for tables/headings/long content (Bot API 10.1+). Set false to opt out. |
TELEGRAM_STREAMING |
No | follows TELEGRAM_RICH_MESSAGES |
Stream replies live via sendRichMessageDraft. Defaults to true when rich messages are enabled, false otherwise. Set false for send-once mode (single final message). |
TELEGRAM_ALLOWED_USERS |
No | — | Comma-separated Telegram user IDs allowed to interact with the bot. Empty = deny all. Unified binary only — standalone gateway uses [gateway].allowed_users instead. |
TELEGRAM_ALLOW_ALL_USERS |
No | false |
Explicit flag: true = allow all users, false = check allowed_users. Defaults to false (deny-all, per identity-trust-none ADR). Unified binary only. |
GATEWAY_WS_TOKEN |
No | — | WebSocket auth token |
GATEWAY_LISTEN |
No | 0.0.0.0:8080 |
Listen address |
TELEGRAM_WEBHOOK_PATH |
No | /webhook/telegram |
Webhook endpoint path |
Bot doesn't respond in groups:
- Check bot privacy mode:
/setprivacy→Disablein @BotFather - Verify
bot_usernamein OAB config matches the bot's actual username - Check the bot is @mentioned in the message
"not enough rights to create a topic":
- Give the bot Manage Topics permission in supergroup admin settings
Webhook returns 502/530:
- Check the Cloudflare Tunnel or reverse proxy is running
- Verify
curl http://localhost:8080/healthreturnsok
Agent spawns but immediately closes:
- Run
kubectl exec -it deployment/openab-telegram -- kiro-cli login --use-device-flow - Ensure auth is persisted on a PVC, not an emptyDir