Skip to main content

Telegram bot dispatch and demo-account model

Telegram on Orbit is a first-class messaging channel — a business contact channel in its own right, not an SMS-lite or WhatsApp-lite shortcut. It is dispatched through exactly one of two assets: your own BotFather bot connected by pasting a token, or the shared demo bot Orbit operates for evaluation. This page defines how a dispatch picks between the two, which delivery mechanics and media semantics make Telegram different from the carrier channels, how Telegram plugs into the shared routing and fallback machinery, and why a channel Telegram does not charge for still writes cost rows. The per-feature how-to lives on the Telegram channel page; the cross-channel ladder vocabulary lives on the channel test-account model. This page owns the Telegram-specific concepts. Tenants ship Telegram when their audience already lives on it: countries where Telegram is the default chat app, support desks that want a free inbound contact surface before any carrier channel is provisioned, and businesses whose customers prefer a rich-app conversation over SMS. Once Telegram is connected, a customer chat is one more identity on a contact and one more channel on a conversation — the inbox, the AI agent, and the routing machinery treat it exactly like the others.

Dispatch: own bot or shared demo bot

Every Telegram send resolves one of two paths, decided by the same gate you meet as a 403:
  • Your own bot — connected once by pasting the BotFather token into POST /api/v1/telegram/connect-bot. All Bot API features are available, dispatched under your bot’s identity.
  • The shared demo bot — available while you evaluate. You link one chat against the shared bot with POST /api/v1/telegram/test/start-verify (a five-minute deep-link token, rendered in the dashboard as a link and QR), and POST /api/v1/telegram/test/send dispatches curated demo content to that chat, bounded by a daily test-send allowance. When your organization also owns a bot, the same test/send endpoint dispatches through your own bot instead — the call shape never changes.
The gate classes are the cross-channel ones: the send-permitted gate opens on either an own bot or an active demo verification, and the asset-owner gate (bot identity mutations, payments, business connections, sticker packs) only opens on an own bot. Both classes are defined once on the channel test-account model; Telegram implements them with the Bot API feature list. The shared machinery that orders your own bot ahead of platform assets — and the identity-bound rule that a send never goes out under an identity that is not yours — is stated once on Who carries the send in the message send request lifecycle.

The demo pack, and what linking a chat seeds

A verified demo account is not an empty channel. On verification, Orbit seeds a curated pack of demo content so the dashboard preview and the test send both have real material to work with:
  • Text templates spanning the common transactional shapes — OTP, welcome, order status, appointment reminder, shipping update, payment receipt, feedback request, booking confirmation, account alert, back-in-stock — with {{1}}-style variables filled by sample values.
  • Inline keyboards for conversational UX — a support menu, NPS collector, agent handoff, and an appointment time picker — rendered as real button matrices.
  • Reply keyboards for the three Bot API request primitives (contact, location, group member) that inline buttons cannot express.
  • Polls, venues, Mini App, and streaming draft — the remainder of the current Bot API surface presented as demo placeholders, so the preview shows what the paid surface will look like when you connect your own bot.
The whole pack is returned by GET /api/v1/telegram/demo-content, so your front end can render it without a Bot API round-trip. Demo content IDs follow a fixed orbit_demo_* prefix, which is how the demo gate filters curated material from operator-authored content on the test path.

Webhook delivery, never polling — and the update envelope

Orbit receives Telegram traffic by webhook only. Connecting a bot registers its webhook against Orbit’s platform endpoint (setWebhook with a per-credential secret); polling (getUpdates) is never used, so there is no update-offset state to lose and no long-poll loop to supervise. The one platform guarantee that keeps re-verifying is webhook health: a scheduled check re-registers any bot whose webhook has drifted (retracted by BotFather, overwritten off-platform, or erroring) so inbound is never silently black-holed — the same drift-guard pattern that protects the shared demo bot’s registration. Every inbound update is mapped into the platform’s common message envelope before routing touches it:
  1. Chat identity — the update’s chat id is keyed against telegram_chats, which joins the chat to a contact. A first message from a new chat creates the contact on its Telegram identity.
  2. Update → envelope — text, media, keyboard callbacks, and contact/location shares are normalized into the same inbound envelope SMS, WhatsApp, and email arrivals use (see the message envelope model); direction is inbound, channel is telegram.
  3. Into the conversation pipeline — the envelope lands on the contact’s open conversation (or opens one) exactly like any other channel; the inbound message routing rules and inbox auto-assignment machinery see no Telegram special case.
That normalization is why the worked example at the bottom of this page is short: after the webhook resolves the update, everything downstream is the shared pipeline.

Resolving inbound updates back to your organization

Telegram webhook payloads do not carry the bot id — every bot’s updates arrive on the same endpoint — so routing depends on the X-Telegram-Bot-Api-Secret-Token header Telegram echoes back from the secret registered at setWebhook time. The credentials repository resolves that header in ordered lanes:
  1. Per-organization bot credential row. Each connected bot stores a per-row webhook secret; a match resolves the owning organization and refuses organizations that have been soft-deleted, so inbound into a retired tenant is rejected rather than routed.
  2. Shared demo-bot row. The shared test bot is a single platform entry; a match authorizes the request, and tenant attribution then happens by verified chat id or by the linking token redeemed on /start.
  3. Derived platform secret. A value derived from the platform secret — matching exactly what the platform registration passed to setWebhook — closes the loop between registration and verification without a second Secret.
Delivery receipts use the same first lane: the receipt endpoint resolves the bot by its webhook secret and returns a retryable failure if the lookup itself errors, so a database blip does not masquerade as a forged token. A missing or non-matching secret answers 401, and Orbit distinguishes a genuine mismatch from an infrastructure failure — a probe is logged and dropped; an outage is loud and retryable.

The bot-credential lifecycle

One credential row per bot, kept for audit:
  • Connect validates the token with a getMe call, stores the token encrypted, registers the webhook with its per-row secret, and marks the row active. A bot id can belong to exactly one organization at a time.
  • Disconnect soft-retires the row (revoking the webhook) rather than deleting it — history of who owned a bot is preserved, and lookup lanes short-circuit retired rows cheaply.
  • Reconnect reactivates a retired row in place, so re-pasting a bot token — by the same organization or a new one claiming a released bot — succeeds instead of colliding with the archived row.
Numeric Telegram bot ids exceed 32-bit range; they are handled as 64-bit values end-to-end so routing and ownership checks do not lose precision at the HTTP boundary.

Media semantics: a URL the provider fetches

Telegram media travels by URL, not upload. A photo, video, or document send carries mediaUrl (see the Telegram channel page) and Orbit hands that URL to Telegram’s Bot API, whose servers fetch the file — there is no multipart upload stage on the Orbit side. That makes Telegram a preflight-style media channel in the media planes taxonomy: the link is declared on the send and resolved by the provider, rather than attached to a stored object first. Practical consequences to design for:
  • The URL must be reachable from Telegram’s fetchers. A presigned Media Library URL works; a private object store path does not. The media library issues signed URLs for exactly this kind of provider-fetch handoff.
  • Stored vs preflight is your choice. Use a stored asset when the same file serves many sends or must survive sourcing changes; use a preflight URL when the asset is generated per send.
  • Bot API size and type limits apply at Telegram’s side. A rejected media URL lands as a provider-level delivery failure and advances any configured fallback chain like any other failure.

Sender, termination, and fallback — Telegram in the shared routing machinery

Telegram plugs into the same routing planes the carrier channels use; what differs is only what the “sender” is.
  • Sender resolution. On the carrier channels, sender resolution picks a phone number, sender ID, or pool. On Telegram, the sender is the bot itself — dispatch resolves to your connected bot or the shared demo bot (the two-section gate above), and Telegram is exempt from the from/pool/service chain entirely. In the unified-termination vocabulary, Telegram is one member of the closed channel enum — a deliver hop targets telegram, and your bot’s identity goes with it (see unified termination routing).
  • Unified termination. Because telegram is a first-class termination channel, a termination rule can absorb an SMPP or REST submit onto Telegram the same way it can onto WhatsApp — one recipient the customer gave you a Telegram handle for gets the message on Telegram while everyone else stays on SMS, and the single billed egress semantics of the terminator hold unchanged.
  • Cross-channel fallback toward SMS. In an org-level cross-channel fallback chain, Telegram can be a hop in either direction — most commonly Telegram → SMS: a send that fails on Telegram (user blocked the bot, chat unreachable, media URL rejected) advances to SMS under the same re-checks every hop runs (consent/TCPA re-verification, fresh sender resolution on the new channel, per-hop billing). The inverse — SMS-failing-onto-Telegram — delivers onto your bot when the recipient is a linked Telegram contact.
  • The free-channel DLR note. Telegram carries no per-message DLR gating plane on the no-receipt transition sweep (see the DLR pipeline) — a Telegram row at sent is the platform-imputed terminal state, exactly like the other zero-price channels.

Opt-in enablement, reachability, and compliance posture

All Telegram controls below are tenant-owned: you enable the channel, you own the bot, you declare your audience.
  • Opt-in by construction. No tenant gets Telegram “for free” — the channel is inert until you either connect a bot or verify a demo chat, and production traffic additionally requires the send-permitted gate to pass. There is no ambient shared asset that can send to your contacts; the demo bot reaches only chats it explicitly verified against a token you minted.
  • Reachability is contact-derived. Telegram reachability comes from the recipient messaging your bot first (or redeeming your /start-deep-link verification). Telegram is not a numbering plan: there is no tenant-owned MSISDN database to blast onto bots; every reachable chat is a contact who opted to talk to your bot. That makes the channel inherently inbound-sided — ship it as a contact and support surface first, and only consider business-initiated blasts for audiences that already chose you.
  • Region notes. Telegram availability is a fact of the destination market (a handful of countries block or throttle it; reachability is defined by the contact’s client, not a carrier). Where Telegram reaches, it is free — no per-message carrier rate — which is why most fallback chains place it before the paid SMS hop for Telegram-first audiences, and after it for SMS-first ones.
  • Compliance surface. The channel-level consent and suppression rules apply unchanged: STOP-family keywords and suppression lists honor a Telegram chat the same way they honor a phone number, and a contact blocked on Telegram can still be reached via fallback on a consented channel. Nothing on this page adds a legal gate the carrier channels do not already carry — the differences are which asset owns identity and who the recipient had to talk to first.

Worked example: a customer message arrives

  1. A customer opens your bot (your connected one, or the shared demo bot after verification) and sends “Where is my invoice?”
  2. Telegram pushes the update to the platform webhook with the X-Telegram-Bot-Api-Secret-Token header; the credentials repository resolves it on the per-organization lane (own bot) or the shared lane plus verified chat id (demo), and refuses anything that does not match — 401, logged, dropped.
  3. The inbound webhook handler maps the update into the common inbound envelope: direction: inbound, channel: telegram, the chat id joining the message to its contact via telegram_chats.
  4. The envelope enters the same conversations pipeline the other channels use: the contact’s open conversation gains the message, or a new conversation opens. Your inbox rules, AI-agent session, and auto-assignment do exactly what they do for SMS or WhatsApp arrivals — the conversation now shows channels: ["telegram"] and replies default to the bot.
  5. Your operator (or AI agent) replies from the inbox; the send resolves the dispatch gate — own bot or demo — and dispatches through the Bot API. If the reply fails on Telegram (blocked chat, unreachable user), the org’s fallback chain can advance to SMS — re-checked, re-priced, and recorded on the same message thread.
Nothing in steps 2–5 is Telegram-specific except the secret-header resolution at the door and the URL-based media attachment semantics above; the whole downstream lifecycle is the shared one.

Cost model: ledger rows on a free channel

Telegram’s Bot API is free at the provider level. Orbit still writes a $0 cost row on every Telegram send, marked free for production traffic and test for demo-bot traffic:
  • Analytics stay complete. The per-channel cost rollup aggregates message prices by channel; without an explicit row, Telegram volume would vanish from the chart and skew per-message-cost denominators low.
  • Demo traffic is metered. Demo-tier rows let the platform account for the shared asset’s operating cost, the same reason the WhatsApp demo tier is metered.
  • A wiring point for paid features. When a paid Telegram surface such as Telegram Stars lands, the breakdown gains a unit-priced tier while the ledger shape, the aggregation, and the dashboard donut keep working unchanged.
The rollup endpoint GET /api/v1/telegram/analytics/costs splits demo vs free volume over a window and returns the totals with a free-tier disclaimer, so a future paid tier becomes a column in the same shape rather than a new integration.

Cross-references