> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# The channel test-account model: shared test to BYO

> How every interactive channel on Devotel Orbit runs the same tester-to-producer ladder — a shared test account bound to one verified recipient, then your own channel asset — and the two gate classes that decide what a test-account organization can and cannot do.

# 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](/concepts/provisioning-and-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](https://t.me/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:

| Mode             | Meaning                                               | What you can do                                                                                                          |
| ---------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `not_connected`  | No test verification, no BYO asset.                   | Read-only. Send paths reject with a 409 telling you to connect or verify.                                                |
| `test_verifying` | A linking token has been minted but not yet redeemed. | Resume the deep link (the state response carries the pending token) — a page reload picks the flow up where it left off. |
| `test_active`    | One chat is verified against the shared bot.          | Send curated demo content to the verified chat, bounded by a daily cap.                                                  |
| `production`     | A BYO bot is connected.                               | The full channel capability surface, under your own brand.                                                               |

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.
* **Disconnect** — `POST /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

* [Telegram channel](/channels/telegram) — message shapes, keyboards, token handling, ownership disputes.
* [WhatsApp channel](/channels/whatsapp) — templates, flows, catalog, Embedded Signup.
* [Telegram onboarding guide](/guides/telegram-onboarding) — the ordered how-to path from empty channel to production bot.
* [Sandbox, test mode, and the provisioning model](/concepts/provisioning-and-test-mode) — the orthogonal `dv_test_sk_*` simulation toggle, and the `TENANT_PROVISIONING` 503.
* [Verification lifecycle](/concepts/verification-lifecycle) — how approval cycles on regulated channels progress.
