ReStream delivers events over a single WebSocket endpoint. Clients must connect, subscribe to channels, and decode the incoming Protocol Buffer payloads.
Production endpoint: wss://restream.bags.fm
This is a more complex guide on how to connect to ReStream manually, ideally, for most use cases, using the SDK is easier and more practical. You can read more about how to connect using the SDK here.

Message Format

Each event is serialized with the following structure: {topic}:{subject};{varint_len}{protobuf_payload}
  • topic: The event type (e.g. launchpad_launch)
  • subject: The subject of the event (e.g. BAGS)
  • ;: A delimiter separating the channel from the payload
  • varint_len: Length of the protobuf payload, encoded as a varint
    • You can read more about delimited protobuf events here.
  • protobuf_payload: The actual event, encoded as a Protocol Buffer message
Head to #supported-events for more details on topics and subjects.
You must parse the prefix topic:subject; before decoding the protobuf payload.

Connecting

Before you can subscribe to events, you need to establish a WebSocket connection to the ReStream endpoint.

Prerequisites

npm install ws protobufjs

Establishing connection

import WebSocket from "ws";

const ws = new WebSocket("wss://restream.bags.fm");

ws.on("open", () => {
  console.log("Connected to ReStream");

  // Start ping task - Check next chapter for infos
  // startPingTask(ws);

  // Subscribe to a launchpad channel
  ws.send(
    JSON.stringify({
      type: "subscribe",
      event: "launchpad_launch:BAGS",
    })
  );
});

Decoding events

import { Reader } from "protobufjs/minimal";
import { LaunchpadLaunchEvent } from "./gen/restream_events";

/**
 * Decode a LaunchpadLaunchEvent from a serialized buffer.
 *
 * Buffer format:
 *   [topic][":" delimiter][subject][";" suffix][varint length][protobuf payload]
 */
export function decodeLaunchpadLaunchEvent(
  buf: Uint8Array
): LaunchpadLaunchEvent {
  // 1. Find the SUFFIX marker (`;`)
  const suffixByte = ";".charCodeAt(0);
  let suffixIndex = -1;
  for (let i = 0; i < buf.length; i++) {
    if (buf[i] === suffixByte) {
      suffixIndex = i;
      break;
    }
  }
  if (suffixIndex === -1) {
    throw new Error("Invalid buffer: SUFFIX ';' not found");
  }

  // 2. Slice after SUFFIX
  const afterSuffix = buf.subarray(suffixIndex + 1);

  // 3. Decode using protobufjs's decodeDelimited
  return LaunchpadLaunchEvent.decodeDelimited(afterSuffix);
}

Connection Requirements

Clients must send a ping message at least once every minute to keep their connection alive. Connections that don’t ping within 60 seconds will be automatically disconnected. It’s recommended to set up a ping interval:
// Send ping every 30 seconds to ensure connection stays alive
setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ type: 'ping' }));
  }
}, 30000);