Skip to main content
← Back to API Documentation

WebSocket API

Real-time bidirectional communication with sub-50ms latency

WebSocket
Real-time
Bidirectional
<50ms Latency

Connection Establishment

Secure WebSocket connection with authentication

WebSocket URL

wss://ws.wave.inc/v1/streams
// Using native WebSocket API
const ws = new WebSocket(
  'wss://ws.wave.inc/v1/streams?api_key=wave_live_xxxxx'
);

ws.onopen = () => {
  console.log('Connected to WAVE WebSocket');

  // Subscribe to stream events
  ws.send(JSON.stringify({
    type: 'subscribe',
    stream_id: 'stream_abc123',
    events: ['stream.started', 'analytics.update', 'chat.message']
  }));
};

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  console.log('Received:', message.type, message.payload);

  switch(message.type) {
    case 'stream.started':
      console.log('Stream went live!', message.payload);
      break;
    case 'analytics.update':
      console.log('Viewers:', message.payload.concurrent_viewers);
      break;
    case 'chat.message':
      console.log('Chat:', message.payload.username, message.payload.message);
      break;
  }
};

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

ws.onclose = (event) => {
  console.log('Disconnected:', event.code, event.reason);
};

// Send chat message
function sendMessage(text) {
  ws.send(JSON.stringify({
    type: 'chat.message',
    stream_id: 'stream_abc123',
    message: text
  }));
}

// Send reaction
function sendReaction(emoji) {
  ws.send(JSON.stringify({
    type: 'reaction.sent',
    stream_id: 'stream_abc123',
    reaction: emoji
  }));
}

Stream Events

3 event types

stream.started
Server → Client

Fired when a stream goes live

Payload Schema

{
  "stream_id": "string",
  "started_at": "ISO 8601 timestamp",
  "protocol": "string",
  "ingest_endpoint": "string"
}
stream.ended
Server → Client

Fired when a stream stops

Payload Schema

{
  "stream_id": "string",
  "ended_at": "ISO 8601 timestamp",
  "duration": "number (seconds)",
  "recording_id": "string | null"
}
stream.health
Server → Client

Real-time stream health metrics (every 5s)

Payload Schema

{
  "stream_id": "string",
  "bitrate": "number (kbps)",
  "fps": "number",
  "packet_loss": "number (0-1)",
  "latency": "number (ms)"
}

Viewer Analytics

3 event types

viewer.joined
Server → Client

New viewer joined the stream

Payload Schema

{
  "stream_id": "string",
  "viewer_id": "string",
  "country": "string",
  "device": "string",
  "joined_at": "ISO 8601 timestamp"
}
viewer.left
Server → Client

Viewer left the stream

Payload Schema

{
  "stream_id": "string",
  "viewer_id": "string",
  "watch_time": "number (seconds)",
  "left_at": "ISO 8601 timestamp"
}
analytics.update
Server → Client

Real-time analytics update (every 10s)

Payload Schema

{
  "stream_id": "string",
  "concurrent_viewers": "number",
  "total_views": "number",
  "peak_viewers": "number",
  "average_watch_time": "number"
}

Interactive Features

3 event types

chat.message
Bidirectional

Chat message sent/received

Payload Schema

{
  "stream_id": "string",
  "message_id": "string",
  "user_id": "string",
  "username": "string",
  "message": "string",
  "timestamp": "ISO 8601 timestamp"
}
reaction.sent
Client → Server

Send emoji reaction

Payload Schema

{
  "stream_id": "string",
  "reaction": "string (emoji)",
  "user_id": "string"
}
reaction.received
Server → Client

Reaction broadcast to all viewers

Payload Schema

{
  "stream_id": "string",
  "reaction": "string",
  "count": "number",
  "timestamp": "ISO 8601 timestamp"
}

Connection Management

4 event types

ping
Bidirectional

Keepalive ping/pong

Payload Schema

{
  "timestamp": "number"
}
error
Server → Client

Error notification

Payload Schema

{
  "code": "string",
  "message": "string",
  "details": "object"
}
subscribe
Client → Server

Subscribe to stream events

Payload Schema

{
  "stream_id": "string",
  "events": "string[]"
}
unsubscribe
Client → Server

Unsubscribe from stream

Payload Schema

{
  "stream_id": "string"
}

Reconnection Strategy

Handle disconnections and maintain connection reliability

Best Practices

  • Exponential backoff: Start with 1s, double each attempt, max 30s
  • Heartbeat/ping: Send ping every 30s, expect pong within 5s
  • Resubscribe: Re-subscribe to events after reconnection
  • Handle closure codes: Different codes indicate different retry strategies

WebSocket Closure Codes

1000

Normal Closure

Retry: No

1001

Going Away

Retry: Yes, after 5s

1006

Abnormal Closure

Retry: Yes, exponential backoff

4401

Invalid Auth

Retry: No - fix API key

4429

Rate Limited

Retry: Yes, after 60s

4500

Server Error

Retry: Yes, exponential backoff

Example Reconnection Logic

class WaveWebSocket {
  constructor(apiKey, streamId) {
    this.apiKey = apiKey;
    this.streamId = streamId;
    this.reconnectDelay = 1000; // Start at 1s
    this.maxReconnectDelay = 30000; // Max 30s
    this.connect();
  }

  connect() {
    this.ws = new WebSocket(
      `wss://ws.wave.inc/v1/streams?api_key=${this.apiKey}`
    );

    this.ws.onopen = () => {
      console.log('Connected');
      this.reconnectDelay = 1000; // Reset backoff
      this.subscribe();
      this.startHeartbeat();
    };

    this.ws.onclose = (event) => {
      console.log('Disconnected:', event.code);
      this.stopHeartbeat();

      // Determine if we should reconnect
      if (this.shouldReconnect(event.code)) {
        setTimeout(() => {
          this.reconnectDelay = Math.min(
            this.reconnectDelay * 2,
            this.maxReconnectDelay
          );
          this.connect();
        }, this.reconnectDelay);
      }
    };

    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };

    this.ws.onmessage = (event) => {
      const msg = JSON.parse(event.data);
      if (msg.type === 'pong') {
        this.lastPong = Date.now();
      } else {
        this.handleMessage(msg);
      }
    };
  }

  shouldReconnect(code) {
    // Don't reconnect on normal closure or auth errors
    return code !== 1000 && code !== 4401;
  }

  startHeartbeat() {
    this.heartbeatInterval = setInterval(() => {
      if (this.ws.readyState === WebSocket.OPEN) {
        this.ws.send(JSON.stringify({
          type: 'ping',
          timestamp: Date.now()
        }));

        // Check if pong received within 5s
        setTimeout(() => {
          if (Date.now() - this.lastPong > 5000) {
            console.warn('No pong received, reconnecting');
            this.ws.close(1006);
          }
        }, 5000);
      }
    }, 30000); // Every 30s
  }

  stopHeartbeat() {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
    }
  }

  subscribe() {
    this.ws.send(JSON.stringify({
      type: 'subscribe',
      stream_id: this.streamId,
      events: ['stream.started', 'analytics.update']
    }));
  }
}
WebSocket API - WAVE API Documentation | WAVE