
Twilio Video
- 48 installs
- 6 repo stars
- Updated March 13, 2026
- alphaonedev/openclaw-graph
twilio-video is a Claude Code skill that guides implementing and operating Twilio Programmable Video (rooms, access tokens, track moderation, and Composition recording) in production.
About
This skill guides an agent through designing, implementing, and operating Twilio Programmable Video in production. It covers creating Group and P2P rooms, minting access tokens with video grants, publishing and moderating tracks, server-side recording via Compositions, and using the Network Quality API. A developer uses it when adding WebRTC video calling to a multi-tenant application.
- Create and manage Twilio Video Group/P2P rooms with lifecycle controls
- Mint Access Tokens with Video grants and rotate API keys
- Server-side recording via Compositions plus Network Quality API and bandwidth profiles
Twilio Video by the numbers
- 48 all-time installs (skills.sh)
- Ranked #3,249 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
twilio-video capabilities & compatibility
Requires a Twilio account with API Key/Secret; usage billed by Twilio
- Capabilities
- twilio voice · twilio whatsapp · voice call · web api
- Works with
- aws · gcp
- Use cases
- api development · transcription
- Platforms
- macOS · Linux
- Pricing
- Bring your own API key
What twilio-video says it does
Enable OpenClaw to design, implement, operate, and troubleshoot Twilio Video in production:
Create and manage Video Rooms (Group and Peer-to-Peer), including lifecycle controls.
Use Network Quality API and bandwidth profiles to maintain call quality under real-world network constraints.
npx skills add https://github.com/alphaonedev/openclaw-graph --skill twilio-videoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 48 |
|---|---|
| repo stars | ★ 6 |
| Last updated | March 13, 2026 |
| Repository | alphaonedev/openclaw-graph ↗ |
What it does
Add production Twilio WebRTC video rooms with recording, moderation, and network-quality handling to a multi-tenant app.
Who is it for?
Engineers building a multi-tenant, production WebRTC video system on Twilio
When should I use this skill?
Adding Twilio Video rooms, tokens, or Composition recording to an application
What you get
A production Twilio Video integration with rooms, token auth, recording, and network-quality handling
- Video Rooms management
- Access token minting
- Composition recording pipeline
By the numbers
- Pins twilio-video@2.31.1 JS SDK and twilio@4.23.0 Node helper library
Files
twilio-video
Purpose
Enable OpenClaw to design, implement, operate, and troubleshoot Twilio Video in production:
- Create and manage Video Rooms (Group and Peer-to-Peer), including lifecycle controls.
- Issue Access Tokens with Video grants (identity, room scoping, TTL, key rotation).
- Publish/subscribe tracks (audio/video/data), manage track priorities, and implement moderation (mute/unpublish/remove participants).
- Enable and operate recording via Compositions (server-side recording outputs), including callbacks and storage pipelines.
- Use Network Quality API and bandwidth profiles to maintain call quality under real-world network constraints.
- Integrate Video with Twilio cluster patterns: webhooks, auth, rate limits, cost controls, and operational playbooks.
This guide assumes you are building a multi-tenant, production WebRTC system with compliance, observability, and incident response requirements.
---
Prerequisites
Twilio account + credentials
- Twilio Account SID (format
AC...) - Twilio API Key SID (format
SK...) and Secret - (Optional) Twilio Auth Token (format
...) for legacy/basic auth; prefer API Key + Secret for server-to-server. - A Twilio Video-enabled project (default for most accounts).
Create API Key:
twilio api:core:keys:create --friendly-name "video-prod-key-2026-02"Supported SDKs / libraries (pin versions)
Server-side (token minting, REST API calls):
- Node.js:
node >= 20.11.1(LTS), npm>= 10.2.4 twilio@4.23.0(Twilio Node helper library)jsonwebtoken@9.0.2(if you implement custom JWT handling; Twilio helper can mint tokens)- Python:
python >= 3.11.7 twilio==9.4.1- Go:
go >= 1.22.1 github.com/twilio/twilio-go@v1.20.3
Client-side:
- Twilio Video JS SDK:
twilio-video@2.31.1 - Browser support: latest Chrome/Edge, Firefox ESR, Safari 17+ (macOS/iOS constraints apply)
- iOS: TwilioVideo iOS SDK
5.10.0(CocoaPods/SPM) - Android: Twilio Video Android SDK
7.6.0(Gradle)
Twilio CLI:
twilio-cli@5.5.1(Node-based)- Plugins:
@twilio-labs/plugin-video@0.7.0(Video commands; plugin availability varies—verify in your environment)
Install Twilio CLI:
npm i -g twilio-cli@5.5.1
twilio --versionInstall plugin:
twilio plugins:install @twilio-labs/plugin-video@0.7.0
twilio pluginsAuth setup (local dev + CI)
Prefer environment variables:
TWILIO_ACCOUNT_SIDTWILIO_API_KEYTWILIO_API_SECRET- (Optional)
TWILIO_AUTH_TOKEN(avoid in CI if possible)
Example (bash):
export TWILIO_ACCOUNT_SID="YOUR_ACCOUNT_SID"
export TWILIO_API_KEY="YOUR_API_KEY_SID"
export TWILIO_API_SECRET="your_api_secret_from_console"Twilio CLI login (stores credentials locally; not recommended for CI runners):
twilio loginNetwork + infra prerequisites
- Public HTTPS endpoint for webhooks (Composition callbacks, Status callbacks).
- TLS: modern ciphers; certificate from a trusted CA (Let’s Encrypt OK).
- Time sync: NTP enabled on servers minting JWTs (clock skew breaks tokens).
- TURN/STUN: Twilio provides; ensure outbound UDP/TCP 3478/5349 allowed; enterprise networks may require TCP/TLS fallback.
---
Core Concepts
Rooms: Group vs Peer-to-Peer (P2P)
- Group Room: media routed via Twilio’s SFU; supports larger rooms, recording/compositions, bandwidth profiles, network quality, dominant speaker, etc. Default for most production use.
- Peer-to-Peer Room: direct mesh between participants; limited scalability; fewer server-side features; generally not recommended for production beyond 1:1 with strict latency constraints.
Key properties:
type:group|group-small|peer-to-peer(availability depends on account/region)status:in-progress|completedmaxParticipants: enforce caps to control cost and quality
Participants and Tracks
- Participant: identity in a room (unique per room).
- Tracks:
- Audio track
- Video track
- Data track (reliable/unreliable messaging)
- Track lifecycle:
published→subscribed(remote) →unpublished/stopped - Server-side moderation often means:
- Disconnect participant
- Force unpublish (where supported)
- Enforce client behavior via signaling + policy
Access Tokens (JWT)
Twilio Video uses JWT access tokens with a VideoGrant:
identity: stable user identifier (tenant-scoped)ttl: seconds; keep short (e.g., 3600) and refreshroom: optional; restrict token to a specific room- Signed with API Key Secret
Clock skew is a common failure mode; keep NTP.
Recording via Compositions
Twilio Video “recording” in production typically means Compositions:
- A Composition is a server-side rendered output (e.g., MP4) created from recorded tracks.
- You can configure:
- Layout (grid, active speaker, custom)
- Format
- Status callback URL
- You must handle asynchronous completion and storage (S3/GCS/Azure) yourself.
Network Quality API
- Provides per-participant network quality levels (0–5) and stats.
- Use it to:
- Adapt bitrate/resolution
- Trigger UI warnings
- Decide when to switch to audio-only
- Combine with bandwidth profiles for predictable behavior.
Bandwidth Profiles and Track Priority
- Bandwidth profiles define how Twilio allocates bandwidth among tracks.
- Track priority (
low/standard/high) influences which tracks degrade first. - Use for:
- Prioritizing presenter video
- Keeping audio stable under congestion
Webhooks and callbacks
Common callback types:
- Composition status callbacks
- Room status callbacks (where configured)
- Participant events (often handled client-side; server can poll REST API)
Webhooks must be:
- HTTPS
- Verified (Twilio signature validation)
- Idempotent (Twilio retries)
---
Installation & Setup
Official Python SDK — Video
Repository: https://github.com/twilio/twilio-python PyPI: pip install twilio · Supported: Python 3.7–3.13
from twilio.rest import Client
from twilio.jwt.access_token import AccessToken
from twilio.jwt.access_token.grants import VideoGrant
client = Client()
# Create a room
room = client.video.v1.rooms.create(
unique_name="DailyStandup",
type="go" # or 'group' / 'peer-to-peer'
)
print(room.sid)
# Generate participant access token
token = AccessToken(
os.environ["TWILIO_ACCOUNT_SID"],
os.environ["TWILIO_API_KEY"],
os.environ["TWILIO_API_SECRET"],
identity="alice"
)
token.add_grant(VideoGrant(room="DailyStandup"))
print(token.to_jwt())Source: twilio/twilio-python — video
Ubuntu 22.04 LTS (x86_64)
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg jq
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v
npm -v
sudo npm i -g twilio-cli@5.5.1
twilio --version
twilio plugins:install @twilio-labs/plugin-video@0.7.0Fedora 39/40 (x86_64)
sudo dnf install -y nodejs npm jq
node -v
npm -v
sudo npm i -g twilio-cli@5.5.1
twilio plugins:install @twilio-labs/plugin-video@0.7.0macOS (Intel + Apple Silicon)
Using Homebrew:
brew install node@20 jq
echo 'export PATH="/opt/homebrew/opt/node@20/bin:$PATH"' >> ~/.zshrc # Apple Silicon
echo 'export PATH="/usr/local/opt/node@20/bin:$PATH"' >> ~/.zshrc # Intel
source ~/.zshrc
npm i -g twilio-cli@5.5.1
twilio plugins:install @twilio-labs/plugin-video@0.7.0Windows 11 (PowerShell)
Install Node.js 20 LTS from official installer or winget:
winget install OpenJS.NodeJS.LTS
node -v
npm -v
npm i -g twilio-cli@5.5.1
twilio --version
twilio plugins:install @twilio-labs/plugin-video@0.7.0Server library setup (Node.js)
mkdir -p video-service && cd video-service
npm init -y
npm i twilio@4.23.0 fastify@4.26.2 @fastify/env@4.3.0 pino@8.19.0
npm i -D typescript@5.3.3 tsx@4.7.0 @types/node@20.11.19Minimal tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}Server library setup (Python)
python3.11 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip==24.0
pip install twilio==9.4.1 fastapi==0.109.2 uvicorn==0.27.1 python-dotenv==1.0.1---
Key Capabilities
Room lifecycle management
Production patterns:
- Create rooms on-demand (or pre-provision for scheduled sessions).
- Enforce
uniqueNamenaming conventions: include tenant + resource id. - Set
maxParticipantsto control cost and prevent abuse. - Complete rooms explicitly when sessions end to stop billing.
Example naming scheme:
acme-prod:class:9f3c2b1atenantId:resourceType:resourceId
Node: create room:
import twilio from "twilio";
const client = twilio(process.env.TWILIO_API_KEY!, process.env.TWILIO_API_SECRET!, {
accountSid: process.env.TWILIO_ACCOUNT_SID!,
});
async function createRoom() {
const room = await client.video.v1.rooms.create({
uniqueName: "acme-prod:class:9f3c2b1a",
type: "group",
maxParticipants: 25,
recordParticipantsOnConnect: false
});
return room;
}Complete room:
await client.video.v1.rooms("RM0123456789abcdef0123456789abcdef")
.update({ status: "completed" });Access token minting (VideoGrant)
Token service requirements:
- Authenticate caller (your auth, not Twilio’s).
- Authorize access to a room (tenant + role checks).
- Mint short-lived token (e.g., 15–60 minutes).
- Rotate API keys without downtime (support multiple signing keys).
Node token minting:
import twilio from "twilio";
const { AccessToken } = twilio.jwt;
const { VideoGrant } = AccessToken;
export function mintVideoToken(params: {
identity: string;
room?: string;
ttlSeconds?: number;
}) {
const token = new AccessToken(
process.env.TWILIO_ACCOUNT_SID!,
process.env.TWILIO_API_KEY!,
process.env.TWILIO_API_SECRET!,
{
identity: params.identity,
ttl: params.ttlSeconds ?? 3600
}
);
token.addGrant(new VideoGrant({ room: params.room }));
return token.toJwt();
}Python token minting:
from twilio.jwt.access_token import AccessToken
from twilio.jwt.access_token.grants import VideoGrant
def mint_video_token(identity: str, room: str | None = None, ttl: int = 3600) -> str:
token = AccessToken(
account_sid=os.environ["TWILIO_ACCOUNT_SID"],
signing_key_sid=os.environ["TWILIO_API_KEY"],
secret=os.environ["TWILIO_API_SECRET"],
identity=identity,
ttl=ttl,
)
token.add_grant(VideoGrant(room=room))
return token.to_jwt()Track publication, subscription, and moderation
Client-side (JS) connect with bandwidth profile and dominant speaker:
import Video from "twilio-video";
const room = await Video.connect(token, {
name: "acme-prod:class:9f3c2b1a",
dominantSpeaker: true,
networkQuality: { local: 1, remote: 1 },
bandwidthProfile: {
video: {
mode: "collaboration",
dominantSpeakerPriority: "high",
maxSubscriptionBitrate: 2500000,
renderDimensions: {
high: { width: 1280, height: 720 },
standard: { width: 640, height: 360 },
low: { width: 320, height: 180 }
}
}
}
});Moderation patterns:
- “Mute” is typically enforced by client policy (disable local track) + server-side role enforcement.
- For hard enforcement, disconnect participant:
await client.video.v1.rooms("RM...").participants("PA...")
.update({ status: "disconnected" });Recording and Compositions
Typical flow:
1. Enable participant recording (if required) or rely on composition sources. 2. Create Composition when room completes (or on-demand). 3. Receive status callback (processing → completed/failed). 4. Fetch composition media URL and ingest into storage.
Create composition (Node):
const composition = await client.video.v1.compositions.create({
roomSid: "RM0123456789abcdef0123456789abcdef",
audioSources: "*",
videoLayout: {
grid: { video_sources: ["*"] }
},
format: "mp4",
statusCallback: "https://video.acme.com/twilio/composition-status",
statusCallbackMethod: "POST"
});Fetch composition media:
const c = await client.video.v1.compositions("CJ0123456789abcdef0123456789abcdef").fetch();
console.log(c.links?.media);Network Quality API usage
Client-side event handling:
room.on("networkQualityLevelChanged", (level, stats) => {
// level: 0..5
// stats: { audio: { send, recv }, video: { send, recv } } depending on SDK
if (level <= 2) {
console.warn("Network degraded", level, stats);
}
});Server-side: fetch participant network quality (where supported by REST API fields; otherwise rely on client telemetry).
Bandwidth and codec tuning
Production defaults:
- Prefer VP8 for broad compatibility; consider H.264 for Safari-heavy audiences.
- Use
maxSubscriptionBitrateto cap downstream usage. - Use track priority for presenter:
const videoTrack = Array.from(room.localParticipant.videoTracks.values())[0]?.track;
videoTrack?.setPriority("high");Webhook handling (Composition callbacks)
Requirements:
- Validate Twilio signature (
X-Twilio-Signature) - Idempotency: dedupe by
CompositionSid+Status - Retry-safe: Twilio retries on non-2xx
Fastify example:
import Fastify from "fastify";
import twilio from "twilio";
const app = Fastify({ logger: true });
app.post("/twilio/composition-status", async (req, reply) => {
const signature = req.headers["x-twilio-signature"] as string | undefined;
const url = "https://video.acme.com/twilio/composition-status";
const isValid = twilio.validateRequest(
process.env.TWILIO_AUTH_TOKEN!, // signature validation uses Auth Token
signature ?? "",
url,
req.body as Record<string, string>
);
if (!isValid) return reply.code(403).send({ error: "invalid signature" });
// process CompositionSid, Status, RoomSid, etc.
return reply.code(204).send();
});
app.listen({ port: 3000, host: "0.0.0.0" });Note: Signature validation uses Auth Token, not API Secret. If you avoid Auth Token in services, isolate webhook verification into a small component with restricted secret access.
---
Command Reference
Notes:
- Twilio CLI command availability depends on installed plugins and Twilio CLI version.
- For Video, many operations are easiest via REST API using helper libraries; CLI is useful for inspection.
Twilio CLI: global flags
Common across commands:
-o, --output [json|tsv|table]output format--properties <props>comma-separated properties to display--no-headeromit header (tsv/table)--profile <name>use a named profile from~/.twilio-cli/config.json--log-level [debug|info|warn|error]
Example:
twilio api:video:v1:rooms:list -o json --log-level debugRooms (REST via CLI)
List rooms:
twilio api:video:v1:rooms:list \
--status in-progress \
--limit 50 \
-o table \
--properties sid,uniqueName,status,type,maxParticipants,dateCreatedRelevant flags:
--status <in-progress|completed>--unique-name <string>(filter; if supported by CLI generator)--limit <int>
Fetch room:
twilio api:video:v1:rooms:fetch --sid RM0123456789abcdef0123456789abcdef -o jsonCreate room:
twilio api:video:v1:rooms:create \
--unique-name "acme-prod:class:9f3c2b1a" \
--type group \
--max-participants 25 \
--record-participants-on-connect false \
-o jsonUpdate room (complete):
twilio api:video:v1:rooms:update \
--sid RM0123456789abcdef0123456789abcdef \
--status completed \
-o jsonParticipants
List participants in a room:
twilio api:video:v1:rooms:participants:list \
--room-sid RM0123456789abcdef0123456789abcdef \
--status connected \
--limit 200 \
-o table \
--properties sid,identity,status,dateCreatedFlags:
--room-sid <RM...>--status <connected|disconnected>--identity <string>(if supported)--limit <int>
Disconnect participant:
twilio api:video:v1:rooms:participants:update \
--room-sid RM0123456789abcdef0123456789abcdef \
--sid PA0123456789abcdef0123456789abcdef \
--status disconnected \
-o jsonCompositions
Create composition:
twilio api:video:v1:compositions:create \
--room-sid RM0123456789abcdef0123456789abcdef \
--audio-sources "*" \
--format mp4 \
--status-callback "https://video.acme.com/twilio/composition-status" \
--status-callback-method POST \
-o jsonFetch composition:
twilio api:video:v1:compositions:fetch \
--sid CJ0123456789abcdef0123456789abcdef \
-o jsonList compositions:
twilio api:video:v1:compositions:list \
--room-sid RM0123456789abcdef0123456789abcdef \
--limit 20 \
-o table \
--properties sid,status,format,dateCreatedRecordings (if applicable to your account/features)
List recordings for a room:
twilio api:video:v1:recordings:list \
--grouping-sid RM0123456789abcdef0123456789abcdef \
--limit 50 \
-o table \
--properties sid,status,trackName,dateCreatedAPI Keys (rotation support)
Create key:
twilio api:core:keys:create --friendly-name "video-key-rotation-2026-02" -o jsonList keys:
twilio api:core:keys:list -o table --properties sid,friendlyName,dateCreatedRevoke key:
twilio api:core:keys:remove --sid YOUR_API_KEY_SID---
Configuration Reference
1) OpenClaw service env file
Path (Linux systemd pattern):
/etc/openclaw/video-service.env
Example:
TWILIO_ACCOUNT_SID=YOUR_ACCOUNT_SID
TWILIO_API_KEY=YOUR_API_KEY_SID
TWILIO_API_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
VIDEO_TOKEN_TTL_SECONDS=3600
VIDEO_ROOM_TYPE=group
VIDEO_MAX_PARTICIPANTS=25
PUBLIC_BASE_URL=https://video.acme.com
COMPOSITION_STATUS_CALLBACK_URL=https://video.acme.com/twilio/composition-status
LOG_LEVEL=info2) systemd unit
Path:
/etc/systemd/system/openclaw-video.service
[Unit]
Description=OpenClaw Twilio Video Service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
EnvironmentFile=/etc/openclaw/video-service.env
WorkingDirectory=/opt/openclaw/video-service
ExecStart=/usr/bin/node /opt/openclaw/video-service/dist/server.js
Restart=on-failure
RestartSec=2
User=openclaw
Group=openclaw
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/openclaw-video
AmbientCapabilities=
CapabilityBoundingSet=
LockPersonality=true
MemoryDenyWriteExecute=true
[Install]
WantedBy=multi-user.target3) NGINX reverse proxy (TLS + webhook path)
Path:
/etc/nginx/sites-available/video.acme.com- symlink to
/etc/nginx/sites-enabled/video.acme.com
server {
listen 443 ssl http2;
server_name video.acme.com;
ssl_certificate /etc/letsencrypt/live/video.acme.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/video.acme.com/privkey.pem;
client_max_body_size 2m;
location /twilio/ {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 30s;
}
location /healthz {
proxy_pass http://127.0.0.1:3000/healthz;
}
}4) Twilio CLI config
Path:
~/.twilio-cli/config.json
Example with profiles:
{
"activeProfile": "prod",
"profiles": {
"prod": {
"accountSid": "YOUR_ACCOUNT_SID",
"apiKeySid": "YOUR_API_KEY_SID",
"apiKeySecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
"staging": {
"accountSid": "YOUR_ACCOUNT_SID",
"apiKeySid": "YOUR_API_KEY_SID",
"apiKeySecret": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
}
}
}---
Integration Patterns
Compose with Twilio Verify (step-up auth before joining a room)
Pattern:
1. User logs in with your primary auth. 2. For high-risk actions (joining a paid session, recording-enabled room), require Verify challenge. 3. Only mint Video token after Verify success.
Pseudo-flow:
POST /verify/start→ Twilio Verify V2POST /verify/checkPOST /video/token(requires verified session)
Operational notes:
- Rate limit token minting per user/IP.
- Store Verify decision with TTL (e.g., 10 minutes).
Compose with Programmable Messaging (session notifications)
Use Messaging to send:
- “Room starting” reminders
- “Recording available” link after composition completes
Production requirements:
- Handle STOP/opt-out
- Status callbacks for delivery failures
- 10DLC registration (US A2P) if using long codes
Pipeline example:
1. Composition callback completed 2. Store media in S3 3. Send SMS with link 4. Track delivery via status webhook
Compose with SendGrid (recording delivery + audit)
SendGrid transactional email:
- Dynamic template with recording link, session metadata, retention policy
- Handle bounces/spam; suppress invalid recipients
Compose with Studio (orchestration)
Use Studio Flow to orchestrate:
- Pre-session reminders
- Post-session surveys
- Escalation to support if composition fails
Trigger Studio via REST Trigger API from your backend when room completes.
CI/CD pipeline (key rotation + smoke tests)
- Rotate API keys monthly:
- Create new key
- Deploy with dual-key support (active + next)
- Switch signing key
- Revoke old key after TTL window
Smoke test script:
- Create room
- Mint token
- (Optional) headless connect test (Playwright) for JS SDK
- Complete room
- Create composition
- Verify callback received
---
Error Handling & Troubleshooting
Include these in runbooks; ensure logs capture Twilio request IDs.
1) Auth failure (bad credentials)
Error (REST):
TwilioRestException: Authenticate
HTTP 401
{"code":20003,"message":"Authenticate","more_info":"https://www.twilio.com/docs/errors/20003","status":401}Root causes:
- Wrong API Key/Secret
- Using API Key SID with Auth Token as password
- Account SID mismatch in client initialization
Fix:
- Verify
TWILIO_ACCOUNT_SID,TWILIO_API_KEY,TWILIO_API_SECRET - Ensure helper library is initialized with
accountSidwhen using API keys - Rotate compromised keys
2) Rate limiting
Error:
TwilioRestException: Too Many Requests
HTTP 429
{"code":20429,"message":"Too Many Requests","more_info":"https://www.twilio.com/docs/errors/20429","status":429}Root causes:
- Bursty room/participant polling
- Excessive composition creation retries
Fix:
- Add exponential backoff + jitter
- Cache room state; avoid tight polling loops
- Batch operations; reduce list calls
- Implement circuit breaker for Twilio API
3) Invalid room name / duplicate uniqueName
Error (typical):
TwilioRestException: The requested resource /Rooms was not found
HTTP 404Or validation errors depending on endpoint. Another common failure is attempting to create a room with an existing uniqueName while it’s in-progress.
Root causes:
- Reusing
uniqueNamefor concurrent sessions - Not completing rooms
Fix:
- Use unique per-session names (include timestamp/UUID)
- Complete rooms on session end
- If you need stable names, fetch existing room by uniqueName and reuse only if
completed
4) Token rejected (clock skew / expired)
Client error (JS SDK):
TwilioError: Access Token expired or invalidRoot causes:
- Server clock skew
- TTL too short
- Token minted with wrong signing key secret
Fix:
- Ensure NTP on token service
- Increase TTL to 3600 and refresh proactively
- Validate key rotation logic
5) Webhook signature validation fails
Your service logs:
invalid signatureRoot causes:
- Using wrong URL in validation (must match exact public URL)
- Missing/incorrect
TWILIO_AUTH_TOKEN - Proxy modifies URL/host; mismatch between internal and external URL
Fix:
- Use externally visible URL (including scheme/host/path) in validation
- Ensure NGINX sets
HostandX-Forwarded-Proto - Log computed URL and compare to Twilio request
6) Composition stuck in processing / never completes
Composition status remains processing for extended time.
Root causes:
- Very large room duration
- Layout configuration issues
- Source tracks missing or ended unexpectedly
- Twilio-side processing delays
Fix:
- Ensure room completed before composition creation (or accept longer processing)
- Use
audioSources: "*"andvideo_sources: ["*"]for broad capture - Add watchdog: if
processing> threshold (e.g., 2 hours), alert and open Twilio support ticket with Composition SID
7) Participant cannot connect behind corporate firewall
Client logs show ICE failures; typical symptom: “Connecting…” then disconnect.
Root causes:
- UDP blocked; TURN over TCP/TLS required
- Deep packet inspection interfering with WebRTC
Fix:
- Ensure outbound TCP 443 allowed (Twilio TURN over TLS)
- Provide guidance to allowlist Twilio domains
- Consider fallback to audio-only or PSTN (Twilio Voice) for extreme networks
8) Media permissions denied (browser)
Browser error:
NotAllowedError: Permission deniedRoot causes:
- User denied mic/camera
- Insecure context (not HTTPS)
- iOS Safari requires user gesture to start capture
Fix:
- Enforce HTTPS
- Prompt with clear UI and retry
- Request permissions after user interaction
9) “Participant identity already exists” (duplicate identity in same room)
Some SDKs surface:
TwilioError: Participant identity is already in useRoot causes:
- Reconnecting with same identity without disconnecting old session
- Multi-tab join without unique identity suffix
Fix:
- Enforce single session per identity (kick old participant)
- Use identity scheme:
userId:deviceIdand map to user in app layer
10) 21211 invalid To (cluster pattern relevance)
If you send SMS notifications and see:
{"code":21211,"message":"The 'To' number +1555... is not a valid phone number.","status":400}Fix:
- Normalize E.164
- Validate phone numbers before sending
- Store verified numbers only
---
Security Hardening
Secrets management
- Store
TWILIO_API_SECRETandTWILIO_AUTH_TOKENin a secrets manager: - AWS Secrets Manager / GCP Secret Manager / Vault
- Do not bake secrets into container images.
- Rotate API keys regularly; revoke unused keys.
Webhook verification
- Always validate
X-Twilio-Signature. - Enforce strict path matching; reject unexpected methods.
- Implement idempotency keys:
CompositionSid + Statusunique constraint in DB
Least privilege and isolation
- Separate services:
- Token minting service (API Key Secret)
- Webhook verification service (Auth Token)
- Use distinct Twilio API keys per environment and per service.
TLS and headers
- Enforce TLS 1.2+ (prefer TLS 1.3).
- HSTS for your domain.
- For web apps: CSP, Permissions-Policy for camera/mic.
OS hardening (CIS-aligned)
For Linux hosts (CIS Ubuntu 22.04 LTS guidance):
- Disable password SSH auth; use keys.
- Enable automatic security updates.
- Restrict outbound egress where possible (Twilio endpoints + your storage).
- systemd hardening (see unit file:
NoNewPrivileges,ProtectSystem=strict, etc.).
Abuse prevention
- Rate limit token endpoint by:
- user id
- IP
- room id
- Require authenticated session; never mint tokens anonymously.
- Validate room membership server-side; do not trust client-provided room names.
---
Performance Tuning
Reduce API calls (measurable)
Before: polling room participants every 2 seconds per active room.
- 500 rooms → 250 req/s → triggers 20429.
After: event-driven + cached state:
- Poll only on state transitions (room created/completed) or on-demand admin actions.
- Expected reduction: >90% API traffic.
Implementation:
- Cache room metadata in Redis with TTL.
- Use client-side events for participant join/leave; send to backend via your own websocket if needed.
Bandwidth profile tuning
Set maxSubscriptionBitrate to cap downstream:
- Default (uncapped): can exceed 4–6 Mbps in grid views.
- Tuned:
2.5 Mbpscap for collaboration. - Expected impact: fewer freezes on mid-tier connections; lower egress cost.
Track priority for presenter
- Set presenter video to
high, othersstandard/low. - Expected impact: presenter remains stable under congestion; non-critical tiles degrade first.
Composition cost control
- Only create compositions when needed:
- user requested recording
- compliance requirement
- Avoid composing every room by default.
Client-side capture constraints
Use 720p for presenter, 360p for others:
const localVideoTrack = await Video.createLocalVideoTrack({
width: 1280,
height: 720,
frameRate: 24
});Expected impact:
- Lower CPU on clients vs 1080p
- Better stability on integrated GPUs
---
Advanced Topics
Multi-region considerations
- Twilio Video media region selection may affect latency.
- Keep your token service region-agnostic but consider routing users to nearest region via room configuration (where supported).
- Measure RTT and join time by geography; store metrics per ISP.
Key rotation without downtime
Pattern:
- Maintain
ACTIVE_SIGNING_KEY_SIDandNEXT_SIGNING_KEY_SID. - Mint tokens with active key; accept tokens signed by either during rotation window.
- Rotate:
1. Create new key 2. Deploy config with both keys 3. Switch active 4. Wait > max TTL 5. Revoke old key
Idempotent composition creation
If your “room completed” handler can run twice:
- Use DB unique constraint on
roomSidfor composition job. - If composition already exists, skip creation.
Handling reconnect storms
When a network blip occurs, many clients reconnect simultaneously.
Mitigations:
- Token endpoint rate limiting with burst allowance
- Client backoff before reconnect
- Avoid creating new rooms on reconnect; reuse same room name
Safari/iOS constraints
- Autoplay restrictions: require user gesture before starting audio.
- Camera switching behavior differs; test with iOS 17+.
- H.264 often more reliable on Safari; validate codec negotiation.
Data tracks for control plane
Use data tracks for:
- “raise hand”
- “mute request”
- “layout hints”
But do not rely on them for authoritative moderation; enforce server-side policy.
---
Usage Examples
1) Create a scheduled group room, mint tokens, and enforce max participants
Steps: 1. Backend creates room at schedule time. 2. Users request token; backend checks enrollment. 3. Client connects with bandwidth profile.
Backend (Node) create room:
const room = await client.video.v1.rooms.create({
uniqueName: "acme-prod:webinar:2026-02-21T18-00Z",
type: "group",
maxParticipants: 100,
recordParticipantsOnConnect: false
});Token endpoint returns JWT scoped to room:
const jwt = mintVideoToken({
identity: "tenant_acme:user_42",
room: "acme-prod:webinar:2026-02-21T18-00Z",
ttlSeconds: 1800
});2) Moderator disconnects abusive participant
Admin UI calls backend:
curl -X POST https://video.acme.com/admin/rooms/RM.../participants/PA.../disconnectBackend:
await client.video.v1.rooms(roomSid).participants(participantSid)
.update({ status: "disconnected" });Audit log:
- actor identity
- participant identity
- timestamp
- reason code
3) Post-session composition + SMS notification (Messaging cluster pattern)
Flow: 1. Room completed. 2. Create composition. 3. On callback completed, store media URL, send SMS.
Composition callback handler:
- Verify signature
- If
Status=completed, enqueue jobsend_recording_sms
SMS send (ensure STOP handling + status callbacks in your messaging service).
4) Network quality-driven UI downgrade to audio-only
Client:
- Subscribe to
networkQualityLevelChanged. - If level <= 1 for > 10 seconds:
- disable local video track
- show banner
- When level >= 3 for > 20 seconds:
- re-enable video
Pseudo:
let lowSince = null;
room.on("networkQualityLevelChanged", (level) => {
const now = Date.now();
if (level <= 1) {
lowSince ??= now;
if (now - lowSince > 10000) {
room.localParticipant.videoTracks.forEach(pub => pub.track.disable());
}
} else {
lowSince = null;
}
});5) Key rotation drill (monthly)
1. Create new API key. 2. Deploy token service with both secrets. 3. Switch active signing key. 4. Validate new tokens connect. 5. After 2 hours (if TTL=3600), revoke old key.
Commands:
twilio api:core:keys:create --friendly-name "video-prod-2026-03" -o json
twilio api:core:keys:list -o table --properties sid,friendlyName,dateCreated
twilio api:core:keys:remove --sid SKoldkey...6) Incident: 20429 rate limit during peak
Mitigation steps:
- Immediately increase backoff in API callers.
- Disable non-essential polling endpoints.
- Use cached room state for dashboards.
- Postmortem: identify hot loops (participants:list) and replace with event ingestion.
---
Quick Reference
| Task | Command / API | Key flags / fields |
|---|---|---|
| List in-progress rooms | twilio api:video:v1:rooms:list | --status in-progress --limit 50 -o table |
| Create room | twilio api:video:v1:rooms:create | --unique-name ... --type group --max-participants N |
| Complete room | twilio api:video:v1:rooms:update | --sid RM... --status completed |
| List participants | twilio api:video:v1:rooms:participants:list | --room-sid RM... --status connected |
| Disconnect participant | twilio api:video:v1:rooms:participants:update | --room-sid RM... --sid PA... --status disconnected |
| Create composition | twilio api:video:v1:compositions:create | --room-sid RM... --audio-sources "*" --format mp4 --status-callback URL |
| Fetch composition | twilio api:video:v1:compositions:fetch | --sid CJ... |
| Rotate API key | twilio api:core:keys:create/remove | --friendly-name ... / --sid SK... |
| Mint token (Node) | AccessToken + VideoGrant | identity, ttl, room |
---
Graph Relationships
DEPENDS_ON
twilio-core-auth: Account SID, API Key/Secret, Auth Token handling, key rotationtwilio-webhooks: signature validation, retry/idempotency patternswebrtc-client: browser/device constraints, ICE/TURN behavior, media permissionsobservability: structured logs, request IDs, metrics, alerting
COMPOSES
twilio-messaging: SMS/WhatsApp notifications for session events, recording delivery, status callbacks, STOP handlingtwilio-verify: step-up authentication before token minting / recording accesssendgrid-email: transactional email for recording links, audit trails, bounce handlingtwilio-studio: orchestration of reminders, surveys, escalation flowstwilio-voice: PSTN fallback for audio when WebRTC fails; IVR for support
SIMILAR_TO
zoom-video-sdk(conceptually similar: rooms/sessions, tokens, recording)agora-rtc(tracks, channel join, bandwidth profiles)daily-webrtc(rooms, recording, webhooks)
Related skills
FAQ
What does the twilio-video skill cover?
Creating Group and P2P Video Rooms, issuing access tokens with video grants, publishing and moderating tracks, server-side recording via Compositions, and the Network Quality API.
What credentials does Twilio Video need?
A Twilio Account SID (AC...), an API Key SID (SK...) and secret, and a Video-enabled project; API Key plus secret is preferred over Auth Token for server-to-server.