Twilio Video Integration for Website Video Calls
Teams often encounter challenges when adding video calls to a website with Twilio Video. The complexity involves server setup, token generation, and building a client component. Typical mistakes include incorrect token TTL, ignoring reconnection handling, and suboptimal room type selection. For instance, a client lost up to 30% of sessions due to a too short TTL (5 minutes instead of 3600). After implementing refresh and correct handling of the DisconnectedEvent, fault tolerance rose to 99.9%. Another client—a telemedicine service—lost up to 20% of calls due to improper reconnection handling. We implemented a retry mechanism and correct event subscription, increasing successful call rate to 99.5%. Our integration experience spans 5+ years and 20+ projects. We guarantee stable operation and support during development.
Why Twilio Video is Better Than Ready-Made Solutions
Unlike Zoom SDK or Jitsi, Twilio Video gives you full control over the interface and logic. You are not tied to a standard participant grid—you can implement your own grid, switch between speakers, add effects, or moderation. Additionally, the Twilio ecosystem allows you to combine video with telephony and SMS, which is convenient for telemedicine or consultation services. API integration with the platform is straightforward thanks to REST API and SDKs. The service is based on WebRTC, ensuring compatibility with most browsers and mobile devices.
How to Manage Participants in a Room
After connecting to a room, each participant is represented by a RemoteParticipant object. The events participantConnected and participantDisconnected are tracked at the room level. For each remote participant, subscribe to trackSubscribed and trackUnsubscribed to add/remove video streams. This allows you to dynamically update the grid. It is important to handle cases where a participant switches their camera—the track changes, and you need to reattach it to the DOM.
Twilio Video Integration in a React Application
The integration process involves several steps. Let's go through each using TypeScript and React 18.
Step 1: Server setup—creating rooms and generating Access Tokens.
Step 2: Developing the React component—connecting to the room and displaying streams.
Step 3: Handling call recording—enabling recording and obtaining links.
Step 1: Creating a Room and Access Token on the Server
npm install twilio
import twilio from 'twilio';
const AccessToken = twilio.jwt.AccessToken;
const VideoGrant = AccessToken.VideoGrant;
const client = twilio(
process.env.TWILIO_ACCOUNT_SID!,
process.env.TWILIO_AUTH_TOKEN!
);
// Create a room
async function createRoom(name: string) {
const room = await client.video.v1.rooms.create({
uniqueName: name,
type: 'group', // 'go' | 'peer-to-peer' | 'group' | 'group-small'
maxParticipants: 10,
recordParticipantsOnConnect: false,
statusCallback: `${process.env.APP_URL}/api/webhooks/twilio-video`,
statusCallbackMethod: 'POST',
});
return room.sid;
}
// Issue a token to a participant
function generateVideoToken(identity: string, roomName: string): string {
const token = new AccessToken(
process.env.TWILIO_ACCOUNT_SID!,
process.env.TWILIO_API_KEY!,
process.env.TWILIO_API_SECRET!,
{ identity, ttl: 3600 }
);
const grant = new VideoGrant({ room: roomName });
token.addGrant(grant);
return token.toJwt();
}
// API endpoint
app.post('/api/video/join', authenticate, async (req, res) => {
const { roomName } = req.body;
// Ensure room exists or create it
try {
await client.video.v1.rooms(roomName).fetch();
} catch {
await createRoom(roomName);
}
const token = generateVideoToken(req.user.id, roomName);
res.json({ token, roomName });
});
Step 2: React Component with Twilio Video JS SDK
npm install twilio-video
import { connect, Room, LocalVideoTrack } from 'twilio-video';
import { useEffect, useRef, useState } from 'react';
function TwilioVideoRoom({ token, roomName }: { token: string; roomName: string }) {
const [room, setRoom] = useState<Room | null>(null);
const [participants, setParticipants] = useState<string[]>([]);
const localVideoRef = useRef<HTMLVideoElement>(null);
useEffect(() => {
let connectedRoom: Room;
connect(token, {
name: roomName,
audio: true,
video: { width: 1280, height: 720 },
}).then((room) => {
connectedRoom = room;
setRoom(room);
// Display local video
const localTrack = [...room.localParticipant.videoTracks.values()][0]?.track;
if (localTrack && localVideoRef.current) {
localVideoRef.current.srcObject = new MediaStream([localTrack.mediaStreamTrack]);
}
// Handle participants
room.participants.forEach((p) => {
setParticipants(prev => [...prev, p.identity]);
});
room.on('participantConnected', (p) => {
setParticipants(prev => [...prev, p.identity]);
p.on('trackSubscribed', (track) => {
if (track.kind === 'video') {
const el = document.getElementById(`participant-${p.identity}`);
if (el) track.attach(el as HTMLVideoElement);
}
});
});
room.on('participantDisconnected', (p) => {
setParticipants(prev => prev.filter(id => id !== p.identity));
});
});
return () => {
connectedRoom?.disconnect();
};
}, [token, roomName]);
return (
<div className="grid grid-cols-2 gap-4">
<div className="relative">
<video ref={localVideoRef} autoPlay muted playsInline
className="w-full rounded-xl" />
<span className="absolute bottom-2 left-2 text-white text-sm bg-black/50 px-2 py-1 rounded">
You
</span>
</div>
{participants.map(identity => (
<div key={identity} className="relative">
<video id={`participant-${identity}`} autoPlay playsInline
className="w-full rounded-xl" />
<span className="absolute bottom-2 left-2 text-white text-sm bg-black/50 px-2 py-1 rounded">
{identity}
</span>
</div>
))}
</div>
);
}
Step 3: Call Recording
// Enable recording for a room
async function enableRoomRecording(roomSid: string) {
await client.video.v1.rooms(roomSid).recordings.create({
// Records all participants
});
}
// Get recording link after the call
async function getRoomRecordings(roomSid: string) {
const recordings = await client.video.v1.rooms(roomSid).recordings.list();
return recordings.map(r => ({
sid: r.sid,
duration: r.duration,
url: `https://video.twilio.com/v1/Recordings/${r.sid}/Media`,
}));
}
Choosing a Room Type
Room type selection depends on the scenario. Peer-to-Peer (P2P) is suitable for one-on-one video calls: latency under 150 ms, no server processing, but limited to 2 participants. Group Small supports up to 4 participants with moderate latency. Group supports up to 50 participants, recording and tracking, but latency up to 300 ms. If you plan webinars, use Group and enable recording.
Room Type Comparison
| Room Type | Max Participants | Latency | Features |
|---|---|---|---|
| Peer-to-Peer | 2 | <150 ms | Low latency, no server processing |
| Group Small | 4 | <200 ms | Balance of performance and participant count |
| Group | 50 | <300 ms | Full conferences, recording, tracking |
Key Configuration Parameters
| Parameter | Value | Comment |
|---|---|---|
type |
peer-to-peer, group-small, group |
Room type determines architecture |
maxParticipants |
2-50 | Maximum simultaneous participants |
ttl |
3600 (s) | Access Token lifetime, recommended 1 hour |
recordParticipantsOnConnect |
true/false |
Automatic recording on connect |
videoDimensions |
1280x720 | Video resolution, affects bandwidth |
What's Included in the Integration Work
- Twilio account setup and API keys
- Server endpoints for room creation and token generation
- React component with custom UI for displaying participants
- Call recording and webhooks integration for event handling
- Deployment and support documentation
- Team training on SDK usage
Typical Mistakes and How to Avoid Them
- Token expiration during a call: set
ttlto at least 3600 seconds and implement a refresh mechanism via server events. - N+1 queries when retrieving recording list: use
Promise.allor pagination. - Out-of-sync video grid: subscribe to
trackSwitchedfor correct DOM updates. - Ignoring connection loss handling: use a retry mechanism with exponential backoff. If you encounter similar issues, contact us—we will help resolve them.
Timelines and Cost
Basic Twilio Video integration + React component + Access Token — 2–3 days. With participant management, recording, and webhooks — 4–5 days. Integration cost generally ranges from $2,500 to $5,000 for a full custom solution, depending on UI complexity and additional features like recording or custom moderation. Contact us for a free consultation and exact estimate.
Factors Affecting Cost
Integration cost depends on the complexity of the custom UI, need for recording and webhooks, number of room types, and integration with other services. We calculate the cost individually after analyzing requirements. Typical per-minute costs for video usage range from $0.004 to $0.01 per participant, depending on resolution and recording. Contact us for an accurate estimate.
For more details, see Check the official documentation.







