# Read Room Data via WebSocket

Rooms group multiple Pulsoid users so their real-time heart rate data and membership changes can be consumed through a single WebSocket connection.

A client connects to a room's WebSocket endpoint and receives a stream of messages: heart rate updates from all room members, membership changes, and room configuration updates.

{% hint style="info" %}
Your application should implement reconnection logic. If the WebSocket connection drops, reconnect after a short delay.
{% endhint %}

***

#### Request

| Key            | Value                                                        |
| -------------- | ------------------------------------------------------------ |
| URL            | `wss://dev.pulsoid.net/api/v2/data/rooms/{roomId}/real_time` |
| Required scope | `data:room:read`                                             |

Replace `{roomId}` with the ID of the room you want to subscribe to. The connecting user must be a member of the room.

#### Authentication

Provide your OAuth2 Bearer token using one of the following methods:

| Method          | Example                                                                              |
| --------------- | ------------------------------------------------------------------------------------ |
| Query parameter | `wss://dev.pulsoid.net/api/v2/data/rooms/{roomId}/real_time?access_token=YOUR_TOKEN` |
| Header          | `Authorization: Bearer YOUR_TOKEN`                                                   |

#### Query Parameters

| Parameter      | Type     | Default      | Description                                                 |
| -------------- | -------- | ------------ | ----------------------------------------------------------- |
| `access_token` | `string` |              | OAuth2 access token (alternative to `Authorization` header) |
| `kinds`        | `string` | `heart_rate` | Comma-separated list of message kinds to subscribe to       |

#### Available Message Kinds

| Kind                  | Description                                    |
| --------------------- | ---------------------------------------------- |
| `heart_rate`          | Real-time heart rate updates from room members |
| `room_member_updated` | A member's profile or config was added/changed |
| `room_member_removed` | A member was removed from the room             |
| `room_updated`        | The room configuration was changed             |

To subscribe to multiple kinds, separate them with commas:

```
?kinds=heart_rate,room_member_updated,room_member_removed,room_updated
```

***

#### Response Messages

All messages are JSON objects with the following base structure:

| Field       | Type     | Description                                   |
| ----------- | -------- | --------------------------------------------- |
| `kind`      | `string` | The message kind (see table above)            |
| `timestamp` | `string` | ISO 8601 timestamp of when the event occurred |

Each message includes exactly one additional payload field matching its `kind`.

**`heart_rate`**

Contains the heart rate reading from a room member.

```json
{
  "kind": "heart_rate",
  "timestamp": "2026-02-21T12:00:00Z",
  "heart_rate": {
    "profile_id": "507f1f77bcf86cd799439011",
    "bpm": 85
  }
}
```

| Field                   | Type      | Description                          |
| ----------------------- | --------- | ------------------------------------ |
| `heart_rate.profile_id` | `string`  | Unique identifier of the room member |
| `heart_rate.bpm`        | `integer` | Heart rate in beats per minute       |

**`room_member_updated`**

Sent when a member is added to the room or their profile/config changes.

```json
{
  "kind": "room_member_updated",
  "timestamp": "2026-02-21T12:00:00Z",
  "room_member_updated": {
    "profile_id": "507f1f77bcf86cd799439011",
    "config": { "color": "#ff0000" }
  }
}
```

| Field                            | Type     | Description                                 |
| -------------------------------- | -------- | ------------------------------------------- |
| `room_member_updated.profile_id` | `string` | Unique identifier of the room member        |
| `room_member_updated.config`     | `object` | Arbitrary configuration data for the member |

**`room_member_removed`**

Sent when a member is removed from the room.

```json
{
  "kind": "room_member_removed",
  "timestamp": "2026-02-21T12:00:00Z",
  "room_member_removed": {
    "profile_id": "507f1f77bcf86cd799439011"
  }
}
```

| Field                            | Type     | Description                             |
| -------------------------------- | -------- | --------------------------------------- |
| `room_member_removed.profile_id` | `string` | Unique identifier of the removed member |

**`room_updated`**

Sent when the room configuration changes.

```json
{
  "kind": "room_updated",
  "timestamp": "2026-02-21T12:00:00Z",
  "room_updated": {
    "room_id": "room-abc-123",
    "config": { "theme": "dark", "layout": "grid" }
  }
}
```

| Field                  | Type     | Description                               |
| ---------------------- | -------- | ----------------------------------------- |
| `room_updated.room_id` | `string` | Identifier of the room                    |
| `room_updated.config`  | `object` | Arbitrary configuration data for the room |

***

#### Lazy Delivery of Initial State

When you first connect, you will **not** immediately receive room and member configuration messages. Instead, the server uses lazy delivery:

1. The room configuration (`room_updated`) is sent **once**, just before the first `heart_rate` message arrives.
2. Each member's configuration (`room_member_updated`) is sent **once**, when that member's first heart rate appears.

This means no messages arrive until heart rate data starts flowing, unless an explicit room or member update is triggered server-side.

{% hint style="warning" %}
If you need room and member configuration on connect, subscribe to `room_updated` and `room_member_updated` kinds. The configs will arrive automatically before the first heart rate data.
{% endhint %}

***

#### HTTP Status Codes

| Status | Description                                                                 |
| ------ | --------------------------------------------------------------------------- |
| `101`  | WebSocket upgrade successful                                                |
| `401`  | Missing or invalid token, or token does not have the `data:room:read` scope |
| `403`  | The authenticated user is not a member of the room                          |
| `500`  | Unexpected server error                                                     |

***

#### Connection Lifecycle

* The WebSocket connection will **automatically close** when the OAuth2 token expires.
* If a member is **removed from the room** while connected, their WebSocket connection is disconnected.
* The server sends only **text messages** (JSON). The client should ignore any incoming binary frames.

***

#### Example: JavaScript with `@pulsoid/socket`

For JavaScript and TypeScript applications, use the official [`@pulsoid/socket`](https://www.npmjs.com/package/@pulsoid/socket) library. It handles auto-reconnection, provides typed events, and has zero dependencies.

```bash
npm install @pulsoid/socket
```

```javascript
import PulsoidSocket from '@pulsoid/socket';

const room = PulsoidSocket.createRoom('YOUR_TOKEN', 'your-room-id');

room.on('heart-rate', (data) => {
  console.log(`${data.profileId}: ${data.bpm} BPM`);
});

room.on('room-member-updated', (data) => {
  console.log(`Member updated: ${data.profileId}`);
});

room.on('room-member-removed', (data) => {
  console.log(`Member removed: ${data.profileId}`);
});

room.on('room-updated', (data) => {
  console.log('Room config updated');
});

await room.connect();
```

See the [GitHub repository](https://github.com/pulsoid-oss/pulsoid-socket) for full documentation and configuration options.

#### Example: JavaScript with native WebSocket

```javascript
const roomId = "your-room-id";
const token = "your-access-token";
const kinds = "heart_rate,room_member_updated,room_member_removed,room_updated";

const url = `wss://dev.pulsoid.net/api/v2/data/rooms/${roomId}/real_time?access_token=${token}&kinds=${kinds}`;

const ws = new WebSocket(url);

ws.onopen = () => {
  console.log("Connected to room");
};

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);

  switch (message.kind) {
    case "heart_rate":
      console.log(`${message.heart_rate.profile_id}: ${message.heart_rate.bpm} BPM`);
      break;
    case "room_member_updated":
      console.log(`Member updated: ${message.room_member_updated.profile_id}`);
      break;
    case "room_member_removed":
      console.log(`Member removed: ${message.room_member_removed.profile_id}`);
      break;
    case "room_updated":
      console.log(`Room config updated`);
      break;
  }
};

ws.onclose = (event) => {
  console.log("Disconnected from room", event.code, event.reason);
  // Implement reconnection logic here
};

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.pulsoid.net/rooms/read-room-data-via-websocket.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
