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

# Build a customer-facing video support surface

> End-to-end walkthrough for a customer support video surface: choose the room primitive, create and configure the room, issue host and guest links, embed the room in React, review the session in History, and escalate from voice.

# Build a customer-facing video support surface

This guide walks the full path from deciding which video primitive fits your support flow to reviewing the completed session in History. It is intended for operators building a customer-facing surface: an on-site "video call us" button, an agent sharing a guest link, or a voice call that needs a video leg.

By the end you will know how to:

1. Choose between a hosted meeting and an embedded room.
2. Create and configure a room from a template.
3. Issue tokens and guest links with the right record permission.
4. Embed the room with a minimal React component.
5. Read the session back in History, including recording and transcript.
6. Escalate to video from a live voice call when needed.

## 1. Pick the right video primitive

Orbit offers two room shapes for support video:

| Primitive | Best for | Entry point |
| - | - | - |
| **Hosted meeting** | Scheduled appointments, multi-party sessions, recurring standups | Share a guest link; the guest opens it on `orbit.devotel.io` |
| **Embedded room** | In-app or on-site support — the customer never leaves your page | Mount `OrbitVideoRoom` inside your React app with a server-minted token |

Choose a **hosted meeting** when the customer can join through a calendar invite or a support ticket link and you do not need to own the surrounding UI. The guest link opens a complete room on Orbit, including the tile grid, control bar, and device picker.

Choose an **embedded room** when the video call is part of your own product surface — for example, a "video call us" button on your help page or a video pane inside your dashboard. You keep the customer in your page and Orbit supplies only the media layer.

The rest of this guide uses the embedded pattern as its main example, because it touches every step: room creation, token issuance, a React mount, history read-back, and voice escalation.

## 2. Create the room from a template

A template keeps every support room consistent. Create a template once, then instantiate rooms from it so each session starts with the same cap, recording, waiting-room, and retention settings.

Use a template that matches a support consultation:

* **Max participants:** 2 to 8. Two seats cover the customer and agent; a few extra seats leave room for a supervisor or warm transfer.
* **Max duration:** 45 to 60 minutes. A hard stop prevents abandoned rooms from running indefinitely.
* **Waiting room:** on. The customer waits in receive-only mode until an agent admits them.
* **Recording:** on, with the consent behavior your jurisdiction requires.
* **Virtual backgrounds / noise suppression:** allowed or off per your brand preference.

See [Video room templates](/guides/video-room-templates) for the full template lifecycle. To create the template from the API:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/video/room-templates" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Support consultation",
    "description": "1:1 customer support video, waiting room on, 60 min cap",
    "config": {
      "max_participants": 4,
      "max_duration_minutes": 60,
      "waiting_room": true,
      "recording_enabled": true
    }
  }'
```

Then instantiate a room from the template when a support session is needed:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/video/room-templates/tpl_x4k9/instantiate" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Support — Jordan, Oct 5" }'
```

The response returns the `room_id` and a `host_token` for the agent who creates the room.

<Note>
  You can also create a room directly with `POST /api/v1/video/rooms-scheduled`. Templates are the better choice when you want every support session to open with the same configuration.
</Note>

## 3. Issue tokens and a guest link

Two credentials are usually needed for a support session:

* A **host token** for the agent, minted with `participant_tier: host`.
* A **guest invite link** for the customer, created from the room with the right permission bounds.

### Agent token

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/rm_9f2/join" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "identity": "agent-jordan",
    "display_name": "Jordan (Support)",
    "participant_tier": "host"
  }'
```

The response contains `token` and `livekit_url` (or the `ws_url` alias). The agent's client connects with these. The host tier lets the agent admit customers from the waiting room, mute participants, and end the room.

### Guest link

For the customer, mint an invite link instead of handing over a raw token. The link redeems to a token when opened, so the customer never sees a JWT.

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/rm_9f2/invites" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "expires_in_seconds": 3600,
    "max_uses": 1,
    "allow_recording": true
  }'
```

The response contains an `invite_token`. Build the guest URL using your dashboard locale and the token:

```text theme={null}
https://orbit.devotel.io/en/join/inv_tk_8d4f1a9c2b7e
```

Set `allow_recording` consistently with your room's recording setting and your jurisdiction's consent rules. If the customer must opt in to recording, leave it off and surface the consent prompt before sharing the link.

<Note>
  A guest link with `max_uses: 1` is single-use. If a customer drops and rejoins, mint a fresh link. For a persistent support queue, create one long-lived room and issue a new invite per customer.
</Note>

## 4. Embed the room in React

The Web SDK's `OrbitVideoRoom` widget mounts the SDK-generated surface — tile grid, control bar, device picker, and leave action — into any container in your React app.

Your backend must mint the token; your frontend only receives the short-lived token and the media URL.

```tsx theme={null}
import { useEffect, useRef, useState } from "react";
import { OrbitVideoRoom } from "@devotel-orbit/web";

export function SupportVideoRoom({
  roomId,
  displayName,
}: {
  roomId: string;
  displayName: string;
}) {
  const containerRef = useRef<HTMLDivElement>(null);
  const roomRef = useRef<ReturnType<typeof OrbitVideoRoom.init> | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (!containerRef.current) return;

    fetch("/api/support/video-token", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ roomId, displayName }),
    })
      .then((r) => {
        if (!r.ok) throw new Error("Could not start video session");
        return r.json();
      })
      .then(({ token, serverUrl }) => {
        roomRef.current = OrbitVideoRoom.init({
          container: containerRef.current!,
          token,
          serverUrl,
          displayName,
        });

        roomRef.current.on("error", (err: Error) => setError(err.message));
      })
      .catch((err) => setError(err.message));

    return () => {
      roomRef.current?.leave();
      roomRef.current = null;
    };
  }, [roomId, displayName]);

  if (error) return <p className="text-red-600">{error}</p>;

  return (
    <div className="h-[600px] w-full rounded-lg border">
      <div ref={containerRef} className="h-full w-full" />
    </div>
  );
}
```

Backend handler outline (Node.js):

```ts theme={null}
import { Orbit } from "@devotel-orbit/node";

const orbit = new Orbit({ apiKey: process.env.ORBIT_API_KEY });

app.post("/api/support/video-token", async (req, res) => {
  const { roomId, displayName } = req.body;

  const result = await orbit.request(
    "POST",
    `/video/rooms-scheduled/${roomId}/join`,
    {
      identity: `customer-${req.user.id}`,
      display_name: displayName,
      participant_tier: "panelist",
    }
  );

  res.json({
    token: result.data.token,
    serverUrl: result.data.livekit_url ?? result.data.ws_url,
  });
});
```

### Waiting-room UX

If the room has the waiting room armed, the customer's first join lands in a receive-only lobby. Listen for the effective grant or render a simple "waiting for an agent" overlay until the host admits them. The agent admits from the dashboard moderation panel or by calling `POST /video/rooms-scheduled/{id}/waiting-room/admit/{identity}`.

## 5. Read the session back in History

After the room ends, the session appears on the **Voice → Video → History** page. The same rows are available through `GET /api/v1/video/rooms-scheduled?status=ended`.

### What each row shows

* **End-reason badge** — how the session closed: clean finish, operator end, idle timeout, or error.
* **Duration badge** — session length when at least one participant joined.
* **Recording badge** — present when recording was enabled.
* **Recording health** — **Recording missing** or **Recording degraded** when the post-recording check found the artifact unusable.
* **Ended timestamp** — wall-clock end time in your workspace timezone.

### Recording and transcript

Open a recorded row to play the recording. The playback link refreshes automatically, so a long review session does not expire.

Below the recording, the transcript panel shows the conversation captured during the session. Search it to find what was said, and purge it there if your data policy requires removal.

### Retention

Ended sessions are retained for **90 days** by default, followed by a two-week grace window before hard deletion. Override the window with the `video_room_retention_days` organization setting (minimum 7 days). See [Video room history](/guides/video-room-history) and the [video room model](/concepts/video-room-model) for the full lifecycle.

## 6. Escalate to video from a live voice call

When a voice call needs a screen share or face-to-face step, escalate it to a video room without asking the customer to hang up.

From the [Browser Softphone](/guides/voice-browser-softphone), the agent taps **Move to video**, or your integration calls:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/call-escalation/video" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "callSid": "CA4f2c1d3e8a7b9c0d1e2f3a4b5c6d7e8f",
    "remotePartyNumber": "+15559876543",
    "remotePartyName": "Jordan Weiss",
    "notifyRemoteParty": true,
    "sipCredentialId": "sip_01h2k3m4n5p6"
  }'
```

The response returns:

* `host` — the agent's token and media URL.
* `guest` — the customer's invite link and expiry.
* `remote_party_notification` — whether the SMS delivery succeeded.

The customer taps the link and joins as a guest. Once both sides are in the room, the client hangs up the voice leg. At the end of the meeting, the agent ends the room. The originating `callSid` is stamped on the room, so the voice call and the video session stay linked in History and analytics.

See [Escalate a live voice call into a video room](/guides/voice-video-escalation) for the full request contract and cleanup steps.

## Production checklist

* [ ] The support room is created from a template with consistent recording, waiting-room, and duration settings.
* [ ] The agent joins as `host`; the customer uses a guest link or a panelist token minted server-side.
* [ ] The API key stays on the backend; the browser receives only the short-lived token and media URL.
* [ ] Recording permission on the guest link matches your consent flow and jurisdictional requirements.
* [ ] The waiting-room UX renders "waiting for an agent" when the customer is lobby-downgraded.
* [ ] Sessions are reviewed in History before the retention window passes.
* [ ] Voice escalations stamp the `callSid` on the room and end the room explicitly after use.

## Related guides

* [Video meetings and conferences](/guides/video-meetings) — scheduled rooms and webinars.
* [Video room access tokens](/guides/video-room-access-tokens) — token grants, tiers, and expiry.
* [Embed a video consultation button](/guides/embed-video-consultation-button) — the drop-in widget pattern.
* [Video room history](/guides/video-room-history) — finding and reading ended sessions.
* [Escalate a live voice call into a video room](/guides/voice-video-escalation) — voice-to-video handoff.
* [Video room templates](/guides/video-room-templates) — saving and instantiating room configurations.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.