or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

analytics-metrics.mdclient-setup.mddata.mddelivery-usage.mderror-handling.mdindex.mdjwt-signing.mdjwt.mdlive-streaming.mdplayback-control.mdsystem-operations.mdsystem.mdtranscription-vocabularies.mdupload-utilities.mdvideo-assets.mdvideo-playback.mdvideo-uploads.mdvideo.mdweb-inputs.mdwebhooks.md

webhooks.mddocs/

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