Skip to main content

The channel test-account model: shared test to BYO

Every interactive messaging channel on Orbit runs the same onboarding ladder: you send real traffic today through a shared test account that Orbit owns, prove your integration end to end, and then connect your own channel asset (a bot, a WhatsApp Business account, an RCS agent) to go to production. Telegram and WhatsApp ship this ladder as identical architecture — same per-channel state machine, same two gate classes, same capability contract — so once you understand one channel, you understand every channel that follows this model. This page defines the shared model; the channel pages carry the per-channel detail. The ladder is distinct from dv_test_sk_* test mode: test mode decides whether a send is simulated (persisted, never dispatched, no provider reached), while the test account is a real delivery path — messages actually reach the provider under Devotel’s shared channel asset. Both exist so you can evaluate in the way that fits: simulation for integration shape, real delivery for end-to-end proof.

The tester-to-producer ladder, and why each channel’s step differs

The graduation step differs per channel for one reason: what it takes to own a production asset upstream.
  • Telegram is unregulated. The production asset is a BotFather bot, minted on demand in a chat with @BotFather. Graduation is pasting the token BotFather hands you into POST /api/v1/telegram/connect-bot; Orbit validates the token with a getMe call and registers your webhook subscription immediately. There is no approval queue, no brand review, no waiting period — the token either resolves to a bot or it does not.
  • WhatsApp and RCS are regulated. The production asset is a Meta-approved WhatsApp Business Account (WABA) or a Google-approved RCS agent. Graduation goes through an approval cycle — Meta’s template and commerce policies on WhatsApp, Google’s agent verification on RCS — and takes hours to days. The shared test account exists precisely so that approval time is not dead integration time: you build and validate against the shared WABA while your own is pending.
Both ladders terminate in the same place: your own asset connected, the same API surface, your own branding and full capability.

The three-mode state machine

Your organization sits in exactly one mode per channel at any time. Telegram makes the modes explicit: The transitions, and who advances each:
  • Start verification — you call POST /api/v1/telegram/test/start-verify, which mints a single-use linking token (five-minute TTL) and returns a deep link (https://t.me/<shared_bot>?start=verify_<token>) plus the same value to render as a QR code. Opening the link in Telegram and pressing Start redeems the token; Orbit’s inbound webhook binds that chat to your organization and advances the mode to test_active.
  • Paste a token — you call POST /api/v1/telegram/connect-bot with the BotFather token. The token is validated against getMe, encrypted, and the webhook subscription registered. The mode becomes production. Production wins over test: when both exist, the state machine reports production and the test block drops out of the response.
  • DisconnectPOST /api/v1/telegram/test/disconnect clears the verified-chat link (back to not_connected), and POST /api/v1/telegram/disconnect-bot revokes the webhook registration and retires the BYO row.
Updates to Telegram are in-flight carrier events after your calls above; the mode only advances once the webhook redeems the token or the token validates. The parallel WhatsApp counterpart runs the identical shape with a different asset. Its send gate answers the same question — “is this organization allowed to push WhatsApp at all?” — for the same reason: either the organization owns a connected WABA, or its test account is verified against the shared Devotel WABA. The verification mechanism differs (a verified phone number rather than a deep-linked chat) and graduation goes through Meta Embedded Signup, but the state machine, the daily cap, and the gate classes below are structurally the same.

The two gate classes

Every capability on these channels falls into one of two gate classes. This is the part of the model your code encounters as 403s.

Send-permitted — reachable from both test and production

The send gate permits the call when either the organization owns a connected asset or the organization has an active test account. It answers one question: is this organization allowed to push traffic on this channel at all? Dashboards and preflight checks use it so they fan out without duplicating the test-mode allowance logic. In test_active, per-send enforcement still applies underneath: sends go only to the verified chat, against the curated demo allowlist, bounded by a daily cap of 100 test sends per UTC day. Telegram and WhatsApp implement the send gate independently, but the contract — own asset or verified test account, both permitted — is identical.

Asset-owner-only — production only

The ownership gate rejects with 403 FORBIDDEN unless the organization owns the channel asset. Telegram groups these Bot API features in it, because each one mutates the bot as an asset or operates the bot’s economy:
  • setMyCommands beyond the curated demo command set — the shared bot carries a demo command list you can exercise; redefining it is owner-only.
  • setChatPhoto, setChatTitle, setChatDescription — chat identity mutations.
  • Payments: sendInvoice, answerPreCheckoutQuery, answerShippingQuery.
  • Business connections (getBusinessConnection and the features bound to one).
  • Sticker-pack management (createNewStickerSet, addStickerToSet, setStickerSetThumb).
  • getMe / setMyName mutations — anything that changes the bot identity itself.
On WhatsApp the same gate class covers WABA-bound assets: template create/update/delete and submission for approval, flow publish, catalog product CRUD, and WABA-bound calling. Meta ties asset ownership to the WABA owner, so a shared WABA cannot be the asset-owner-of-record for a tenant. You meet the ownership gate as a 403 FORBIDDEN with a connect-your-own message — on Telegram “Connect your own Telegram bot to use this feature”, on WhatsApp “Connect your WhatsApp Business account to use this feature”. Both are permanent, deterministic responses: connect the asset and the same call passes.

The shared-asset identity lock, and the $0 ledger rows

Two consequences of running on an asset Orbit owns: Identity mutations are a 403 even for an active test organization. The shared bot has one identity — its name, photo, command list, and sticker economy — serving every evaluating tenant. If test_active organizations could rename it or redefine its commands, one tenant’s experiment would be every tenant’s bot. So the ownership gate does not ask “are you allowed to send?” (the send gate does) but “do you own this asset?” — and on a test_active organization the answer is always no. Connect your own bot and the same calls pass. Zero-cost rows exist on purpose. Telegram’s Bot API is free at the provider level, so every Telegram cost-ledger row is $0.00 — but the row is written anyway, marked tier: "test" when the send rode the shared bot. Three reasons:
  • Cost analytics stay complete. The per-channel cost rollup aggregates message prices by channel; without an explicit $0 row, Telegram volume would vanish from the chart and skew per-message-cost denominators.
  • Dev capacity is measurable. Test-tier rows let the platform account for the shared asset’s operating cost, the same reason the WhatsApp test tier is metered.
  • Future paid features have a wiring point. When a paid Telegram surface lands (Telegram Stars), the tiers and consumers already exist.

What your code branches on

One read, one contract, no code change between test and production:
  • GET /api/v1/<channel>/channel-state is the single source of truth for where your organization sits on the ladder. On Telegram it reports mode (not_connected, verifying, test_active, production), the shared bot username, the daily test-send counter and limit, the verified chat when test-linked, and the pending verification token while verifying. The state read is deliberately degrade-safe: if an upstream availability fault hits it, Orbit returns the well-defined not_connected empty state rather than a 5xx, so your dashboard card renders “Connect Telegram” instead of an error.
  • Branch on the returned mode, never on your own record of what you connected. The per-mode capability matrix is exactly the gate table above: test_active gets send-permitted features to the verified chat; production gets both gate classes.
  • The same endpoint routes differently per mode. POST /api/v1/telegram/test/send dispatches: on test_active it sends via the shared bot to the verified chat and counts the daily cap; on production it sends through your own bot. Your integration’s send calls — dashboard or API — are identical before and after graduation. The only thing that changes between test and production is the answers channel-state gives and which gates open.
That same-endpoint contract is the entire point of the model: prove the integration against the shared asset, then graduate by pasting a token — not by changing your code.

Cross-references