Telegram channel
# Telegram channel
Telegram sends bot updates to `POST /telegram`. BONNIE checks the shared webhook secret, acknowledges the update, and sends results through the Bot API's `sendMessage` method. Long polling with `getUpdates` is not implemented.
## Agent setup
```go
package main
import (
"github.com/mark3labs/bonnie"
"github.com/mark3labs/bonnie/channel/telegram"
)
func main() {
bonnie.New(
bonnie.WithTelegram(telegram.Config{Username: "your_bot"}),
).Serve()
}
```
```sh
export TELEGRAM_BOT_TOKEN='123456789:REPLACE_ME'
export TELEGRAM_WEBHOOK_SECRET='REPLACE_WITH_A_RANDOM_SECRET'
# Also set the model provider key.
bonnie dev --addr 127.0.0.1:8081 --tui=false
```
The root option requires both credentials. Empty `Token`, `Secret`, and `APIURL` fields come from `TELEGRAM_BOT_TOKEN`, `TELEGRAM_WEBHOOK_SECRET`, and `TELEGRAM_API_URL`. Explicit nonempty fields take precedence. Low-level `telegram.New(runner, cfg, coreOptions...)` requires `Secret` but permits no `Token`; it can receive input but cannot send replies.
Set `Username` to the bot username without `@`. There is no username environment fallback or automatic `getMe` discovery. `Command` defaults to `ask`, without `/`. `Path` defaults to `/telegram`. `APIURL` defaults to `https://api.telegram.org`; normally leave it unset.
## Bot and webhook setup
1. Create a bot with BotFather and save the bot token.
2. Set `Username` to the username BotFather assigned.
3. Generate a separate webhook secret. Telegram accepts 1–256 characters from letters, digits, `_`, and `-` for `secret_token`; the adapter checks only that its configured secret is nonempty.
4. Start BONNIE behind public HTTPS or a tunnel.
5. Register the webhook with the same secret:
```sh
curl -sS -X POST \
"https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook" \
--data-urlencode 'url=https://HOST/telegram' \
--data-urlencode "secret_token=$TELEGRAM_WEBHOOK_SECRET" \
--data-urlencode 'allowed_updates=["message"]'
curl -sS "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getWebhookInfo"
```
Use the configured `Path` if you changed it. Bot API URLs contain the token: protect shell history, proxy logs, and monitoring output. Do not publish the URL with a real token.
For groups, add the bot and keep privacy mode enabled unless it must receive more traffic. Privacy mode controls which messages Telegram delivers; BONNIE's admission rules still apply. Use `/ask@your_bot` for an explicit group invocation. Registering command descriptions with BotFather or `setMyCommands` can improve the UI, but it is not required by the handler. Give only the chat permissions needed to send replies, including forum-topic access where applicable.
## Admission and addresses
| Surface | Admitted input | Local address |
| --- | --- | --- |
| Private chat | Every nonempty text message from a non-bot sender | `` |
| Group or supergroup | Leading `/ask`, matching `/ask@your_bot`, or an entity mention of the configured username | `` |
| Forum topic | Same group invocation rule, with `message_thread_id` | `/` |
| Channel posts | Not supported | None |
Saved addresses add `telegram/`, for example `telegram/-1001234567890/42`. A normal group without forum topics has one shared conversation. A message reply ID is not a separate conversation key. Bindings survive a restart.
Matching leading commands are removed from input. A mention admits a group message but is not removed from its text. Username and command matches are case-sensitive. The mention implementation indexes text by bytes, although Telegram entity offsets use UTF-16 units. Mentions after non-ASCII text can fail admission; a leading `/ask@your_bot` avoids dependence on those offsets.
Only the top-level `message` update is read. Edited messages, callback queries, channel posts, attachments, and bot-authored messages are ignored. The adapter does not read or deduplicate `update_id`. A webhook retry can therefore produce another input or cancellation request.
## Questions and controls
A waiting run sends its question as text. The next admitted text on the same chat or topic answers it. There are no inline approval buttons. In a group, an ordinary reply without the invocation is ignored even if the bot already has a binding. Use `/ask@your_bot ANSWER`.
Shared controls can be sent directly in a private chat, or through the invocation in a group, for example `/ask@your_bot /new`. Native `/cancel` and `/cancel@your_bot` work in private chats, groups, and supergroups without `/ask`. They require no trailing text, and a targeted command must match `Username`. Cancellation selects the current chat or topic; it has no turn-ID guard. Repeated updates are not suppressed. See [shared controls](/channels/overview#follow-ups-questions-and-controls).
The result can say that cancellation was requested. This does not confirm that external work stopped or undo an action.
## Verification and identity
Every request must carry `X-Telegram-Bot-Api-Secret-Token` equal to `Secret`. The comparison is constant-time. A missing or incorrect header returns 401. This is a shared secret check, not a signature over the body or a timestamp check. Anyone with the secret can submit an update with a claimed user ID. Keep HTTPS and the secret secure.
The JSON body reader is capped at 1 MiB. Verified requests are acknowledged with 200 and body `ok`, including malformed or ignored updates. An ACK is not a durable input receipt or completed turn.
Admitted input records principal authenticator `telegram`, kind `user`, decimal sender ID, and attributes `username` and `chat_id`. This is not an approval authorization policy. Users of a shared chat can answer its waiting run. Add host checks for sensitive actions, and protect the default [HTTP API](/channels/http#authentication-and-authorization).
## Delivery and limits
`sendMessage` receives `chat_id`, `text`, and, for topics, `message_thread_id`. No `parse_mode` is set: replies are plain text, with model markdown shown as written. There is no activity indicator or file delivery.
The current splitter uses a 4,096-byte part budget and at most five parts, not a Unicode character-count guarantee. Excess text can be cut without a truncation notice. Ordinary delivery logs HTTP failures and does not have a durable retry queue. It does not separately validate an `ok: false` body returned with HTTP 2xx. The tracked schedule path does validate Telegram's `ok` field. The durable run remains available for inspection.
## Hand-offs and schedules
`Receive(ctx, "CHAT_ID", text, opts)` or `Receive(ctx, "CHAT_ID/TOPIC_ID", text, opts)` binds or continues that conversation before execution. Targets must be strings with numeric chat and topic components, not Go integer values. No root instruction is posted; the result is the visible message.
Use the same string target for tracked schedules. Delivery failures retry the saved result, but a crash or multipart retry can repeat posts. See [scheduled channel work](/channels/overview#scheduled-channel-work).
## Source and tests
[Telegram godoc](https://pkg.go.dev/github.com/mark3labs/bonnie/channel/telegram). Source: `channel/telegram/telegram.go` and `tracked.go`, with `options.go` for environment defaults. Tests cover secret verification, private and group admission, topics, bot filtering, cancellation, hand-off continuity, and tracked delivery in `telegram_test.go`, `cancel_test.go`, `handoff_test.go`, and `tracked_test.go`.