# Connection & Protocol

The hub WebSocket (`/hub/ws`) is the internal streaming endpoint exposed by mx-notifier. It uses the same binary protobuf framing protocol that the MultiversX node uses to push data to mx-notifier. Consumers connect, send a subscription message, and receive a continuous stream of matching normalized events.

***

## Endpoint

```
ws://<notifier-host>/hub/ws
```

The endpoint is binary-only. All frames — inbound (consumer to mx-notifier) and outbound (mx-notifier to consumer) — are binary WebSocket frames carrying serialized `WsMessage` protobuf messages.

{% hint style="warning" %}
Do not send text frames to this endpoint. The server expects binary frames exclusively. Text frames are discarded or cause a protocol error depending on server configuration.
{% endhint %}

***

## Frame format

Every frame is a serialized `WsMessage` protobuf message:

```protobuf
message WsMessage {
  uint32 version         = 1;  // Protocol version; must be >= 1
  bool   with_acknowledge = 2;  // If true, sender expects an ACK reply
  uint64 counter         = 3;  // Monotonically increasing frame counter
  int32  type            = 4;  // 1 = ACK, 2 = PAYLOAD
  string topic           = 5;  // Non-empty for PAYLOAD frames
  bytes  payload         = 6;  // Serialized event data
}
```

### Field reference

| Field              | Type   | Required    | Description                                                                                                                          |
| ------------------ | ------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `version`          | uint32 | Yes         | Protocol version. Must be `>= 1`. Frames with version 0 are rejected.                                                                |
| `with_acknowledge` | bool   | No          | When `true`, the receiver must send an ACK frame with the same `counter` value before sending the next payload. Defaults to `false`. |
| `counter`          | uint64 | Yes         | Monotonically increasing per-connection frame counter. Used to correlate ACK messages with their corresponding PAYLOAD frames.       |
| `type`             | int32  | Yes         | `1` = ACK frame. `2` = PAYLOAD frame.                                                                                                |
| `topic`            | string | Conditional | Non-empty string identifying the event type. Required for PAYLOAD frames (type 2). Empty for ACK frames.                             |
| `payload`          | bytes  | Conditional | Serialized event data. Present for PAYLOAD frames. Empty for ACK frames.                                                             |

***

## Message types

### PAYLOAD (type = 2)

PAYLOAD frames carry actual event data. The `topic` field identifies the event category. The `payload` field contains a serialized protobuf message whose schema depends on the topic.

A PAYLOAD frame is valid when:

* `version >= 1`
* `type == 2`
* `topic` is a non-empty string
* `payload` is non-empty

### ACK (type = 1)

ACK frames are control messages for flow acknowledgement. When a PAYLOAD frame arrives with `with_acknowledge = true`, the receiver must reply with an ACK frame carrying the same `counter` value before the sender transmits the next frame.

An ACK frame sets:

* `type = 1`
* `counter` = the counter value from the PAYLOAD being acknowledged
* `topic` and `payload` are empty

***

## Acknowledgement protocol

When `with_acknowledge` is `true` on a received PAYLOAD frame, mx-notifier waits for the consumer to send an ACK before delivering the next event. This is a stop-and-wait flow control mechanism.

Sequence for acknowledged delivery:

```
mx-notifier                          Consumer
    │                                    │
    │── PAYLOAD (counter=42, ack=true) ──►│
    │                                    │  (process event)
    │◄── ACK (counter=42) ───────────────│
    │                                    │
    │── PAYLOAD (counter=43, ack=true) ──►│
    │                                    │
```

{% hint style="info" %}
Most consumers should leave `with_acknowledge = false` and rely on the server-side backpressure mechanisms described below. Use acknowledged delivery only when the consumer must guarantee ordered, exactly-once processing before the next event arrives.
{% endhint %}

***

## Frame validation rules

mx-notifier silently drops frames that fail any of the following rules:

| Rule                       | Condition                                    |
| -------------------------- | -------------------------------------------- |
| Version check              | `version >= 1`                               |
| Type check                 | `type` is `1` (ACK) or `2` (PAYLOAD)         |
| Topic required for payload | PAYLOAD frames must have a non-empty `topic` |

***

## Subscribing

After connecting, send a `SubscribeEvent` protobuf message serialized inside a `WsMessage` PAYLOAD frame (topic: `subscribe`). The `SubscribeEvent` declares the filter criteria for the events you want to receive.

See [filtering.md](https://docs.xoxno.com/developers/notifier/filtering) for the full `SubscribeEvent` structure, match level options, and code examples.

***

## Backpressure

mx-notifier applies two layers of backpressure to protect the server under high load:

**Global semaphore** — a server-wide limit caps concurrent in-flight event deliveries across all hub connections. When the limit is reached, new events queue until capacity frees.

**Per-connection semaphore** — each hub connection has its own concurrency limit. If a consumer reads frames slowly, its per-connection semaphore fills and new events targeting that connection are held until the consumer catches up.

{% hint style="warning" %}
If a consumer consistently fails to drain its receive buffer, the server closes the connection when the per-connection queue reaches its maximum depth. Consumers must read frames promptly and avoid blocking the receive loop with expensive processing. Offload heavy work to a separate goroutine or thread.
{% endhint %}

***

## JavaScript example

The following example uses [protobufjs](https://github.com/protobufjs/protobuf.js) to connect, decode a PAYLOAD frame, and send an ACK.

```javascript
import protobuf from 'protobufjs';
import WebSocket from 'ws';

// Load the WsMessage schema (proto file must match server definition)
const root = await protobuf.load('ws_message.proto');
const WsMessage = root.lookupType('WsMessage');

const ws = new WebSocket('ws://notifier.internal/hub/ws');
ws.binaryType = 'arraybuffer';

ws.on('open', () => {
  // Send a subscription frame (see filtering.md for SubscribeEvent encoding)
  const subscribePayload = encodeSubscribeEvent({ matchLevel: 'All' });

  const frame = WsMessage.create({
    version: 1,
    withAcknowledge: false,
    counter: 1,
    type: 2,           // PAYLOAD
    topic: 'subscribe',
    payload: subscribePayload,
  });

  ws.send(WsMessage.encode(frame).finish());
});

ws.on('message', (data) => {
  const msg = WsMessage.decode(new Uint8Array(data));

  if (msg.type === 2) {
    // PAYLOAD frame
    console.log('topic:', msg.topic);
    console.log('payload length:', msg.payload.length);

    // Send ACK if requested
    if (msg.withAcknowledge) {
      const ack = WsMessage.create({
        version: 1,
        counter: msg.counter,
        type: 1,   // ACK
      });
      ws.send(WsMessage.encode(ack).finish());
    }
  }
});

ws.on('close', (code, reason) => {
  console.log('Connection closed:', code, reason.toString());
  // Implement reconnect with exponential backoff here
});
```

{% hint style="info" %}
mx-notifier auto-reconnects to the MultiversX node if the upstream WebSocket drops. The hub WebSocket connection to consumers remains open during node reconnection, but event delivery pauses until the upstream connection is restored.
{% endhint %}
