Skip to main content

Telegram Onboarding: Shared Test Bot to Your Own Bot

This guide walks you from an empty Telegram channel to a working, production-ready setup. It complements the Telegram channel page, which covers message shapes and capability detail — this guide is the ordered path you follow the first time, and re-run whenever you attach, rotate, or retire a bot. Telegram has no approval cycle and no brand registration. The only real gate is that a production bot is a BotFather bot that you own, and Orbit can only connect it via the token BotFather hands you. Until you have a token, the built-in test account lets you send real messages through Orbit’s shared bot so you can validate your integration today.

1. The test-account loop — send real messages today

The test account routes through Devotel’s shared, already-connected bot. You never touch BotFather to use it. Three steps:

Step 1: Start verification

The response carries a linking token, a deep link (https://t.me/<shared_bot>?start=<token>), and a QR-code value. Open the deep link (or scan the QR code) in Telegram on your device — the linked chat with the shared bot is what makes the channel test_active.

Step 2: Send a test message through the shared bot

While the channel is test_active, the send goes out under the shared bot. Once you bring your own bot and the channel enters production mode, the same endpoint routes through your bot instead, so your integration code does not change between test and production. Check where you are at any point with GET /api/v1/telegram/channel-state — it returns the channel mode plus the verified-chat list when test-linked:
The successful response uses the standard envelope:
200

Step 3: Deliver demo flows while still test-scope

Test scope is enough to prove a group-keyword flow end to end: message a group that your shared-bot link is in, confirm the inbound message.received hits your webhook, then confirm your keyword router fires. The demo pack (GET /api/v1/telegram/demo-content) ships sample group-keyword and cost-rollup flows so you can validate rendering and routing before you commit a token. To reset the loop — for example, to point a different tester device at it later — call POST /api/v1/telegram/test/disconnect. Verified-chat links clear; the daily test quota carries over. The test account is for evaluation, not production: sends go out under the shared bot’s branding, not yours, and the quota is deliberately small. Move to your own bot below for anything a customer touches.

2. Bring your own bot (paste-token onboarding)

The production path is a single call once BotFather hands you a token.
  1. Talk to @BotFather in Telegram and create a bot. Save the token it returns — treat it like a bearer credential (see Token handling on the channel page).
  2. Connect the token to Orbit:
On success, the response is a standard envelope; data confirms the connected bot and meta identifies the request:
200
One bot, one tenant: Orbit identifies a bot by its bot_id (the numeric prefix before the colon) and enforces a single-tenant claim. Once an Orbit organization connects a bot, the same bot_id cannot be connected to a second organization until the first disconnects — a second attempt returns 409 TELEGRAM_BOT_ALREADY_CONNECTED:
409 Error
Disconnect the bot from the other organization, or if you suspect the token leaked, revoke it in BotFather and follow the ownership disputes recovery paths. Retrying the same token without resolving ownership returns the same conflict.
  1. Orbit registers the webhook subscription immediately: the bot is pointed at https://api.orbit.devotel.io/api/v1/webhooks/inbound/telegram, and the X-Telegram-Bot-Api-Secret-Token value Telegram must present is rotated and stored in Orbit’s encrypted credential vault. Inbound delivery is live from that call.
Access: /test/*, /connect-bot, and /disconnect-bot are write-class operations — they require the owner, admin, or developer role, not just a valid session.

3. Send with metadata — types, keyboards, and formatting

Sending goes through the unified Messaging surface (POST /api/v1/messages/telegram). The text body is the top-level body field; everything else — the send type, parse_mode, and keyboards — lives under metadata, whose values are all strings. Supported type values: text (default), photo, video, document, location. A photo, video, or document send needs metadata.mediaUrl; a location send needs metadata.latitude and metadata.longitude. Supply inline_keyboard and reply_keyboard as JSON-stringified button[][] arrays — one string, an outer array of rows, each row an array of button objects. parse_mode accepts HTML or MarkdownV2. A full send with a two-row inline keyboard. Each language keeps the keyboard as a JSON string inside metadata; do not pass the button rows as a nested object.
A successful request is accepted for delivery and returns the full response envelope:
202
force_reply and remove_keyboard use the same stringified-array convention when you want a forced text reply or a keyboard removed.

4. Inbound webhook — receive Telegram messages

A plain inbound message and an inline-keyboard press both arrive at your webhook as message.received on the telegram channel. For a button press, the button’s callback_data becomes the message body; there is no separate telegram.callback event to subscribe to.
Telegram sends X-Telegram-Bot-Api-Secret-Token to Orbit’s registered inbound URL. Orbit rotates the per-bot secret_token during connect and validates it before forwarding the normalized event to your receiver. Your receiver sees the forwarded message.received payload; it does not receive Telegram’s secret header. Never use a BotFather token as a webhook secret. Example forwarded event:

5. Edit and delete are Orbit-side only

PATCH /messages/:id and DELETE /messages/:id operate on the Orbit message record — they never call Telegram’s editMessageText or deleteMessage:
  • PATCH: edits the body and/or scheduled_at of a message that is still scheduled (not yet dispatched). Once the message has been sent, the edit returns 409 MESSAGE_NOT_EDITABLE — a delivered message is not re-editable.
  • DELETE: soft-deletes the Orbit message record and syncs the removal across your dashboards.
If you claim the message is edited or removed in Telegram itself, that is wrong — Orbit does not attempt it. For a delivered message the right recovery is a follow-up correction sent to the chat.

6. Lifecycle — retire the bot

POST /api/v1/telegram/disconnect-bot soft-deletes the connected-bot row and revokes the webhook registration with Telegram (deleteWebhook). The conversation history survives — Orbit keys history by bot_id, not by the token — so if you rotate the token via BotFather and reconnect with the new value, your history is preserved.

7. Error reference

A failed send returns a JSON error envelope whose error.code is one of the values below. Webhook security depends on the same secret_token per bot that Orbit rotates on connect — inbound updates that do not present it are rejected, so a spoofed sender cannot inject traffic into your callbacks.

8. Group events via the telegram-group-events module

Community bots added to groups or supergroups produce a different event shape — member joins and leaves, message-delete floods, and group keyword flows land on the dedicated telegram-group-events webhook module rather than the plain inbound path. Keep your primary handler on message.received for the customer chat surface, and subscribe the group module only if your bot operates in groups — the high-churn event classes (member-count churn, message-delete storms) are classified and dropped there so standard routing stays clean.

9. The model behind the ladder

The concept page The channel test-account model documents what this guide walks through operationally: the three-mode state machine, the two gate classes (send-permitted vs bot-owner-only), the shared-bot identity lock, and why the same endpoints route differently per mode so your code doesn’t change between test and production.