> ## Documentation Index
> Fetch the complete documentation index at: https://www.vobiz.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Vobiz Messaging WebSocket - Real-Time Events

> Stream live messaging events from Vobiz over a WebSocket - inbound messages, delivery status, broadcast progress, 10DLC brand and campaign changes and number purchases - with Node and browser examples, reconnect strategy and backfill.

The messaging WebSocket pushes events to a connected client as they happen: a reply arriving, a delivery receipt, a broadcast counter moving, a 10DLC campaign changing stage, a number being purchased. It is built for dashboards and consoles that are open on screen. For events your backend must never miss, use [webhooks](/docs/sms/webhooks); they are redelivered until accepted.

## Connect

```
wss://api.vobiz.ai/api/v1/sms/ws
```

Authentication depends on where the client runs:

| Client | How to authenticate |
| - | - |
| Server (Node, Python, Go) | Send `X-Auth-ID` and `X-Auth-Token` as headers on the HTTP upgrade request, exactly as on a REST call. |
| Browser | Browsers cannot set custom headers on a WebSocket. Pass your console session JWT as `?token=<jwt>` in the URL instead. |

The connection is scoped to your account. You receive every event for the account; there is no per-topic subscription. Anything you send to the server is read and ignored, so you can send a small frame periodically as a keep-alive.

<Warning>
  Never put your Auth Token in browser code. The `?token=` form is for the console session JWT only. If you need live events in your own web app, terminate the WebSocket on your server and relay to the browser.
</Warning>

## Frames

Every frame from the server is a JSON text message with two keys:

```json theme={null}
{
  "event": "message.received",
  "data": { }
}
```

`event` is one of the six names below. `data` is the full object the event concerns, so you can update your UI without a follow-up request.

| Event | `data` | When |
| - | - | - |
| `message.received` | A [message](/docs/sms/api/messages/get-message) with `direction: in` and `status: received` | A contact messaged one of your numbers or RCS senders |
| `message.status` | A message with its updated `status`, and `errorCode` / `errorMessage` when present | A delivery report arrived for a message you sent |
| `broadcast.updated` | A [broadcast](/docs/sms/api/broadcasts/get-broadcast) including `stats` | The broadcast changed status, and periodically while it is running |
| `10dlc.brand.updated` | A [brand](/docs/sms/api/10dlc/get-brand) | The registry changed the brand's stage or status |
| `10dlc.campaign.updated` | A [campaign](/docs/sms/api/10dlc/get-campaign) | The registry changed the campaign's stage |
| `numbers.updated` | `{"e164": "+14155550100", "action": "purchased"}` or `"released"` | A number was bought or released on the account |

Example frames:

```json message.status theme={null}
{
  "event": "message.status",
  "data": {
    "id": "9b2e1c40-8a3d-4f5e-9c1a-7d6b2e8f4a01",
    "accountId": "MA_XXXXXXXX",
    "direction": "out",
    "channel": "sms",
    "from": "+14155550100",
    "to": "+14155550123",
    "text": "Acme: Your order #4821 has shipped. Reply STOP to opt out.",
    "mediaUrls": null,
    "status": "delivered",
    "conversationId": "e7d6c5b4-a3f2-4e1d-9c0b-8a7f6e5d4c3b",
    "createdAt": "2026-10-12T10:30:00.123456Z",
    "updatedAt": "2026-10-12T10:30:04.512000Z"
  }
}
```

```json numbers.updated theme={null}
{
  "event": "numbers.updated",
  "data": {
    "e164": "+14155550100",
    "action": "purchased"
  }
}
```

## Node example

Install the `ws` package (`npm install ws`), then:

```javascript Node theme={null}
import WebSocket from "ws";

const URL = "wss://api.vobiz.ai/api/v1/sms/ws";
const headers = {
  "X-Auth-ID": "MA_XXXXXXXX",
  "X-Auth-Token": "YOUR_AUTH_TOKEN",
};

let attempt = 0;

function connect() {
  const ws = new WebSocket(URL, { headers });

  ws.on("open", () => {
    attempt = 0;
    console.log("connected");
    // Backfill anything that happened while we were offline
    backfill().catch(console.error);
  });

  ws.on("message", (raw) => {
    const { event, data } = JSON.parse(raw.toString());
    switch (event) {
      case "message.received":
        console.log("inbound", data.from, data.text);
        break;
      case "message.status":
        console.log("status", data.id, data.status, data.errorCode ?? "");
        break;
      case "broadcast.updated":
        console.log("broadcast", data.id, data.status, data.stats);
        break;
      case "10dlc.brand.updated":
      case "10dlc.campaign.updated":
        console.log(event, data.id, data.stage);
        break;
      case "numbers.updated":
        console.log("number", data.action, data.e164);
        break;
    }
  });

  ws.on("close", scheduleReconnect);
  ws.on("error", (err) => {
    console.error(err.message);
    ws.close();
  });

  // Keep-alive: the server ignores inbound frames, so any small frame works
  const ping = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) ws.send("ping");
  }, 30_000);
  ws.on("close", () => clearInterval(ping));
}

function scheduleReconnect() {
  const delay = Math.min(30_000, 1000 * 2 ** attempt) + Math.random() * 500;
  attempt += 1;
  console.log(`reconnecting in ${Math.round(delay)} ms`);
  setTimeout(connect, delay);
}

let lastSeen = new Date().toISOString();

async function backfill() {
  const since = lastSeen;
  lastSeen = new Date().toISOString();
  const res = await fetch(
    `https://api.vobiz.ai/api/v1/sms/messages?startDate=${encodeURIComponent(since)}&limit=100`,
    { headers }
  );
  if (!res.ok) throw new Error(await res.text());
  const { messages } = await res.json();
  for (const m of messages) console.log("backfill", m.id, m.direction, m.status);
}

connect();
```

## Browser example

In the browser you pass the console session JWT in the query string. Here `sessionToken` is the JWT your signed-in console session holds.

```javascript Browser theme={null}
const sessionToken = window.__VOBIZ_SESSION_JWT__; // obtained from your signed-in session
const url = `wss://api.vobiz.ai/api/v1/sms/ws?token=${encodeURIComponent(sessionToken)}`;

let attempt = 0;

function connect() {
  const ws = new WebSocket(url);

  ws.onopen = () => {
    attempt = 0;
    console.log("connected");
  };

  ws.onmessage = (ev) => {
    const { event, data } = JSON.parse(ev.data);
    if (event === "message.received") {
      appendToInbox(data); // your UI code
    } else if (event === "message.status") {
      updateStatusBadge(data.id, data.status); // your UI code
    } else if (event === "broadcast.updated") {
      renderProgress(data.id, data.stats); // your UI code
    }
  };

  ws.onclose = () => {
    const delay = Math.min(30000, 1000 * 2 ** attempt) + Math.random() * 500;
    attempt += 1;
    setTimeout(connect, delay);
  };

  ws.onerror = () => ws.close();
}

connect();
```

## Reconnect and backfill

A WebSocket drops when networks change, proxies time out or the server restarts. Plan for it:

<Steps>
  <Step title="Reconnect with exponential backoff">
    Start at about one second, double on each failed attempt, cap at 30 seconds, and add jitter so many clients do not reconnect at the same instant. Reset the counter after a successful connection.
  </Step>

  <Step title="Backfill after reconnecting">
    Events emitted while you were disconnected are not replayed on the socket. Record the time of the last frame you processed and, on reconnect, call [`GET /messages`](/docs/sms/api/messages/list-messages) with `startDate` set to that time to pick up messages and status changes. For broadcasts and registrations, re-read the objects you have on screen with [`GET /broadcasts/{id}`](/docs/sms/api/broadcasts/get-broadcast), [`GET /10dlc/brands/{id}`](/docs/sms/api/10dlc/get-brand) and [`GET /10dlc/campaigns/{id}`](/docs/sms/api/10dlc/get-campaign).
  </Step>

  <Step title="Keep the connection alive">
    Send a small frame every 30 seconds or so. The server reads and discards client frames, and the traffic keeps idle-connection timeouts on intermediaries from closing the socket.
  </Step>

  <Step title="Use webhooks for anything that must be durable">
    If your backend acts on an event, for example marking an order as notified when `message.status` reaches `delivered`, subscribe to it with a [webhook](/docs/sms/webhooks). Webhooks are redelivered until your endpoint accepts them; the socket is a live view.
  </Step>
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Webhooks" icon="webhook" href="/docs/sms/webhooks">
    Durable, signed delivery to your server.
  </Card>

  <Card title="Conversations" icon="comments" href="/docs/sms/conversations">
    Build an inbox from `message.received` events.
  </Card>
</CardGroup>

## Before you go live

<CardGroup cols={2}>
  <Card title="Compliance overview" icon="shield-check" href="/docs/sms/compliance/overview">
    The registration events you will see stream in.
  </Card>

  <Card title="Messaging numbers" icon="hashtag" href="/docs/sms/numbers">
    Purchases and releases behind `numbers.updated`.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.