docs
0
# Webhooks
1
2
Webhook signature verification and event parsing for secure handling of Mux platform events including video processing, live streaming, and system notifications.
3
4
## Imports
5
6
```typescript
7
import { Mux } from "@mux/mux-node";
8
9
// For webhook handling
10
const mux = new Mux({
11
webhookSecret: process.env.MUX_WEBHOOK_SECRET
12
});
13
14
// Access webhook utilities
15
const webhooks = mux.webhooks;
16
```
17
18
## Capabilities
19
20
### Webhook Verification and Parsing
21
22
Verify webhook signatures and parse webhook events with automatic type detection and validation.
23
24
```typescript { .api }
25
/**
26
* Verify webhook signature and parse event payload
27
* @param body - Raw webhook request body (string)
28
* @param headers - HTTP headers from webhook request
29
* @param secret - Webhook signing secret (optional, uses client config if not provided)
30
* @returns Parsed and verified webhook event
31
* @throws Error if signature verification fails
32
*/
33
unwrap(body: string, headers: HeadersLike, secret?: string): UnwrapWebhookEvent;
34
35
/**
36
* Verify webhook signature without parsing event
37
* @param body - Raw webhook request body (string)
38
* @param headers - HTTP headers from webhook request
39
* @param secret - Webhook signing secret (optional, uses client config if not provided)
40
* @throws Error if signature verification fails
41
*/
42
verifySignature(body: string, headers: HeadersLike, secret?: string): void;
43
44
interface HeadersProtocol {
45
get: (header: string) => string | null | undefined;
46
}
47
48
type HeadersLike = Record<string, string | string[] | undefined> | HeadersProtocol;
49
```
50
51
**Usage Examples:**
52
53
```typescript
54
import express from 'express';
55
56
const app = express();
57
58
// Webhook endpoint with raw body parsing
59
app.post('/webhooks/mux', express.raw({ type: 'application/json' }), (req, res) => {
60
try {
61
// Verify and parse webhook event
62
const event = mux.webhooks.unwrap(
63
req.body.toString(),
64
req.headers,
65
process.env.MUX_WEBHOOK_SECRET
66
);
67
68
// Handle different event types
69
switch (event.type) {
70
case 'video.asset.ready':
71
console.log('Asset ready:', event.data.id);
72
break;
73
case 'video.live_stream.active':
74
console.log('Live stream active:', event.data.id);
75
break;
76
default:
77
console.log('Unknown event type:', event.type);
78
}
79
80
res.status(200).send('OK');
81
} catch (error) {
82
console.error('Webhook verification failed:', error);
83
res.status(400).send('Invalid signature');
84
}
85
});
86
87
// Signature verification only
88
app.post('/webhooks/verify-only', express.raw({ type: 'application/json' }), (req, res) => {
89
try {
90
mux.webhooks.verifySignature(req.body.toString(), req.headers);
91
92
// Signature is valid, process the raw body as needed
93
const eventData = JSON.parse(req.body.toString());
94
console.log('Valid webhook received:', eventData.type);
95
96
res.status(200).send('OK');
97
} catch (error) {
98
res.status(400).send('Invalid signature');
99
}
100
});
101
```
102
103
## Webhook Event Types
104
105
### Video Asset Events
106
107
Events related to video asset processing and lifecycle.
108
109
```typescript { .api }
110
interface VideoAssetCreatedWebhookEvent extends BaseWebhookEvent {
111
type: 'video.asset.created';
112
data: Asset;
113
}
114
115
interface VideoAssetReadyWebhookEvent extends BaseWebhookEvent {
116
type: 'video.asset.ready';
117
data: Asset;
118
}
119
120
interface VideoAssetErroredWebhookEvent extends BaseWebhookEvent {
121
type: 'video.asset.errored';
122
data: Asset;
123
}
124
125
interface VideoAssetUpdatedWebhookEvent extends BaseWebhookEvent {
126
type: 'video.asset.updated';
127
data: Asset;
128
}
129
130
interface VideoAssetDeletedWebhookEvent extends BaseWebhookEvent {
131
type: 'video.asset.deleted';
132
data: Asset;
133
}
134
135
interface VideoAssetWarningWebhookEvent extends BaseWebhookEvent {
136
type: 'video.asset.warning';
137
data: Asset & {
138
/** Warning message */
139
warning?: string;
140
};
141
}
142
143
interface VideoAssetNonStandardInputDetectedWebhookEvent extends BaseWebhookEvent {
144
type: 'video.asset.non_standard_input_detected';
145
data: Asset;
146
}
147
148
interface VideoAssetLiveStreamCompletedWebhookEvent extends BaseWebhookEvent {
149
type: 'video.asset.live_stream_completed';
150
data: Asset;
151
}
152
153
interface VideoAssetStaticRenditionsReadyWebhookEvent extends BaseWebhookEvent {
154
type: 'video.asset.static_renditions.ready';
155
data: Asset;
156
}
157
158
interface VideoAssetStaticRenditionsPreparingWebhookEvent extends BaseWebhookEvent {
159
type: 'video.asset.static_renditions.preparing';
160
data: Asset;
161
}
162
163
interface VideoAssetStaticRenditionsDeletedWebhookEvent extends BaseWebhookEvent {
164
type: 'video.asset.static_renditions.deleted';
165
data: Asset;
166
}
167
168
interface VideoAssetStaticRenditionsErroredWebhookEvent extends BaseWebhookEvent {
169
type: 'video.asset.static_renditions.errored';
170
data: Asset;
171
}
172
173
interface VideoAssetMasterReadyWebhookEvent extends BaseWebhookEvent {
174
type: 'video.asset.master.ready';
175
data: Asset;
176
}
177
178
interface VideoAssetMasterPreparingWebhookEvent extends BaseWebhookEvent {
179
type: 'video.asset.master.preparing';
180
data: Asset;
181
}
182
183
interface VideoAssetMasterErroredWebhookEvent extends BaseWebhookEvent {
184
type: 'video.asset.master.errored';
185
data: Asset;
186
}
187
188
interface VideoAssetMasterDeletedWebhookEvent extends BaseWebhookEvent {
189
type: 'video.asset.master.deleted';
190
data: Asset;
191
}
192
```
193
194
### Live Stream Events
195
196
Events related to live streaming operations and status changes.
197
198
```typescript { .api }
199
interface VideoLiveStreamCreatedWebhookEvent extends BaseWebhookEvent {
200
type: 'video.live_stream.created';
201
data: LiveStream;
202
}
203
204
interface VideoLiveStreamConnectedWebhookEvent extends BaseWebhookEvent {
205
type: 'video.live_stream.connected';
206
data: LiveStream;
207
}
208
209
interface VideoLiveStreamActiveWebhookEvent extends BaseWebhookEvent {
210
type: 'video.live_stream.active';
211
data: LiveStream;
212
}
213
214
interface VideoLiveStreamIdleWebhookEvent extends BaseWebhookEvent {
215
type: 'video.live_stream.idle';
216
data: LiveStream;
217
}
218
219
interface VideoLiveStreamDisconnectedWebhookEvent extends BaseWebhookEvent {
220
type: 'video.live_stream.disconnected';
221
data: LiveStream;
222
}
223
224
interface VideoLiveStreamUpdatedWebhookEvent extends BaseWebhookEvent {
225
type: 'video.live_stream.updated';
226
data: LiveStream;
227
}
228
229
interface VideoLiveStreamEnabledWebhookEvent extends BaseWebhookEvent {
230
type: 'video.live_stream.enabled';
231
data: LiveStream;
232
}
233
234
interface VideoLiveStreamDisabledWebhookEvent extends BaseWebhookEvent {
235
type: 'video.live_stream.disabled';
236
data: LiveStream;
237
}
238
239
interface VideoLiveStreamDeletedWebhookEvent extends BaseWebhookEvent {
240
type: 'video.live_stream.deleted';
241
data: LiveStream;
242
}
243
244
interface VideoLiveStreamWarningWebhookEvent extends BaseWebhookEvent {
245
type: 'video.live_stream.warning';
246
data: LiveStream & {
247
/** Warning message */
248
warning?: string;
249
};
250
}
251
252
interface VideoLiveStreamRecordingWebhookEvent extends BaseWebhookEvent {
253
type: 'video.live_stream.recording';
254
data: LiveStream & { recording_start_time?: string };
255
}
256
```
257
258
### Upload Events
259
260
Events related to direct upload operations and status.
261
262
```typescript { .api }
263
interface VideoUploadCreatedWebhookEvent extends BaseWebhookEvent {
264
type: 'video.upload.created';
265
data: Upload;
266
}
267
268
interface VideoUploadCancelledWebhookEvent extends BaseWebhookEvent {
269
type: 'video.upload.cancelled';
270
data: Upload;
271
}
272
273
interface VideoUploadErroredWebhookEvent extends BaseWebhookEvent {
274
type: 'video.upload.errored';
275
data: Upload;
276
}
277
278
interface VideoUploadAssetCreatedWebhookEvent extends BaseWebhookEvent {
279
type: 'video.upload.asset_created';
280
data: Upload;
281
}
282
```
283
284
### Static Rendition Events
285
286
Events for MP4 rendition generation and processing.
287
288
```typescript { .api }
289
interface VideoAssetStaticRenditionCreatedWebhookEvent extends BaseWebhookEvent {
290
type: 'video.asset.static_rendition.created';
291
data: StaticRendition;
292
}
293
294
interface VideoAssetStaticRenditionReadyWebhookEvent extends BaseWebhookEvent {
295
type: 'video.asset.static_rendition.ready';
296
data: StaticRendition;
297
}
298
299
interface VideoAssetStaticRenditionErroredWebhookEvent extends BaseWebhookEvent {
300
type: 'video.asset.static_rendition.errored';
301
data: StaticRendition;
302
}
303
304
interface VideoAssetStaticRenditionDeletedWebhookEvent extends BaseWebhookEvent {
305
type: 'video.asset.static_rendition.deleted';
306
data: StaticRendition;
307
}
308
309
interface VideoAssetStaticRenditionSkippedWebhookEvent extends BaseWebhookEvent {
310
type: 'video.asset.static_rendition.skipped';
311
data: StaticRendition;
312
}
313
```
314
315
### Track Events
316
317
Events for subtitle and audio track operations.
318
319
```typescript { .api }
320
interface VideoAssetTrackCreatedWebhookEvent extends BaseWebhookEvent {
321
type: 'video.asset.track.created';
322
data: Track;
323
}
324
325
interface VideoAssetTrackReadyWebhookEvent extends BaseWebhookEvent {
326
type: 'video.asset.track.ready';
327
data: Track;
328
}
329
330
interface VideoAssetTrackErroredWebhookEvent extends BaseWebhookEvent {
331
type: 'video.asset.track.errored';
332
data: Track;
333
}
334
335
interface VideoAssetTrackDeletedWebhookEvent extends BaseWebhookEvent {
336
type: 'video.asset.track.deleted';
337
data: Track;
338
}
339
```
340
341
### Simulcast Events
342
343
Events for simulcast target operations during live streaming.
344
345
```typescript { .api }
346
interface VideoLiveStreamSimulcastTargetCreatedWebhookEvent extends BaseWebhookEvent {
347
type: 'video.live_stream.simulcast_target.created';
348
data: SimulcastTarget;
349
}
350
351
interface VideoLiveStreamSimulcastTargetIdleWebhookEvent extends BaseWebhookEvent {
352
type: 'video.live_stream.simulcast_target.idle';
353
data: SimulcastTarget;
354
}
355
356
interface VideoLiveStreamSimulcastTargetStartingWebhookEvent extends BaseWebhookEvent {
357
type: 'video.live_stream.simulcast_target.starting';
358
data: SimulcastTarget;
359
}
360
361
interface VideoLiveStreamSimulcastTargetBroadcastingWebhookEvent extends BaseWebhookEvent {
362
type: 'video.live_stream.simulcast_target.broadcasting';
363
data: SimulcastTarget;
364
}
365
366
interface VideoLiveStreamSimulcastTargetErroredWebhookEvent extends BaseWebhookEvent {
367
type: 'video.live_stream.simulcast_target.errored';
368
data: SimulcastTarget;
369
}
370
371
interface VideoLiveStreamSimulcastTargetDeletedWebhookEvent extends BaseWebhookEvent {
372
type: 'video.live_stream.simulcast_target.deleted';
373
data: SimulcastTarget;
374
}
375
376
interface VideoLiveStreamSimulcastTargetUpdatedWebhookEvent extends BaseWebhookEvent {
377
type: 'video.live_stream.simulcast_target.updated';
378
data: SimulcastTarget;
379
}
380
```
381
382
### System Events
383
384
Events for system-level notifications and delivery monitoring.
385
386
```typescript { .api }
387
interface VideoDeliveryHighTrafficWebhookEvent extends BaseWebhookEvent {
388
type: 'video.delivery.high_traffic';
389
data: {
390
/** Unique identifier for the delivery report */
391
id?: string;
392
/** Array of asset delivery data */
393
data?: Array<AssetDeliveryData>;
394
/** Current threshold set for alerting */
395
threshold?: number;
396
/** Time range for the report as [start, end] timestamps */
397
timeframe?: Array<number>;
398
};
399
}
400
401
interface AssetDeliveryData {
402
/** The duration of the asset in seconds */
403
asset_duration: number;
404
/** @deprecated Use asset_video_quality instead. The encoding tier that the asset was ingested at */
405
asset_encoding_tier: 'smart' | 'baseline' | 'premium';
406
/** Unique identifier for the asset */
407
asset_id: string;
408
/** The resolution tier that the asset was ingested at */
409
asset_resolution_tier: 'audio-only' | '720p' | '1080p' | '1440p' | '2160p';
410
/** The state of the asset */
411
asset_state: 'ready' | 'errored' | 'deleted';
412
/** Time at which the asset was created. Measured in seconds since the Unix epoch */
413
created_at: number;
414
/** Total number of delivered seconds during this time window */
415
delivered_seconds: number;
416
/** Seconds delivered broken into resolution tiers */
417
delivered_seconds_by_resolution: DeliveredSecondsByResolution;
418
/** The video quality that the asset was ingested at (replaces asset_encoding_tier) */
419
asset_video_quality?: 'basic' | 'plus' | 'premium';
420
/** If exists, time at which the asset was deleted. Measured in seconds since the Unix epoch */
421
deleted_at?: number;
422
/** Unique identifier for the live stream that created the asset */
423
live_stream_id?: string;
424
/** The passthrough value for the asset */
425
passthrough?: string;
426
}
427
428
interface DeliveredSecondsByResolution {
429
/** Delivered seconds within the 720p tier (up to 921,600 pixels total) */
430
tier_720p?: number;
431
/** Delivered seconds in 1080p tier (over 921,600 and <= 2,073,600 pixels) */
432
tier_1080p?: number;
433
/** Delivered seconds in 1440p tier (over 2,073,600 and <= 4,194,304 pixels) */
434
tier_1440p?: number;
435
/** Delivered seconds in 2160p tier (over 4,194,304 pixels) */
436
tier_2160p?: number;
437
/** Delivered seconds of audio only content */
438
tier_audio_only?: number;
439
}
440
```
441
442
## Base Event Structure
443
444
All webhook events extend the base webhook event interface:
445
446
```typescript { .api }
447
interface BaseWebhookEvent {
448
/** Unique identifier for the event */
449
id: string;
450
/** Event type identifier */
451
type: string;
452
/** Event creation timestamp */
453
created_at: string;
454
/** Event data payload */
455
data: unknown;
456
/** Attempts for sending out the webhook event */
457
attempts: Array<WebhookAttempt>;
458
/** Environment information */
459
environment: WebhookEnvironment;
460
/** Object metadata */
461
object: BaseWebhookEvent.Object;
462
/** @deprecated Request ID that triggered the event */
463
request_id?: string | null;
464
/** @deprecated Accessor information */
465
accessor?: string | null;
466
/** @deprecated Accessor source */
467
accessor_source?: string | null;
468
}
469
470
namespace BaseWebhookEvent {
471
interface Object {
472
/** Object identifier */
473
id: string;
474
/** Object type */
475
type: string;
476
}
477
}
478
479
interface WebhookAttempt {
480
/** Unique identifier for the webhook attempt */
481
id?: string;
482
/** URL address for the webhook attempt */
483
address?: string;
484
/** Unique identifier for the webhook */
485
webhook_id?: number;
486
/** Timestamp of the attempt */
487
created_at?: string;
488
/** Max attempts allowed */
489
max_attempts?: number;
490
/** HTTP response status code for the webhook attempt */
491
response_status_code?: number;
492
/** HTTP response headers for the webhook attempt */
493
response_headers?: unknown;
494
/** HTTP response body for the webhook attempt */
495
response_body?: string | null;
496
}
497
498
interface WebhookEnvironment {
499
/** Environment name: 'production' | 'development' */
500
name: string;
501
/** Environment identifier */
502
id: string;
503
}
504
505
/** Core data types referenced in webhook events */
506
interface Asset {
507
id: string;
508
status: 'preparing' | 'ready' | 'errored';
509
duration?: number;
510
aspect_ratio?: string;
511
created_at: string;
512
playback_ids?: PlaybackID[];
513
mp4_support?: string;
514
master_access?: string;
515
test?: boolean;
516
}
517
518
interface LiveStream {
519
id: string;
520
status: 'active' | 'idle';
521
created_at: string;
522
stream_key: string;
523
playback_ids?: PlaybackID[];
524
reconnect_window?: number;
525
max_continuous_duration?: number;
526
test?: boolean;
527
}
528
529
interface Upload {
530
id: string;
531
url: string;
532
status: 'waiting' | 'asset_created' | 'errored' | 'cancelled';
533
new_asset_settings?: Record<string, any>;
534
asset_id?: string;
535
error?: {
536
type: string;
537
message: string;
538
};
539
cors_origin?: string;
540
test?: boolean;
541
}
542
543
interface StaticRendition {
544
name: string;
545
ext: string;
546
height: number;
547
width: number;
548
bitrate: number;
549
filesize?: number;
550
}
551
552
interface Track {
553
id: string;
554
type: 'video' | 'audio' | 'text';
555
duration?: number;
556
max_width?: number;
557
max_height?: number;
558
max_frame_rate?: number;
559
text_type?: 'subtitles' | 'captions';
560
language_code?: string;
561
name?: string;
562
closed_captions?: boolean;
563
passthrough?: string;
564
}
565
566
interface SimulcastTarget {
567
id: string;
568
passthrough?: string;
569
status?: string;
570
stream_key?: string;
571
url?: string;
572
}
573
574
type UnwrapWebhookEvent =
575
| VideoAssetCreatedWebhookEvent
576
| VideoAssetReadyWebhookEvent
577
| VideoAssetErroredWebhookEvent
578
| VideoAssetUpdatedWebhookEvent
579
| VideoAssetDeletedWebhookEvent
580
| VideoAssetLiveStreamCompletedWebhookEvent
581
| VideoAssetStaticRenditionsReadyWebhookEvent
582
| VideoAssetStaticRenditionsPreparingWebhookEvent
583
| VideoAssetStaticRenditionsDeletedWebhookEvent
584
| VideoAssetStaticRenditionsErroredWebhookEvent
585
| VideoAssetMasterReadyWebhookEvent
586
| VideoAssetMasterPreparingWebhookEvent
587
| VideoAssetMasterDeletedWebhookEvent
588
| VideoAssetMasterErroredWebhookEvent
589
| VideoAssetTrackCreatedWebhookEvent
590
| VideoAssetTrackReadyWebhookEvent
591
| VideoAssetTrackErroredWebhookEvent
592
| VideoAssetTrackDeletedWebhookEvent
593
| VideoAssetStaticRenditionCreatedWebhookEvent
594
| VideoAssetStaticRenditionReadyWebhookEvent
595
| VideoAssetStaticRenditionErroredWebhookEvent
596
| VideoAssetStaticRenditionDeletedWebhookEvent
597
| VideoAssetStaticRenditionSkippedWebhookEvent
598
| VideoAssetWarningWebhookEvent
599
| VideoAssetNonStandardInputDetectedWebhookEvent
600
| VideoUploadAssetCreatedWebhookEvent
601
| VideoUploadCancelledWebhookEvent
602
| VideoUploadCreatedWebhookEvent
603
| VideoUploadErroredWebhookEvent
604
| VideoLiveStreamCreatedWebhookEvent
605
| VideoLiveStreamConnectedWebhookEvent
606
| VideoLiveStreamRecordingWebhookEvent
607
| VideoLiveStreamActiveWebhookEvent
608
| VideoLiveStreamDisconnectedWebhookEvent
609
| VideoLiveStreamIdleWebhookEvent
610
| VideoLiveStreamUpdatedWebhookEvent
611
| VideoLiveStreamEnabledWebhookEvent
612
| VideoLiveStreamDisabledWebhookEvent
613
| VideoLiveStreamDeletedWebhookEvent
614
| VideoLiveStreamWarningWebhookEvent
615
| VideoLiveStreamSimulcastTargetCreatedWebhookEvent
616
| VideoLiveStreamSimulcastTargetIdleWebhookEvent
617
| VideoLiveStreamSimulcastTargetStartingWebhookEvent
618
| VideoLiveStreamSimulcastTargetBroadcastingWebhookEvent
619
| VideoLiveStreamSimulcastTargetErroredWebhookEvent
620
| VideoLiveStreamSimulcastTargetDeletedWebhookEvent
621
| VideoLiveStreamSimulcastTargetUpdatedWebhookEvent
622
| VideoDeliveryHighTrafficWebhookEvent;
623
```
624
625
## Webhook Handler Patterns
626
627
### Event Routing
628
629
```typescript
630
function handleWebhookEvent(event: UnwrapWebhookEvent) {
631
switch (event.type) {
632
// Asset events
633
case 'video.asset.ready':
634
handleAssetReady(event.data);
635
break;
636
case 'video.asset.errored':
637
handleAssetError(event.data);
638
break;
639
640
// Live stream events
641
case 'video.live_stream.active':
642
handleStreamActive(event.data);
643
break;
644
case 'video.live_stream.idle':
645
handleStreamIdle(event.data);
646
break;
647
648
// Upload events
649
case 'video.upload.asset_created':
650
handleUploadComplete(event.data);
651
break;
652
653
default:
654
console.log('Unhandled event type:', event.type);
655
}
656
}
657
658
function handleAssetReady(asset: Asset) {
659
console.log(`Asset ${asset.id} is ready for playback`);
660
// Update database, notify users, etc.
661
}
662
663
function handleStreamActive(stream: LiveStream) {
664
console.log(`Live stream ${stream.id} is now active`);
665
// Update stream status, notify viewers, etc.
666
}
667
```
668
669
### TypeScript Type Guards
670
671
```typescript
672
function isAssetEvent(event: UnwrapWebhookEvent): event is VideoAssetReadyWebhookEvent {
673
return event.type.startsWith('video.asset.');
674
}
675
676
function isLiveStreamEvent(event: UnwrapWebhookEvent): event is VideoLiveStreamActiveWebhookEvent {
677
return event.type.startsWith('video.live_stream.');
678
}
679
680
// Usage with type safety
681
if (isAssetEvent(event)) {
682
// TypeScript knows event.data is Asset
683
console.log('Asset duration:', event.data.duration);
684
}
685
```
686
687
## Security Best Practices
688
689
- **Verify Signatures**: Always verify webhook signatures before processing events
690
- **Use HTTPS**: Configure webhook URLs to use HTTPS only
691
- **Validate Payload**: Validate the structure and content of webhook payloads
692
- **Handle Duplicates**: Implement idempotency using event IDs to handle duplicate events
693
- **Error Handling**: Return appropriate HTTP status codes (200 for success, 4xx/5xx for errors)
694
- **Secret Management**: Store webhook secrets securely and rotate them regularly