WhaBuzzdocs
Guides

Push stream

Receive events over a WebSocket, with replay from the last event you processed.

The push stream delivers the same events as webhooks over one WebSocket you open from your server. It needs no public URL, so it suits workers behind a firewall and local development. Use it next to webhooks or instead of them.

wss://api.whabuzz.com/v1/stream?since=<seq> is a WebSocket endpoint, not a REST endpoint, so it is not an operation in the OpenAPI file.

#Connect

Authenticate with your API key in the Sec-WebSocket-Protocol header: offer whabuzz.v1 and then the key. This works in browsers and in the standard WebSocket of Node 22 and later, which cannot set an Authorization header. Server-side clients that can set headers may send Authorization: Bearer wb_live_... instead.

const key = process.env.WHABUZZ_API_KEY
let since = 0 // persist this between runs

function connect() {
  const ws = new WebSocket(`wss://api.whabuzz.com/v1/stream?since=${since}`, ['whabuzz.v1', key])
  ws.onmessage = (m) => {
    const frame = JSON.parse(m.data)
    if (frame.type === 'stream.replay_truncated') return // the socket closes next; reconnect from since
    handle(frame) // your code; dedupe on frame.dedupe_key
    since = frame.seq
  }
  ws.onclose = (e) => {
    if (e.code === 4429) return console.error('too many open streams for this tenant')
    setTimeout(connect, 1000) // reconnect; since resumes where you stopped
  }
  setInterval(() => ws.readyState === 1 && ws.send('ping'), 25_000) // answered with "pong"
}
connect()
js

#Frames

Each frame is one JSON text message: the exact webhook envelope plus seq, the event's position in your tenant's own log. seq only grows. Frames arrive in seq order, with no duplicates within one connection. Delivery is at least once across reconnects, so keep deduplicating on dedupe_key.

#Resume with since

Pass since=<seq> (the seq of the last frame you processed) to receive everything after it, oldest first, and then live events on the same socket. With no since, you get live events only. A since beyond the newest event is treated as the newest event.

A single connection replays at most 10,000 events. If you are further behind, the stream sends {"type":"stream.replay_truncated","last_seq":N} and closes with code 1000. Reconnect with since=N and carry on.

Replayed frames are rebuilt from your event log, so display_phone_number can be null and raw can be null when the raw body has moved to archive storage. Live frames carry the full envelope. Fetch the event by id from the events API when you need the archived body.

#Limits and close codes

CodeMeaning
1000Normal close, including replay_truncated. Reconnect with since.
4429Tenant is at its 10-stream cap.

Before the socket opens, errors are ordinary HTTP responses: 401 for a bad key, 403 for a suspended tenant, 400 for a bad since, 426 when the request is not a WebSocket upgrade.

#Sandbox

With a wb_test_ key, an open stream counts as a connected webhook endpoint, so the tenant shows as connected without a public URL. Live keys are unchanged: a live tenant still needs a webhook endpoint to count as connected.

This page as Markdown: /stream.md. All docs in one file: /llms-full.txt.