> For the complete documentation index, see [llms.txt](https://dnotifier.gitbook.io/product-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://dnotifier.gitbook.io/product-docs/getting-started/understanding-messages.md).

# Understanding messages

Every incoming realtime message in DNotifier arrives as a structured object with **metadata** (routing and envelope fields) and **payload** (your application data). This page is language-agnostic — the same concepts apply to JavaScript, Dart, and future SDKs.

## Message shape

```
┌─────────────────────────────────────┐
│            DNotifierMessage          │
├──────────────────┬──────────────────┤
│    metadata      │     payload      │
│  (envelope)      │  (your data)     │
└──────────────────┴──────────────────┘
```

Your SDK delivers this object to the **`onMessage`** callback (or `onMessage` property in Dart) whenever a WebSocket frame is reassembled and parsed.

## Metadata

Metadata describes the message envelope — who sent it, when, and how DNotifier classified the frame.

| Field       | Type              | Description                                                      |
| ----------- | ----------------- | ---------------------------------------------------------------- |
| `id`        | string (optional) | Unique message identifier when assigned by the server            |
| `sender`    | string            | Prefixed sender address (combines `appId` and sender's `userId`) |
| `timestamp` | number            | Unix epoch milliseconds when the message was sent                |
| `type`      | string (optional) | Frame or content hint from the transport layer                   |

### Reading the sender

The `sender` field is the canonical address of the message origin. Compare it to your known user ids or map it back to your app's user table. When you send a message, recipients see your prefixed sender id in their `onMessage` handler.

Example metadata (conceptual):

```json
{
  "id": "msg_abc123",
  "sender": "app_xyz:user-alice",
  "timestamp": 1717536000123,
  "type": "text"
}
```

Exact field names and optional fields are consistent across SDKs; refer to your language reference for typed definitions.

## Payload

The payload is the **body** of the message — whatever you placed in `data` when calling `send()`, or what another service sent to you.

SDKs wrap raw bytes in a **Payload** helper with conversion methods:

| Method       | Use when                                                        |
| ------------ | --------------------------------------------------------------- |
| `toJSON()`   | Payload is JSON (most text and structured messages)             |
| `toString()` | Payload is plain text                                           |
| `toBase64()` | You need base64 representation (e.g. binary embedded as string) |
| `raw()`      | You need raw bytes (images, audio, custom binary)               |

### Typical text message payload

When the sender called:

```js
send({
  senderId: "user-alice",
  receiverId: "user-bob",
  data: { type: "text", text: "Hello!" },
});
```

The receiver's `onMessage` handler might do:

```js
onMessage: (msg) => {
  const body = msg.payload.toJSON();
  // body === { type: "text", text: "Hello!" }
}
```

Dart equivalent:

```dart
onMessage: (msg) {
  final body = msg.payload.toJSON();
  // body is a Map with type and text keys
},
```

## Message types (`data.type`)

DNotifier does not enforce a fixed enum for application payload types. You define conventions in your app. Common patterns:

| `data.type`    | Typical fields                    | Use case                               |
| -------------- | --------------------------------- | -------------------------------------- |
| `text`         | `text`                            | Chat messages, notifications           |
| `image`        | `content` (bytes or base64)       | Photo sharing                          |
| `audio`        | `content`, `durationMs`           | Voice notes                            |
| `doc`          | `content`, `fileName`, `mimeType` | File attachments                       |
| Custom strings | Your schema                       | Typing indicators, reactions, commands |

The transport may also set metadata `type` for framing; distinguish **metadata.type** (envelope) from **payload.type** (your app schema) in handlers when both are present.

## The onMessage handler

Register `onMessage` when constructing the client:

```js
onMessage: (msg) => {
  const { sender, timestamp } = msg.metadata;
  const body = msg.payload.toJSON();

  switch (body?.type) {
    case "text":
      console.log(`${sender}: ${body.text}`);
      break;
    case "image":
      // handle body.content
      break;
    default:
      console.log("Unknown type", body);
  }
},
```

Best practices:

1. **Never assume JSON** — call `toJSON()` inside try/catch or check content type hints first
2. **Validate `body.type`** — ignore or log unknown types safely
3. **Do not block** — offload heavy work (image decode, disk write) to async tasks
4. **Idempotency** — use `metadata.id` to deduplicate if your app may receive retries

## Large messages and chunking

Payloads larger than your plan's `messageSizeLimit` are split into chunks automatically by the SDK on send and reassembled before `onMessage` fires on receive. Your handler always sees a complete payload — you do not process individual chunks unless using low-level binary APIs.

## Messages vs HTTP responses

This page covers **WebSocket-delivered** messages via `onMessage`. HTTP transport methods (`sendAI`, `search`, `runWorkflow`, etc.) return responses directly from the async method call — they do not flow through `onMessage`.

Some APIs (like `fetchChatHistory`) trigger a response that **does** arrive via `onMessage` even on WebSocket clients. Consult the chat and AI docs for those patterns.

## Quick reference diagram

```
  send({ data: { type, ... } })
              │
              ▼
        DNotifier cloud
              │
              ▼
  onMessage({ metadata, payload })
              │
              ├── metadata.sender    → who
              ├── metadata.timestamp → when
              ├── metadata.id        → dedupe key
              └── payload.toJSON()   → your { type, ... }
```

## Next steps

* [**Send & receive text**](/product-docs/realtime-communication/send-and-receive.md) — Production messaging patterns
* [**Structured payloads**](/product-docs/realtime-communication/structured-payloads.md) — Images, audio, documents
* [**Chat overview**](/product-docs/chat/overview.md) — History and chat UX on top of messages
