Live Video API
Preview
Webhook

Webhooks

Subscribe to interactive room lifecycle, live stream status (started, live, disconnected, ended), participant presence, and recording events. Sendexa POSTs signed JSON so you can update UIs, attendance, and VOD archives without polling.

Real-time lifecycle

Know when rooms and live streams go active or end without polling REST.

Presence events

Track who joined and left interactive rooms with identity and role.

Broadcast events

stream.started, stream.live, stream.disconnected, stream.ended.

Recording ready

Signed download URL when room or stream VOD finishes processing.

Delivery latency

<500ms

event → first POST (p95)

Response budget

5s

return 2xx quickly

Retries

6

exponential backoff ~24h

Events

9

room · stream · recording

Event catalog

video.room.started
Interactive rooms

First participant connected; room status is now active.

video.room.ended
Interactive rooms

Room ended via API, empty timeout, or forced disconnect.

video.participant.joined
Interactive rooms

A client completed media handshake and entered the room.

video.participant.left
Interactive rooms

A participant disconnected (client leave, network, or room end).

video.stream.started
Live streaming

Encoder connected; stream status is starting (ladder building).

video.stream.live
Live streaming

Playback manifests are ready; viewers can join HLS / LL-HLS.

video.stream.disconnected
Live streaming

Encoder dropped; reconnect window is open.

video.stream.ended
Live streaming

Stream closed permanently (API end or reconnect timeout).

video.recording.ready
Rooms & streams

Room composite or stream VOD is available with a signed URL.

Payload envelope

All video webhooks share a common top-level shape. Event-specific fields live under data.

FieldTypeRequiredDescription
idstring
required
Unique delivery / event id (use for idempotency).
typestring
required
Event name, e.g. video.participant.joined
createdAtISO 8601
required
When the event occurred on Sendexa.
businessIdstring
required
Your business / account id.
dataobject
required
Event payload (room, participant, or recording fields).
JSON
{
"id": "evt_vid_01J0ABCXYZ",
"type": "video.participant.joined",
"createdAt": "2026-07-12T10:16:45.120Z",
"businessId": "BU-VOIF86X7",
"apiVersion": "v1",
"data": { }
}
Event payloads

Fired once when the room transitions from ready active.

JSON
{
"id": "evt_vid_01J0START",
"type": "video.room.started",
"createdAt": "2026-07-12T10:16:02.000Z",
"businessId": "BU-VOIF86X7",
"apiVersion": "v1",
"data": {
"roomId": "rm_8f3a2c1b0e9d",
"name": "Support Session #1042",
"status": "active",
"region": "af-west",
"record": true,
"maxParticipants": 4,
"participantCount": 1,
"startedAt": "2026-07-12T10:16:02.000Z",
"metadata": { "ticketId": "TKT-1042" },
"firstParticipant": {
"identity": "agent-ada",
"role": "host"
}
}
}

HTTP headers

HeaderExampleDescription
Content-Typeapplication/jsonJSON body
X-Sendexa-Signaturesha256=abc123...HMAC-SHA256 of raw body with webhook secret
X-Sendexa-Timestamp2026-07-12T10:16:45.120ZSend time — reject if too old (replay protection)
X-Webhook-IDwhk_vid_991Delivery id (distinct from event id on retries)
X-Attempt1Delivery attempt number (1–6)

Verify signatures

JavaScript
import crypto from 'crypto';
import express from 'express';
const app = express();
// Important: verify against the raw body
app.post(
'/webhooks/sendexa-video',
express.raw({ type: 'application/json' }),
(req, res) => {
const secret = process.env.SENDEXA_WEBHOOK_SECRET;
const signature = req.get('X-Sendexa-Signature') || '';
const raw = req.body; // Buffer
const expected =
'sha256=' +
crypto.createHmac('sha256', secret).update(raw).digest('hex');
const ok =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!ok) {
return res.status(401).send('invalid signature');
}
const event = JSON.parse(raw.toString('utf8'));
// Idempotency: skip if event.id already processed
switch (event.type) {
case 'video.room.started':
// mark session live
break;
case 'video.room.ended':
// close ticket timer
break;
case 'video.participant.joined':
case 'video.participant.left':
// attendance
break;
case 'video.recording.ready':
// enqueue download of event.data.url
break;
default:
break;
}
// Respond fast — heavy work goes to a queue
res.status(200).json({ received: true });
}
);
Retry policy

Non-2xx responses or timeouts (>5s) trigger retries with exponential backoff. Deliveries are at-least-once — design handlers to be idempotent using id / X-Webhook-ID.

AttemptDelayDescription
15 secondsImmediate follow-up
230 secondsSecond attempt
35 minutesThird attempt
430 minutesFourth attempt
52 hoursFifth attempt
66 hoursFinal attempt within 24h window
Handler best practices

Respond quickly

Return 2xx within 5 seconds. Push downloads and CRM updates to a queue.

Idempotent processing

Store processed event ids. Retries and network duplicates are expected.

Archive recordings

On recording.ready, fetch the signed URL immediately and store in your bucket.

Join metadata

Correlate room.metadata.ticketId (or similar) to update the right domain object.