Skip to main content

Video

Orbit’s video channel gives you live, in-browser video rooms that run over WebRTC on Orbit Media (Devotel’s hosted SFU). Rooms need nothing installed — a guest joins from a link in any modern browser — and they live on the same account as your SMS, voice, WhatsApp, and email, so a video session lands in the shared inbox alongside the rest of the conversation. This page is the concept-and-reference surface: room kinds, join tokens, the embed, recording, moderation, and broadcast. For the full endpoint parameter and response contract see the Video API reference; for the ordered walkthroughs see Video meetings and conferences, Video room access tokens, and the embedded video consultation button.

When video is the right channel

Reach for video when the conversation needs eye contact or a screen, not as a default channel. Three patterns it fits well:
  • 1:1 agent escalation — a chat or SMS thread hands off to a face-to-face session from the shared inbox, so the agent shares a screen or reads the customer’s demeanor without the customer installing an app.
  • Telehealth and professional consultations — scheduled rooms with a waiting-room lobby, recording consent at join, and per-participant grants (host / panelist / viewer).
  • In-app support — the embedded orbit-video-room element or the JS widget inside your own product, so a customer starts a call without leaving your page.
For broadcast-scale audiences, run the room as a webinar (viewer grants, spotlight, Q&A) and push it to streaming destinations over RTMP — the room stays interactive for panelists while viewers consume the stream.

Webhooks and event ordering

Every room emits granular lifecycle events, so the ordering below is a guarantee you can build on. For a room created with recording_enabled: true, webhooks arrive in this sequence:
  1. video.room.started
  2. video.participant.joined (one per join)
  3. video.participant.left (one per leave)
  4. video.room.ended
  5. video.recording.completed (or video.recording.failed)
Always handle out-of-order delivery defensively — webhook delivery is at-least-once and retries can interleave, so build idempotent receivers and order by event type, not arrival time. When a join does not reach the client the room closes at the created expiry with end_reason: failed_no_connect on video.room.ended. The end_reason field distinguishes last_participant_left, operator_end, timeout, room_finished, and error. Full payload shapes live in the webhook event reference; register receivers per the webhooks overview.

Web SDK: join, publish, and leave

The web SDK’s OrbitVideoRoom joins a room with a container element and the server-minted token — remote tracks attach to the right participant tile as they are subscribed, and the control bar covers mute, camera, screen-share, the device picker, and leave:
Publishing starts on join when start-with-camera / start-with-microphone are set, or when the participant toggles them in the control bar — subject to the grant your join token carries (can_publish). Receiving is automatic: remote tracks attach to their participant tile as they arrive. For the lower-level media client (direct track control beyond the prebuilt widget) see the Web SDK reference; the access tokens guide covers the grant tiers.

Two kinds of rooms

Scheduled rooms are the first-class surface: they carry a host, an optional schedule and recurrence rule, a capacity, recording, guest invite links, and an inbox entry. Ad-hoc rooms are the lighter primitive for one-off “start a call now” flows.

Create and schedule from the API

A scheduled room with a calendar slot, recurrence, recording, and a named host — the same create call in four languages:
The response returns the room plus a host token for the creator, so the organizer joins immediately. Leave scheduled_at unset to start now, pass an RFC 5545 recurrence_rule for a repeating series, and omit max_participants to inherit your tenant’s participant ceiling. The 201 create returns the room handle (room_id) under the data envelope alongside the minted host token — the egress ids and storage descriptor are non-null only when recording started at create. The room itself carries a status ("scheduled" → "live" → "ended") and an expires_at you can read back from the room record; the token above is the response’s terminal shape — a room id plus the handle usable immediately. An ad-hoc room uses the same body on the shorter endpoint — for a click-to-start call inside your product where a persisted schedule record would be noise:
201
Create the ad-hoc room when your user clicks, then mint a join token per participant against the returned room name (next section). The room ends when participants leave; an empty room is reaped promptly, so a stale “start now” room that nobody ever joins does not bill indefinitely.

Limits

Video plans are tenant-level, and every control below is set by the tenant on its own account: GET /api/v1/video/rooms-scheduled/limits returns your resolved participant cap, the platform defaults, and whether an override is in effect — read it before rendering your own create-room form.

Join tokens: the browser never sees your API key

Joining a room is a two-step wire. Your back end mints a bounded, per-participant join token:
The response is self-describing under the standard { data, meta } envelope so a consumer never has to decode the JWT:
Tokens are per-participant: identity, display name, and grant bind at mint time. Re-issuing is a fresh POST every time — mint a new token when expires_at approaches (the default lifetime is 1 hour, override with ttl_seconds in the body) and replace the token in your client before the SFU rejects it. Identity binding is restrictive by design: a token under another user’s identity requires the owner or admin role plus a non-empty impersonation_reason, so guests and teammates cannot mint-as-others from an ordinary API key.

Embed a room with the web SDK

The Orbit web SDK (@devotel-orbit/web) registers a prebuilt orbit-video-room element. All of these attributes are optional except token and server-url: The camera, microphone, screen-share, and device-selection toggles ship with the widget, so an embedded room is a snippet rather than a UI build:
The element emits DOM events you can listen for: orbit-state, orbit-participant-joined, orbit-participant-left, and orbit-error. For the full embed walkthrough see the embedded video consultation button guide.

Recording and playback

Drive recording from the API — /api/v1/video/rooms-scheduled/:id/recording/start, /stop, and /pause — or set recording_enabled: true on create so recording starts automatically when the first participant joins. Completed recordings arrive as composite MP4s and land back on the room, and the returned recording_url is a short-lived signed URL (fetch the room again when it expires) with recording_url_expires_at alongside so you can warn a viewer before the link lapses. Completion (or failure) is delivered as video.recording.completed / video.recording.failed; handle both. To store recordings in your own bucket instead of Orbit storage, see video recording BYO storage. Per-participant recording consent is respected at join time (allow_recording in the join body) and can be changed mid-room by a host via /api/v1/video/rooms-scheduled/:id/participants/:identity/recording-consent; a decliner’s media is excluded from the composite and track egress.

Waiting room: the lobby

Arm the lobby with POST /api/v1/video/rooms-scheduled/:id/waiting-room ({ enabled: true }, optionally auto_promote: true). While the lobby is armed, new non-host joins receive receive-only tokens — the join response reports lobby_downgraded: true and permissions.can_subscribe: false, so your client can show a “waiting for the host to admit you” state instead of broken controls. A host (owner, admin, or developer role) admits a pending participant with POST /api/v1/video/rooms-scheduled/:id/waiting-room/admit/:identity; admission promotes them to the full panelist grant without a new token. With auto_promote: true the hold still applies to the join but the first host to enter lifts everyone automatically — use it for open-office-hours style rooms.

Moderation and broadcast

Hosts moderate from the dashboard or the API: spotlight a speaker, mute or remove a participant, lock the room so no new joins land (/lock), run the waiting-room lobby above, and clear the deck with mute-all. For a larger audience, push the room to a streaming destination with RTMP egress. POST /api/v1/video/rooms-scheduled/:id/egress/rtmp takes:
Provide both URL and stream key in the request body — the response echoes URLs without keys ({ egress_id, rtmp_url, additional_rtmp_urls }) so your UI can display destinations without holding secrets. Keep the egress_id as the handle for DELETE /:id/egress/rtmp/:egressId, which stops the whole fan-out. Additional destinations fan out from a single composite render, so simulcast to YouTube plus Twitch costs one render. Stream keys are never persisted — only the URL and handle land in room settings. To run a room audio-only, set audio_only: true at create: every publisher’s grant is clamped to microphone at token mint, and camera plus screen-share come back for a given participant only once the transcript-verified video-only gate has proof of genuine speech — it widens the two video sources onto an already-publishing grant and can never convert a subscriber into a publisher. The full grant semantics live in Transcript-verified video-only gate. You can also bridge the room to the phone network with dial-in for guests and dial-out to bring a PSTN participant in.

Common failure modes

Run through the full checklist, including token handling and lobby downgrade, in Video room lifecycle troubleshooting; for chat, polls, and RTMP-specific failures see Video features: chat, polls, Q&A, invites, RTMP.

Errors

The feature-level codes — chat, polls, Q&A, invites, moderation, hands, spotlight, whiteboard, RTMP egress, and the SSO-policy gate — have their own runbook: Video room features: chat, polls, Q&A, invites, RTMP.

Room-config templates

Save a room’s configuration as a reusable template under /rooms-scheduled/templates and create later rooms from it with the same behavior as a direct create — the fixed onboarding-call or weekly-sync setup stops being a copy-paste of settings.