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

# Registration-gated video webinars

> Turn a scheduled video room into a registration-gated webinar — open the form, require approval, cap attendees, add custom questions, then review registrants, approve or decline them, check people in, and read the registered-vs-attended report.

# Registration-gated video webinars

A webinar on Orbit is a scheduled video room with a registration gate in front
of it. Instead of handing every viewer the same open guest link, you publish a
registration form, each attendee that signs up receives their own join token,
and only the people you approve hold a working seat.

You flip the gate on per room, and Orbit tracks the whole attendee lifecycle on
that room: who registered, who you approved, declined, waitlisted, or blocked,
who actually checked in on the day, and how the final numbers compare.

## What the gate gives you

1. **Pick the room.** Registration belongs to a single scheduled video room —
   choose which of your rooms runs as a webinar.
2. **Open the gate.** Enable registration to start accepting sign-ups; close it
   any time to freeze the list while keeping existing registrants.
3. **Configure the form.** Require approval, cap the number of approved
   attendees, and attach up to 20 custom questions (label, optional/required).
4. **Review registrants.** Everyone who signs up lands in the room's list with
   their name, email, answers, and a per-person join token.
5. **Decide each status.** Approve, decline, waitlist, or block one registrant
   at a time. Approving over the capacity ceiling is refused, so move someone
   to the waitlist or raise the cap first.
6. **Check attendees in.** Mark approved registrants as attended when they
   show up — on the day, or as a post-event tally.
7. **Read the report.** Registered vs approved vs attended vs no-shows per
   room, so "how many of my 500 sign-ups actually showed" is one panel, not a
   spreadsheet.

One thing the gate does **not** do: it never places an outbound call or
message. The whole surface is configuration and attendee management — you
distribute each registrant's join token through your own channels (your site,
an email tool, or a direct request to the registrant with its unique token).

## In the dashboard

Open **Voice → Video → Webinars**, pick a room from the selector, and the panel
splits into:

* **Report** — registered, approved (against your capacity), attended with the
  attendance rate, pending, and no-shows.
* **Registration** tab — the form settings (open/close, approval toggle,
  capacity, custom questions) and the registrant list with per-row decisions,
  check-in, and join-token copy.
* **Simulive** tab — optional pre-recorded-as-live playout for the same room.

Add a registrant manually from the **Add attendee** button — handy when a VIP
emails you instead of filling in the form. If approval is required they land
as **pending**; otherwise they are approved, or waitlisted when the capacity
is full.

### A registrant's lifecycle

```text theme={null}
register → pending     (when approval is required)
register → approved    (no approval needed, capacity slot free)
register → waitlisted  (no approval needed, capacity already full)

pending / waitlisted / declined → approved   (organizer approves)
approved → declined / blocked / waitlisted   (organizer re-decides)
approved → attended (check-in)
```

Every decision stamps `decided_at` and `decided_by`, and only an **approved**
registrant can be checked in — repeat check-ins are idempotent and keep the
first timestamp. Removing a registrant invalidates their join token
immediately.

## From the API

The Webinars panel sits on the Video API surface, under the scheduled-rooms
prefix. Reads need the `video:read` scope; writes need `video:write` plus an
owner, admin, or developer role (the same roles the dashboard panel grants).

| Action                                   | Endpoint                                                                    |
| ---------------------------------------- | --------------------------------------------------------------------------- |
| Read the config, registrants, and report | `GET /api/v1/video/rooms-scheduled/:id/registration`                        |
| Open/close the gate or tune the form     | `PUT /api/v1/video/rooms-scheduled/:id/registration`                        |
| Register an attendee                     | `POST /api/v1/video/rooms-scheduled/:id/registrants`                        |
| Approve / decline / block / waitlist     | `PATCH /api/v1/video/rooms-scheduled/:id/registrants/:registrantId`         |
| Check an approved registrant in          | `POST /api/v1/video/rooms-scheduled/:id/registrants/:registrantId/check-in` |
| Remove a registrant                      | `DELETE /api/v1/video/rooms-scheduled/:id/registrants/:registrantId`        |

### Open the gate

```bash theme={null}
curl -X PUT "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/ROOM_ID/registration" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "require_approval": true,
    "capacity": 250,
    "custom_fields": [
      { "key": "company", "label": "Company", "required": true },
      { "key": "role", "label": "What do you build?", "required": false }
    ]
  }'
```

All four fields are optional — send only what changed. Custom field keys must
be URL-safe (letters, digits, `-`, and `_`). Closing the gate later is
`{"enabled": false}` — registrants already on the list keep their tokens.

### Register an attendee

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/ROOM_ID/registrants" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jordan Rivera",
    "email": "jordan@example.com",
    "custom_answers": { "company": "Acme", "role": "Support tooling" }
  }'
```

A `201` returns the registrant record — including its unique `join_token`,
which is the value you hand to that attendee. Re-registering the same email
returns `409`; skipping a required question returns `400` naming the question;
a closed gate returns `409`. Your own public sign-up form should post through
your back end or an API key holding the `video:write` scope.

### Read the gate (and the report)

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/ROOM_ID/registration" \
  -H "X-API-Key: dv_live_sk_..."
```

```json theme={null}
{
  "room_id": "ROOM_ID",
  "room_status": "scheduled",
  "config": {
    "enabled": true,
    "require_approval": true,
    "capacity": 250,
    "custom_fields": [{ "key": "company", "label": "Company", "required": true }]
  },
  "registrants": [
    {
      "id": "vreg_d4f891a9b0c046d1b89a2e9157c00051",
      "name": "Jordan Rivera",
      "email": "jordan@example.com",
      "custom_answers": { "company": "Acme" },
      "status": "approved",
      "join_token": "jt_9f0a1c2e8b47d5a6c2e18fbc0a71e4d9",
      "registered_at": "2026-08-24T14:03:11Z",
      "decided_at": "2026-08-24T16:20:01Z",
      "decided_by": "usr_8f4c21",
      "attended": true,
      "attended_at": "2026-08-26T09:58:43Z"
    }
  ],
  "report": {
    "total": 137,
    "by_status": { "pending": 6, "approved": 120, "declined": 4, "blocked": 1, "waitlisted": 6 },
    "attended": 88,
    "no_shows": 32,
    "attendance_rate": 0.73,
    "capacity": 250,
    "capacity_remaining": 130
  }
}
```

`attendance_rate` is attended ÷ approved on a 0–1 scale; `no_shows` counts
approved registrants who never checked in; `capacity_remaining` is `null` when
the room is uncapped.

### Decide, check in, remove

```bash theme={null}
curl -X PATCH "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/ROOM_ID/registrants/vreg_d4f891a9b0c046d1b89a2e9157c00051" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "decision": "approve" }'
```

Approve over the capacity ceiling and you get `409` telling you to waitlist or
raise the cap first. Check-in takes an empty body; a repeat check-in returns
the registrant unchanged. `DELETE` on the same path removes the registrant and
kills their token.

## Webinar or plain room?

Reach for a **plain video room** when a meeting is fine with an open guest link
and you don't need to pre-screen attendees — see [Video](/channels/video) for
the room layer itself (creation, join, recording, broadcast) and the
[Video meetings guide](/guides/video-meetings) for untracked get-togethers.

Reach for a **webinar gate** when the event has a lineup — paid training,
product-launch broadcasts, customer academies — and you care who holds a seat:
approval queues, capacity, check-in, and the attended-vs-registered audit. The
gate layers on top of a normal scheduled room, and every room-level feature
(recording, the lobby, moderators, grants) keeps working underneath it.
