Skip to main content
← Back to API Documentation

Webhook Integration

Event-driven integrations with HMAC signature verification

Event-driven
HMAC Secured
Auto-retry

Quick Setup

Set up your first webhook in 3 steps

1

Create Endpoint

Set up HTTPS endpoint to receive events

2

Register Webhook

Register URL and select events via API

3

Verify Signatures

Validate HMAC signatures for security

Creating a Webhook

Register your webhook endpoint via API

curl -X POST https://api.wave.inc/v1/webhooks \
  -H "Authorization: Bearer wave_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/wave",
    "events": [
      "stream.started",
      "stream.ended",
      "recording.completed"
    ],
    "secret": "whsec_your_secret_key_here",
    "description": "Production webhook for stream events"
  }'

# Response: 201 Created
{
  "id": "wh_abc123xyz",
  "url": "https://your-server.com/webhooks/wave",
  "events": ["stream.started", "stream.ended", "recording.completed"],
  "status": "active",
  "created_at": "2025-11-14T10:00:00Z",
  "secret": "whsec_your_secret_key_here"
}

Available Events

Subscribe to these event types

stream.started
POST

Triggered when a stream goes live

Example Payload

{
  "event": "stream.started",
  "stream_id": "stream_abc123",
  "timestamp": "2025-11-14T10:30:00Z",
  "data": {
    "title": "Product Launch 2025",
    "protocol": "webrtc",
    "ingest_endpoint": "https://rtc.wave.inc/whip/stream_abc123"
  }
}
stream.ended
POST

Triggered when a stream stops

Example Payload

{
  "event": "stream.ended",
  "stream_id": "stream_abc123",
  "timestamp": "2025-11-14T12:30:00Z",
  "data": {
    "duration": 7200,
    "total_viewers": 15420,
    "peak_viewers": 3245,
    "recording_id": "rec_xyz789"
  }
}
recording.completed
POST

Triggered when recording processing is complete

Example Payload

{
  "event": "recording.completed",
  "stream_id": "stream_abc123",
  "recording_id": "rec_xyz789",
  "timestamp": "2025-11-14T12:45:00Z",
  "data": {
    "duration": 7200,
    "file_size": 4294967296,
    "format": "mp4",
    "resolution": "1080p",
    "download_url": "https://cdn.wave.inc/recordings/rec_xyz789.mp4?sig=..."
  }
}
recording.failed
POST

Triggered when recording processing fails

Example Payload

{
  "event": "recording.failed",
  "stream_id": "stream_abc123",
  "recording_id": "rec_xyz789",
  "timestamp": "2025-11-14T12:45:00Z",
  "data": {
    "error_code": "PROCESSING_ERROR",
    "error_message": "Video codec not supported",
    "retry_available": false
  }
}
viewer.threshold_reached
POST

Triggered when viewer count reaches configured threshold

Example Payload

{
  "event": "viewer.threshold_reached",
  "stream_id": "stream_abc123",
  "timestamp": "2025-11-14T11:00:00Z",
  "data": {
    "threshold": 1000,
    "current_viewers": 1042,
    "threshold_type": "concurrent"
  }
}
billing.usage_warning
POST

Triggered when approaching usage quota limits

Example Payload

{
  "event": "billing.usage_warning",
  "timestamp": "2025-11-14T00:00:00Z",
  "data": {
    "resource": "bandwidth",
    "usage_percentage": 85,
    "current_usage_gb": 850,
    "quota_gb": 1000,
    "period_end": "2025-11-30T23:59:59Z"
  }
}

Signature Verification (HMAC-SHA256)

Verify webhook authenticity to prevent spoofing

How Signature Works

  1. WAVE sends webhook with X-Wave-Signature header
  2. Signature is HMAC-SHA256 hash of request body using your webhook secret
  3. Your server computes the same hash and compares with the header
  4. If signatures match, the webhook is authentic
import crypto from 'crypto';
import express from 'express';

import { DesignTokens, getContainer, getSection } from '@/lib/design-tokens';
const app = express();
const WEBHOOK_SECRET = process.env.WAVE_WEBHOOK_SECRET;

// Use raw body for signature verification
app.post('/webhooks/wave',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['x-wave-signature'];
    const body = req.body.toString('utf8');

    // Compute HMAC-SHA256
    const expectedSignature = crypto
      .createHmac('sha256', WEBHOOK_SECRET)
      .update(body)
      .digest('hex');

    // Compare signatures (constant-time comparison)
    if (!crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expectedSignature)
    )) {
      console.error('Invalid signature');
      return res.status(401).send('Invalid signature');
    }

    // Signature valid - parse and process event
    const event = JSON.parse(body);
    console.log('Event received:', event.event);

    switch(event.event) {
      case 'stream.started':
        handleStreamStarted(event);
        break;
      case 'stream.ended':
        handleStreamEnded(event);
        break;
      case 'recording.completed':
        handleRecordingCompleted(event);
        break;
    }

    // Always respond quickly (< 5s)
    res.status(200).send('OK');
  }
);

function handleStreamStarted(event) {
  console.log('Stream started:', event.stream_id);
  // Your business logic here
}

function handleStreamEnded(event) {
  console.log('Stream ended:', event.stream_id);
  console.log('Duration:', event.data.duration, 'seconds');
  // Your business logic here
}

function handleRecordingCompleted(event) {
  console.log('Recording ready:', event.recording_id);
  console.log('Download:', event.data.download_url);
  // Your business logic here
}

app.listen(3000);

Retry Logic & Delivery Guarantees

Automatic retries with exponential backoff

Retry Schedule

AttemptDelayTotal Time
1st retry5 seconds5s
2nd retry30 seconds35s
3rd retry5 minutes~5m 35s
4th retry30 minutes~35m 35s
5th retry2 hours~2h 35m 35s

Best Practices

  • Respond quickly: Return 200 OK within 5 seconds, process asynchronously
  • Idempotency: Use event ID to prevent duplicate processing
  • Error handling: Return 5xx only for temporary failures
  • Logging: Log all webhooks for debugging and auditing

Testing Webhooks Locally

Tools and techniques for local webhook development

Using ngrok for Local Testing

# 1. Install ngrok
brew install ngrok  # macOS
# or download from https://ngrok.com

# 2. Start your local server
node server.js  # Running on port 3000

# 3. Create tunnel
ngrok http 3000

# Output:
# Forwarding https://abc123.ngrok.io -> http://localhost:3000

# 4. Use the HTTPS URL for webhook registration
curl -X POST https://api.wave.inc/v1/webhooks \
  -H "Authorization: Bearer wave_live_xxxxx" \
  -d '{
    "url": "https://abc123.ngrok.io/webhooks/wave",
    "events": ["stream.started"],
    "secret": "whsec_test_secret"
  }'

Testing Webhook Endpoint

# Test webhook delivery via API
curl -X POST https://api.wave.inc/v1/webhooks/wh_abc123/test \
  -H "Authorization: Bearer wave_live_xxxxx"

# Response includes delivery status
{
  "webhook_id": "wh_abc123",
  "test_event": {
    "event": "test.webhook",
    "timestamp": "2025-11-14T10:00:00Z"
  },
  "delivery": {
    "status": 200,
    "response_time_ms": 45,
    "success": true
  }
}
Webhooks - WAVE API Documentation | WAVE