> ## 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.

# Add a 'video call us' button to your website

> Embed the prebuilt OrbitVideoRoom widget as a drop-in video-consultation button on any page — mint the join token on your backend, run the session through a waiting room with host admit, and keep recordings within a tenant-controlled retention window.

# Add a "video call us" button to your website

The fastest path from *visitor on your site* to *face-to-face video session* is
the **`OrbitVideoRoom`** prebuilt widget — the drop-in, embeddable video-call UI
in the [Web SDK](/sdks/web). You ship one script tag plus three lines of
JavaScript, and the widget paints a complete conferencing surface with nothing
else to build:

* a participant tile grid and local self-view
* a control bar with mute, camera, screen-share, a device picker
  (microphone / camera / speaker, switchable mid-call), background blur, and
  leave
* automatic attach of remote tracks as they are subscribed

This guide is the end-to-end walkthrough of the pattern operators run as a
"video call us" or "video consultation" button: the visitor's browser loads the
widget, your backend hands it a one-shot join token, the join lands in an
optional **waiting room** until an agent (host) admits them, and the session
recording lives on **your storage** under a **tenant-controlled retention
window**.

## The four moving parts

|              | Where                    | What it does                                                                      |
| ------------ | ------------------------ | --------------------------------------------------------------------------------- |
| Widget       | Visitor's browser        | `OrbitVideoRoom.init(...)` paints the UI and joins the media room                 |
| Token mint   | **Your backend**         | `POST /api/v1/video/rooms/:id/join` returns `{ token, livekit_url }`              |
| Waiting room | Room settings            | `waiting_room: true` forces a new join to receive-only until the host admits them |
| Recordings   | Your storage + retention | `recording_enabled` + `organizations.settings.video_room_retention_days`          |

## 1 — Embed the button on your site

The widget is host-agnostic — it renders into whatever container element you
pass. Mount it behind a click so the media session only starts when the
visitor actually asks for it.

```html theme={null}
<button id="orbit-video-call-us">Video call us</button>
<div id="orbit-video" style="height: 600px" hidden></div>

<script type="module">
  import { OrbitVideoRoom } from "@devotel/orbit-web-sdk";

  document.getElementById("orbit-video-call-us").addEventListener("click", async () => {
    // 1. Ask YOUR backend to mint a join token (step 2).
    const { token, serverUrl } = await fetch("/your-backend/orbit-video-token", {
      method: "POST",
    }).then((r) => r.json());

    // 2. Show the stage and drop in the prebuilt widget.
    document.getElementById("orbit-video").hidden = false;
    const room = OrbitVideoRoom.init({
      container: document.getElementById("orbit-video"),
      token,
      serverUrl,
      displayName: "Website visitor",
    });
    room.on("state", (s) => console.log("video room:", s));
  });
</script>
```

The full config surface — `startWithCamera`, `startWithMicrophone`,
`enableScreenShare`, `enableDeviceSelection`, `autoJoin: false`, the
`participantJoined` / `participantLeft` / `error` event map, and the
`join()` / `leave()` lifecycle — is on the [Web SDK page](/sdks/web) under
*Prebuilt video room*.

## 2 — Mint the join token on your backend (never in the browser)

Your **API key must never reach the visitor's browser**. The token comes from
the same route the [video-meetings guide](/guides/video-meetings) uses —
`POST /api/v1/video/rooms/:id/join` — issued by your backend, which then hands
only the short-lived token and the media-server URL to the page.

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms/<room_id>/join" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"display_name": "Website visitor"}'
```

The response carries `token` (the media-server access JWT) and `livekit_url`
(the `orbit-media` WebSocket URL — point the widget at this host, not at the
retired `livekit.cloud`). Because your backend mints it per click, each
visitor gets a fresh, scoped token and your long-lived API key stays
server-side.

For a consultation you usually want **one persistent room** per queue or
department ("the sales room", "the support room") rather than a new room per
visitor: create it once from the dashboard (**Rooms → New room**) or the
[video rooms API](/api-reference/video), then have your backend mint join
tokens against that same room ID.

## 3 — Put a waiting room in front of your agents

A consultation button that rings agents blindly is a poor experience. Arm the
room's **waiting room** so a new join holds in a receive-only lobby until a
host admit — the same hold-and-admit flow the video-meetings surface uses for
drop-in guests.

The join route documents this in its response contract: the `participant_tier`
and `permissions` fields are the **effective grant after any waiting-room
clamp**, and `lobby_downgraded` is `true` when an armed waiting room forced
this join to receive-only `viewer`. Your cue is simple:

* `permissions.can_publish === false && permissions.can_subscribe === false`
  → the visitor is in the lobby; render "waiting for the team to admit you"
* on admit, the host promotes them from the roster and their publish grant
  activates

From the dashboard, an agent opens the same room, sees the lobby in the host
moderation panel, and admits the visitor — so the agent surface needs nothing
new.

## 4 — Record the session and set its retention

Consultation sessions are usually recorded for quality or compliance. Enable
`recording_enabled` on the room (dashboard settings or the rooms API) and the
session records to your tenant's configured storage, with signed download
URLs — the same per-tenant storage posture the rest of the recording stack
uses.

Retention is a **tenant-config window**, not a platform lock-up. The hourly
retention sweep reads `organizations.settings.video_room_retention_days`:

* **Default — 90 days.** Sessions whose `ended_at` is older than the window
  are soft-deleted; after a further **14-day grace** they are hard-deleted.
* **Tenant override** — set `video_room_retention_days` to a positive integer
  (minimum 7 days) to lengthen or shorten the window for your own compliance
  posture, or to lengthen the window for sessions you must keep.

## Why not `livekit.cloud`?

Everything on this page runs on Orbit's **self-hosted media stack**
(`orbit-media`). The token's `livekit_url` is an `orbit-media` host — that
field name is inherited from the upstream fork; the actual media path is
Orbit's own, which is why `livekit.cloud` (retired 2026-05-19) never appears
here. Self-hosting the media path is also why the embed adds **no outbound
voice/SMS provider** — the consultation is inbound WebRTC video end to end.

## Production checklist

* [ ] The join token is minted **server-side**; the API key is never in the page.
* [ ] `serverUrl` points at the `livekit_url` from the join response (an `orbit-media` host).
* [ ] The button only mounts the widget **on click**, so idle page loads cost nothing.
* [ ] `waiting_room: true` on the room, and your agents admit from the host moderation panel.
* [ ] `recording_enabled` is set for sessions you must keep.
* [ ] `video_room_retention_days` matches your compliance window (or you accept the 90-day default).
* [ ] You re-mint the token before `expires_at` if a session can run long.
