Documentation Index
Fetch the complete documentation index at: https://orbit-docs.devotel.io/llms.txt
Use this file to discover all available pages before exploring further.
Video API
Real-time video rooms backed by Orbit Media (Devotel’s hosted SFU; forked from LiveKit OSS under Apache-2 License — see attribution). Two flavors:
- Scheduled rooms (
/api/v1/video/rooms-scheduled) — DB-backed, first-class rooms with a host, schedule, capacity, recording, and per-room guest invites. Use when you want the room to outlive a single session, surface in the inbox as a video conversation, or hand out shareable join links.
- Ad-hoc rooms (
/api/v1/video/rooms) — fire-and-forget Orbit Media rooms created directly against the Orbit Media room service. Use for “click to start a call” flows where the room ends when participants leave.
Base path: /api/v1/video
Authentication: API key (X-API-Key) on every authenticated endpoint. The public guest-redeem endpoint (POST /video/invites/:inviteToken/redeem) takes the invite token itself as the credential and requires no API key.
Role requirements: mutating endpoints (create, delete, end, recording start/stop, invite create/revoke, participant kick/mute) require owner, admin, or developer. Read endpoints (list, get) and POST /rooms-scheduled/:id/join are available to any authenticated tenant member; the join route silently downgrades role: "host" to participant for callers that lack the host-mint role.
Orbit Media configuration: every endpoint returns 503 SERVICE_UNAVAILABLE if DEVOTEL_ORBIT_MEDIA_URL / DEVOTEL_ORBIT_MEDIA_API_KEY are not set on the cluster. (Legacy DEVOTEL_LIVEKIT_* env vars remain as deprecated fallbacks and will be removed in a future release; prefer DEVOTEL_ORBIT_MEDIA_* for all new deployments.)
Wire compatibility: Orbit Media speaks the same JWT-signed signaling protocol as the LiveKit OSS fork it descends from. The published @orbit/media-client browser SDK is the supported client. The upstream livekit-client SDK still works at the protocol level today but is not part of Orbit’s supported surface and may diverge.
Scheduled rooms
First-class video rooms backed by tenant_<id>.video_rooms. Each room creates a companion conversations row (channel='video') so the inbox renders it alongside other channels.
Single-occurrence only. Each scheduled room represents one meeting instance — there is no rrule / recurrence / series field today, and the generated .ics calendar attachment does NOT carry an RRULE line. For a weekly stand-up, daily check-in, or any other recurring cadence, the caller must create one scheduled room per occurrence. Native RFC 5545 RRULE support (FREQ=DAILY/WEEKLY/MONTHLY with BYDAY, INTERVAL, COUNT, UNTIL) is on the roadmap; the underlying ICS generator already supports it, but the meeting-create surface intentionally does not expose it until per-occurrence overrides (EXDATE, single-instance reschedule) and the cancellation/re-send flow for series updates land.
Schedule a Room
POST /api/v1/video/rooms-scheduled
Create a new scheduled video room (single occurrence — see the “Single-occurrence only” note above). Tenant-isolated. Optionally pre-fills a start time, enables auto-recording, and caps capacity. Returns a host token immediately so the creator can join without a second round-trip.
Human-readable room title (1–200 chars). Surfaces in the inbox and join UIs.
ISO-8601 timestamp with offset (e.g. 2026-05-10T14:00:00Z). Omit or null for “start now”. Single occurrence only — no rrule companion field today.
When true, recording starts automatically the moment the first participant joins.
Hard cap on concurrent participants (1–500). Orbit Media rejects join attempts past this number.
Arbitrary key-value pairs persisted on the room row. Surfaces back on GET /:id.
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled" \
-H "X-API-Key: dv_live_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Onboarding call - Acme",
"scheduled_at": "2026-05-10T14:00:00Z",
"recording_enabled": true,
"max_participants": 4
}'
import { Orbit } from '@devotel/orbit-sdk'
const orbit = new Orbit({ apiKey: process.env.ORBIT_API_KEY! })
const room = await orbit.video.roomsScheduled.create({
name: 'Onboarding call - Acme',
scheduled_at: '2026-05-10T14:00:00Z',
recording_enabled: true,
max_participants: 4,
})
console.log(room.data.room_id, room.data.host_token)
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/video/rooms-scheduled", headers=headers, json={
"name": "Onboarding call - Acme",
"scheduled_at": "2026-05-10T14:00:00Z",
"recording_enabled": True,
"max_participants": 4
})
print(r.json())
package main
import (
"bytes"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled", bytes.NewBuffer([]byte(`{
"name": "Onboarding call - Acme",
"scheduled_at": "2026-05-10T14:00:00Z",
"recording_enabled": true,
"max_participants": 4
}`)))
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
req.Header.Set("Content-Type", "application/json")
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms-scheduled');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: ' . getenv('ORBIT_API_KEY'),
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
{
"name": "Onboarding call - Acme",
"scheduled_at": "2026-05-10T14:00:00Z",
"recording_enabled": true,
"max_participants": 4
}
JSON);
echo curl_exec($ch);
{
"data": {
"room_id": "8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002",
"room_sid": "RM_AbCdEfGhIjKl",
"host_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"ws_url": "wss://media.orbit.devotel.io"
},
"meta": {
"request_id": "req_video_001",
"timestamp": "2026-05-10T12:00:00Z"
}
}
Error codes: 503 SERVICE_UNAVAILABLE (Orbit Media not configured), 400 (validation), 403 (caller lacks owner/admin/developer), 500 VIDEO_ROOM_CREATION_FAILED.
List Scheduled Rooms
GET /api/v1/video/rooms-scheduled
Retrieve rooms scheduled by the current tenant. Filter by lifecycle status.
Filter by lifecycle: scheduled, live, ended. Omit for all statuses.
Results to return (1–500).
curl -X GET "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled?status=live&limit=50" \
-H "X-API-Key: dv_live_sk_your_key_here"
const rooms = await orbit.video.roomsScheduled.list({ status: 'live', limit: 50 })
console.log(rooms.data)
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/video/rooms-scheduled",
headers=headers, params={"status": "live", "limit": 50})
print(r.json())
package main
import (
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled?status=live&limit=50", nil)
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms-scheduled?status=live&limit=50');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
{
"data": [
{
"id": "8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002",
"name": "Onboarding call - Acme",
"room_sid": "RM_AbCdEfGhIjKl",
"host_user_id": "user_2x7Q...",
"status": "live",
"scheduled_at": "2026-05-10T14:00:00Z",
"started_at": "2026-05-10T14:00:12Z",
"ended_at": null,
"recording_enabled": true,
"recording_url": null,
"max_participants": 4,
"settings": { "conversation_id": "conversation_kPdE..." },
"created_at": "2026-05-09T22:14:00Z",
"updated_at": "2026-05-10T14:00:12Z"
}
],
"meta": { "request_id": "req_video_002", "timestamp": "2026-05-10T14:01:00Z" }
}
Error codes: 503 SERVICE_UNAVAILABLE, 400 (invalid query), 500 VIDEO_ROOM_LIST_FAILED.
Get a Scheduled Room
GET /api/v1/video/rooms-scheduled/{id}
Fetch a single room plus the live participant roster.
Room UUID returned from POST /rooms-scheduled.
curl -X GET "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002" \
-H "X-API-Key: dv_live_sk_your_key_here"
const detail = await orbit.video.roomsScheduled.get('8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002')
console.log(detail.data.room, detail.data.participants)
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002", headers=headers)
print(r.json())
package main
import (
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002", nil)
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
{
"data": {
"room": {
"id": "8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002",
"name": "Onboarding call - Acme",
"room_sid": "RM_AbCdEfGhIjKl",
"status": "live",
"scheduled_at": "2026-05-10T14:00:00Z",
"started_at": "2026-05-10T14:00:12Z",
"ended_at": null,
"recording_enabled": true,
"max_participants": 4
},
"participants": [
{
"identity": "user_2x7Q...",
"display_name": "Jane Doe",
"role": "host",
"joined_at": "2026-05-10T14:00:12Z",
"left_at": null
}
]
},
"meta": { "request_id": "req_video_003", "timestamp": "2026-05-10T14:02:00Z" }
}
Error codes: 503 SERVICE_UNAVAILABLE, 400 (id not a UUID), 404 NOT_FOUND, 500 VIDEO_ROOM_FETCH_FAILED.
Join a Scheduled Room
POST /api/v1/video/rooms-scheduled/{id}/join
Generate a per-participant Orbit Media access token. The resulting JWT is short-lived and scoped to one room + identity.
Unique participant identity (1–200 chars). Used as the Orbit Media participant id. Reusing the same identity in the same room kicks the prior session.
Friendly name shown to other participants.
role
string
default:"participant"
participant or host. Server-side guard: callers that are not owner / admin / developer are silently downgraded to participant.
Optional UUID of an Orbit user this participant represents. Defaults to the calling user.
Optional UUID of a contact the participant represents (CRM linkage).
Token TTL in seconds (60–86400). Defaults to the service-configured value.
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/join" \
-H "X-API-Key: dv_live_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"identity": "user_42",
"display_name": "Jane Doe",
"role": "host"
}'
const token = await orbit.video.roomsScheduled.join('8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002', {
identity: 'user_42',
display_name: 'Jane Doe',
role: 'host',
})
console.log(token.data.token, token.data.ws_url)
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/join",
headers=headers, json={
"identity": "user_42",
"display_name": "Jane Doe",
"role": "host"
})
print(r.json())
package main
import (
"bytes"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/join",
bytes.NewBuffer([]byte(`{"identity":"user_42","display_name":"Jane Doe","role":"host"}`)))
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
req.Header.Set("Content-Type", "application/json")
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/join');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: ' . getenv('ORBIT_API_KEY'),
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"identity":"user_42","display_name":"Jane Doe","role":"host"}');
echo curl_exec($ch);
{
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"ws_url": "wss://media.orbit.devotel.io",
"room_sid": "RM_AbCdEfGhIjKl"
},
"meta": { "request_id": "req_video_004", "timestamp": "2026-05-10T14:05:00Z" }
}
Error codes: 503 SERVICE_UNAVAILABLE, 400 (validation), 404 (room not found), 500 VIDEO_ROOM_JOIN_FAILED.
End a Scheduled Room
POST /api/v1/video/rooms-scheduled/{id}/end
Terminate the room immediately. All participants are disconnected and the room transitions to ended. Recording (if active) is stopped and flushed.
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/end" \
-H "X-API-Key: dv_live_sk_your_key_here"
await orbit.video.roomsScheduled.end('8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002')
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/end", headers=headers)
print(r.status_code)
package main
import (
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/end", nil)
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/end');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
Error codes: 503 SERVICE_UNAVAILABLE, 400 (id not a UUID), 403 (caller lacks owner/admin/developer), 404 (room not found), 500 VIDEO_ROOM_END_FAILED.
Start Recording
POST /api/v1/video/rooms-scheduled/{id}/recording/start
Begin an Orbit Media egress recording on the room. Returns the egress id to track via the Orbit Media webhook lifecycle. If recording_enabled was set at creation time the recording auto-starts and this call is a no-op.
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/recording/start" \
-H "X-API-Key: dv_live_sk_your_key_here"
const egress = await orbit.video.roomsScheduled.recording.start('8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002')
console.log(egress.data.egress_id)
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/recording/start", headers=headers)
print(r.json())
package main
import (
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/recording/start", nil)
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/recording/start');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
{
"data": {
"egress_id": "EG_QrStUvWxYz"
},
"meta": { "request_id": "req_video_005", "timestamp": "2026-05-10T14:06:00Z" }
}
Error codes: 503 SERVICE_UNAVAILABLE, 403 (caller lacks owner/admin/developer), 404 (room not found), 500 VIDEO_RECORDING_START_FAILED.
Stop Recording
POST /api/v1/video/rooms-scheduled/{id}/recording/stop
End the Orbit Media egress. The recording is finalised and the resulting URL (MP4 / HLS depending on egress settings) is written back to the room row asynchronously via the Orbit Media webhook.
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/recording/stop" \
-H "X-API-Key: dv_live_sk_your_key_here"
await orbit.video.roomsScheduled.recording.stop('8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002')
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/recording/stop", headers=headers)
print(r.status_code)
package main
import (
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/recording/stop", nil)
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/recording/stop');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
Error codes: 503 SERVICE_UNAVAILABLE, 403, 404 (room not found), 500 VIDEO_RECORDING_STOP_FAILED.
Guest invites
Issue tokenised join links to people outside your tenant. The invite token IS the credential — anyone who holds it can redeem it (subject to expires_at + max_uses) without an Orbit account.
Create an Invite
POST /api/v1/video/rooms-scheduled/{id}/invites
Mint a guest-redeemable invite for a scheduled room. The returned invite_token is what you embed in a shareable URL.
Optional name pre-filled for the guest in the redeem flow (1–120 chars).
Invite TTL in hours (1–168). Defaults to a service-configured value (typically 24).
Cap on the number of redemptions (1–500). Omit or null for unlimited.
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/invites" \
-H "X-API-Key: dv_live_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"participant_name": "Acme Customer",
"expires_in_hours": 24,
"max_uses": 5
}'
const invite = await orbit.video.roomsScheduled.invites.create('8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002', {
participant_name: 'Acme Customer',
expires_in_hours: 24,
max_uses: 5,
})
console.log(invite.data.invite_token)
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/invites",
headers=headers, json={
"participant_name": "Acme Customer",
"expires_in_hours": 24,
"max_uses": 5
})
print(r.json())
package main
import (
"bytes"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/invites",
bytes.NewBuffer([]byte(`{"participant_name":"Acme Customer","expires_in_hours":24,"max_uses":5}`)))
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
req.Header.Set("Content-Type", "application/json")
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/invites');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: ' . getenv('ORBIT_API_KEY'),
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"participant_name":"Acme Customer","expires_in_hours":24,"max_uses":5}');
echo curl_exec($ch);
{
"data": {
"id": "5b3c7f01-2e44-49ab-bce0-7d0a8c9f0b14",
"room_id": "8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002",
"invite_token": "p9Z-vQrX_3kT2hN1m4yL5wB6aE7s8C9d",
"participant_name": "Acme Customer",
"expires_at": "2026-05-11T22:00:00Z",
"revoked_at": null,
"max_uses": 5,
"uses_count": 0,
"created_at": "2026-05-10T22:00:00Z"
},
"meta": { "request_id": "req_video_006", "timestamp": "2026-05-10T22:00:00Z" }
}
Error codes: 503 SERVICE_UNAVAILABLE, 400 (validation), 403, 404 (room not found), 500 VIDEO_INVITE_CREATE_FAILED. Rate-limited per the authenticated-write bucket.
List Invites
GET /api/v1/video/rooms-scheduled/{id}/invites
Retrieve every invite issued for a room.
When true, also returns invites past expires_at.
Results to return (1–500).
curl -X GET "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/invites?include_expired=true" \
-H "X-API-Key: dv_live_sk_your_key_here"
const invites = await orbit.video.roomsScheduled.invites.list('8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002', {
include_expired: true,
})
console.log(invites.data)
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/invites",
headers=headers, params={"include_expired": "true"})
print(r.json())
package main
import (
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/invites?include_expired=true", nil)
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/invites?include_expired=true');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
{
"data": [
{
"id": "5b3c7f01-2e44-49ab-bce0-7d0a8c9f0b14",
"room_id": "8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002",
"invite_token": "p9Z-vQrX_3kT2hN1m4yL5wB6aE7s8C9d",
"participant_name": "Acme Customer",
"expires_at": "2026-05-11T22:00:00Z",
"revoked_at": null,
"max_uses": 5,
"uses_count": 1,
"created_at": "2026-05-10T22:00:00Z"
}
],
"meta": { "request_id": "req_video_007", "timestamp": "2026-05-10T22:30:00Z" }
}
Error codes: 503 SERVICE_UNAVAILABLE, 400 (invalid query), 404 (room not found), 500 VIDEO_INVITE_LIST_FAILED. Rate-limited per the authenticated-read bucket.
Revoke an Invite
DELETE /api/v1/video/rooms-scheduled/{id}/invites/{inviteId}
Permanently revoke an invite. Subsequent redeem attempts return 404.
curl -X DELETE "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/invites/5b3c7f01-2e44-49ab-bce0-7d0a8c9f0b14" \
-H "X-API-Key: dv_live_sk_your_key_here"
await orbit.video.roomsScheduled.invites.revoke(
'8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002',
'5b3c7f01-2e44-49ab-bce0-7d0a8c9f0b14',
)
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.delete("https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/invites/5b3c7f01-2e44-49ab-bce0-7d0a8c9f0b14",
headers=headers)
print(r.status_code)
package main
import (
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/invites/5b3c7f01-2e44-49ab-bce0-7d0a8c9f0b14", nil)
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/invites/5b3c7f01-2e44-49ab-bce0-7d0a8c9f0b14');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
Error codes: 503 SERVICE_UNAVAILABLE, 400 (id / inviteId not UUIDs), 403, 404 (invite not found), 500 VIDEO_INVITE_REVOKE_FAILED. Rate-limited per the authenticated-write bucket.
Redeem an Invite (public)
POST /api/v1/video/invites/{inviteToken}/redeem
No authentication. Guest-facing endpoint. Exchanges an invite token for a short-lived Orbit Media JWT scoped to one room. Rate-limited per IP (30 requests / minute).
The opaque invite token returned by POST /rooms-scheduled/{id}/invites. 24–64 characters, [A-Za-z0-9_-]+.
Friendly name shown to other participants (1–120 chars).
Optional URL-safe identity ([A-Za-z0-9_:-]+, 1–120 chars). If omitted, the service mints one.
curl -X POST "https://api.orbit.devotel.io/api/v1/video/invites/p9Z-vQrX_3kT2hN1m4yL5wB6aE7s8C9d/redeem" \
-H "Content-Type: application/json" \
-d '{ "display_name": "Acme Customer" }'
// Public endpoint — no API key. Call directly via fetch.
const res = await fetch('https://api.orbit.devotel.io/api/v1/video/invites/p9Z-vQrX_3kT2hN1m4yL5wB6aE7s8C9d/redeem', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ display_name: 'Acme Customer' }),
})
const json = await res.json()
console.log(json.data.token, json.data.media_url)
import requests
r = requests.post("https://api.orbit.devotel.io/api/v1/video/invites/p9Z-vQrX_3kT2hN1m4yL5wB6aE7s8C9d/redeem",
json={"display_name": "Acme Customer"})
print(r.json())
package main
import (
"bytes"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/video/invites/p9Z-vQrX_3kT2hN1m4yL5wB6aE7s8C9d/redeem",
bytes.NewBuffer([]byte(`{"display_name":"Acme Customer"}`)))
req.Header.Set("Content-Type", "application/json")
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/invites/p9Z-vQrX_3kT2hN1m4yL5wB6aE7s8C9d/redeem');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"display_name":"Acme Customer"}');
echo curl_exec($ch);
{
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"ws_url": "wss://media.orbit.devotel.io",
"media_url": "wss://media.orbit.devotel.io",
"livekit_url": "wss://media.orbit.devotel.io",
"room_sid": "RM_AbCdEfGhIjKl",
"room_id": "8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002",
"room_name": "Onboarding call - Acme",
"identity": "guest_8f2a01b7",
"display_name": "Acme Customer",
"expires_at": "2026-05-10T15:00:00Z"
},
"meta": { "request_id": "req_video_008", "timestamp": "2026-05-10T14:00:00Z" }
}
Error codes: 400 (validation, including malformed token), 404 (invite not found / revoked / expired / max-uses exhausted), 429 (per-IP rate-limit), 500 VIDEO_INVITE_REDEEM_FAILED.
Host admin controls
Force-disconnect or force-mute participants in a live scheduled room.
Kick a Participant
POST /api/v1/video/rooms-scheduled/{id}/participants/{identity}/kick
Immediately disconnect a participant from the room. Their Orbit Media session is terminated; they can rejoin only with a fresh token.
The Orbit Media identity to kick (matches the identity passed at join time).
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/participants/guest_8f2a01b7/kick" \
-H "X-API-Key: dv_live_sk_your_key_here"
await orbit.video.roomsScheduled.participants.kick(
'8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002',
'guest_8f2a01b7',
)
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/participants/guest_8f2a01b7/kick",
headers=headers)
print(r.status_code)
package main
import (
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/participants/guest_8f2a01b7/kick", nil)
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/participants/guest_8f2a01b7/kick');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
Error codes: 503 SERVICE_UNAVAILABLE, 400 (id not a UUID), 403, 404 (participant or room not found), 500 VIDEO_PARTICIPANT_KICK_FAILED. Rate-limited per the authenticated-write bucket.
Mute a Participant
POST /api/v1/video/rooms-scheduled/{id}/participants/{identity}/mute
Force-mute a participant’s audio or video track. The participant can unmute themselves; this is a host-driven hard mute, not a permanent block.
audio or video. Selects which track to mute.
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/participants/guest_8f2a01b7/mute" \
-H "X-API-Key: dv_live_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "kind": "audio" }'
await orbit.video.roomsScheduled.participants.mute(
'8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002',
'guest_8f2a01b7',
{ kind: 'audio' },
)
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/participants/guest_8f2a01b7/mute",
headers=headers, json={"kind": "audio"})
print(r.status_code)
package main
import (
"bytes"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/participants/guest_8f2a01b7/mute",
bytes.NewBuffer([]byte(`{"kind":"audio"}`)))
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
req.Header.Set("Content-Type", "application/json")
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/8e7c9a02-3e94-4f4f-b2a8-91d6b9b3a002/participants/guest_8f2a01b7/mute');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: ' . getenv('ORBIT_API_KEY'),
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"kind":"audio"}');
echo curl_exec($ch);
Error codes: 503 SERVICE_UNAVAILABLE, 400 (validation, including kind not in ), 403, 404 (participant or room not found), 500 VIDEO_PARTICIPANT_MUTE_FAILED. Rate-limited per the authenticated-write bucket.
Ad-hoc rooms
Fire-and-forget Orbit Media rooms. No DB row, no inbox conversation, no recording. Use when the room only needs to live for one session and disappear when participants leave. Room names are tenant-prefixed on the Orbit Media side (invariant #TI-P0-3) so collisions across tenants are impossible.
Create an Ad-hoc Room
POST /api/v1/video/rooms
Create an Orbit Media room and receive a host token in the same response.
URL-safe room name (3–200 chars, [a-zA-Z0-9_-]+). Tenant prefix is prepended internally; display_name echoes the un-prefixed name.
Hard cap on concurrent participants (2–100).
Arbitrary key-value pairs forwarded to Orbit Media.
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms" \
-H "X-API-Key: dv_live_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "quick-sync",
"max_participants": 8
}'
const room = await orbit.video.rooms.create({
name: 'quick-sync',
max_participants: 8,
})
console.log(room.data.room_name, room.data.token)
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/video/rooms", headers=headers, json={
"name": "quick-sync",
"max_participants": 8
})
print(r.json())
package main
import (
"bytes"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/video/rooms",
bytes.NewBuffer([]byte(`{"name":"quick-sync","max_participants":8}`)))
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
req.Header.Set("Content-Type", "application/json")
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: ' . getenv('ORBIT_API_KEY'),
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"name":"quick-sync","max_participants":8}');
echo curl_exec($ch);
{
"data": {
"room_name": "tenant_abc1234_quick-sync",
"display_name": "quick-sync",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"url": "wss://media.orbit.devotel.io"
},
"meta": { "request_id": "req_video_009", "timestamp": "2026-05-10T14:00:00Z" }
}
Error codes: 503 SERVICE_UNAVAILABLE, 400 (validation, including non-URL-safe name), 403, 500 VIDEO_ROOM_CREATION_FAILED.
List Ad-hoc Rooms
GET /api/v1/video/rooms
List active Orbit Media rooms owned by the current tenant. Cursor-paginated by room name.
Room name to resume from. Returned in the previous page’s pagination.next_cursor.
Results per page (max 100).
curl -X GET "https://api.orbit.devotel.io/api/v1/video/rooms?limit=20" \
-H "X-API-Key: dv_live_sk_your_key_here"
const rooms = await orbit.video.rooms.list({ limit: 20 })
console.log(rooms.data, rooms.pagination)
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/video/rooms", headers=headers, params={"limit": 20})
print(r.json())
package main
import (
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/video/rooms?limit=20", nil)
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms?limit=20');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
{
"data": [
{
"name": "tenant_abc1234_quick-sync",
"display_name": "quick-sync",
"num_participants": 2,
"max_participants": 8,
"creation_time": "2026-05-10T14:00:00Z",
"metadata": ""
}
],
"pagination": {
"next_cursor": null,
"has_more": false,
"total": 1
},
"meta": { "request_id": "req_video_010", "timestamp": "2026-05-10T14:05:00Z" }
}
Error codes: 503 SERVICE_UNAVAILABLE, 400 INVALID_CURSOR, 500 VIDEO_ROOM_LIST_FAILED.
Get an Ad-hoc Room
GET /api/v1/video/rooms/{name}
Fetch a specific ad-hoc room with its live participant roster. Pass either the prefixed wire name or the bare display name.
Room name (with or without the tenant_<id>_ prefix).
curl -X GET "https://api.orbit.devotel.io/api/v1/video/rooms/quick-sync" \
-H "X-API-Key: dv_live_sk_your_key_here"
const detail = await orbit.video.rooms.get('quick-sync')
console.log(detail.data.participants)
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/video/rooms/quick-sync", headers=headers)
print(r.json())
package main
import (
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/video/rooms/quick-sync", nil)
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms/quick-sync');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
{
"data": {
"name": "tenant_abc1234_quick-sync",
"display_name": "quick-sync",
"num_participants": 2,
"max_participants": 8,
"creation_time": "2026-05-10T14:00:00Z",
"metadata": "",
"participants": [
{
"identity": "user_42",
"name": "Jane Doe",
"joined_at": "2026-05-10T14:00:30Z",
"is_publishing": true
}
]
},
"meta": { "request_id": "req_video_011", "timestamp": "2026-05-10T14:02:00Z" }
}
Error codes: 503 SERVICE_UNAVAILABLE, 404 NOT_FOUND, 500 VIDEO_ROOM_DETAILS_FAILED.
Delete an Ad-hoc Room
DELETE /api/v1/video/rooms/{name}
Close the Orbit Media room and disconnect every participant.
curl -X DELETE "https://api.orbit.devotel.io/api/v1/video/rooms/quick-sync" \
-H "X-API-Key: dv_live_sk_your_key_here"
await orbit.video.rooms.delete('quick-sync')
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.delete("https://api.orbit.devotel.io/api/v1/video/rooms/quick-sync", headers=headers)
print(r.status_code)
package main
import (
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://api.orbit.devotel.io/api/v1/video/rooms/quick-sync", nil)
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms/quick-sync');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
Error codes: 503 SERVICE_UNAVAILABLE, 403 (caller lacks owner/admin/developer), 500 VIDEO_ROOM_CLOSE_FAILED.
Issue a Participant Token
POST /api/v1/video/rooms/{name}/token
Generate a per-participant Orbit Media join token for an ad-hoc room. Use this when the host needs to invite someone else into the room they created.
Friendly participant name (1–200 chars). Also used as the Orbit Media identity.
Arbitrary key-value pairs attached to the Orbit Media participant.
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms/quick-sync/token" \
-H "X-API-Key: dv_live_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "participant_name": "Jane Doe" }'
const token = await orbit.video.rooms.token('quick-sync', { participant_name: 'Jane Doe' })
console.log(token.data.token, token.data.url)
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/video/rooms/quick-sync/token",
headers=headers, json={"participant_name": "Jane Doe"})
print(r.json())
package main
import (
"bytes"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/video/rooms/quick-sync/token",
bytes.NewBuffer([]byte(`{"participant_name":"Jane Doe"}`)))
req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
req.Header.Set("Content-Type", "application/json")
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/video/rooms/quick-sync/token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: ' . getenv('ORBIT_API_KEY'),
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"participant_name":"Jane Doe"}');
echo curl_exec($ch);
{
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"url": "wss://media.orbit.devotel.io"
},
"meta": { "request_id": "req_video_012", "timestamp": "2026-05-10T14:01:00Z" }
}
Error codes: 503 SERVICE_UNAVAILABLE, 400 (validation), 403, 500 TOKEN_GENERATION_FAILED.
Inbound webhook
Orbit Media posts room and recording lifecycle events to POST /api/v1/video/webhook. The endpoint is platform-managed — you don’t call it directly. Events received here drive started_at / ended_at / recording_url updates on video_rooms and the matching inbox-conversation status transitions.
Orbit Media signs each delivery with a JWT minted from the API secret; Orbit validates the signature via the SFU’s WebhookReceiver (carried over from the LiveKit OSS fork) before processing.
See also