# System Topics and Client Event Subscriptions

EMQX publishes client connection, disconnection, and subscription events to system topics prefixed with `$SYS/`, for example:

```
$SYS/brokers/${node}/clients/${clientid}/connected
```

`$SYS` system topics contain Broker node-level information and are intended for internal Broker observability, compatibility, and operational diagnostics. To subscribe to system topics in EMQX Cloud, you must add the appropriate authorization in the default ACL rules, for example, allowing a username such as `emqx` to subscribe to `$SYS/#`.

For production business integrations, the recommended approach is to use EMQX Cloud Data Integration to capture client events, then forward them to business topics using the Republish action. EMQX Cloud supports filtering and transforming event data with rule SQL before republishing the results to any MQTT topic.

This page explains how system topics work in EMQX Cloud and how to use Data Integration to forward client events to business topics for downstream consumption.

## When to Use Each Approach

System topics are suitable for Broker observability, compatibility adapters, and operational diagnostics. If your goal is to capture client connection, disconnection, or subscription events and integrate them into a production system, Data Integration is the better choice.

| Aspect            | $SYS System Topics                                      | Data Integration + Republish                              |
| ----------------- | ------------------------------------------------------- | --------------------------------------------------------- |
| Use case          | Broker observability, diagnostics, compatibility        | Business integration, event forwarding                    |
| Topic structure   | Contains `${node}`, tied to internal Broker node names  | Republish to stable, custom business topics               |
| Data processing   | Clients parse raw messages themselves                   | Filter, transform, and mask data centrally in rule SQL    |
| Security boundary | May expose internal Broker node information             | Set independent access control on business topics         |
| Extensibility     | Subscription-only observation                           | Forward to MQTT topics or external systems simultaneously |

## System Topics

System topics follow the format `$SYS/brokers/${node}/...`, where `${node}` is the Broker node name (for example, `emqx@127.0.0.1`).

### Client Event Topics

The following system topics are available for client state events:

| Event                  | System Topic                                                           |
| ---------------------- | ---------------------------------------------------------------------- |
| Client connected       | `$SYS/brokers/${node}/clients/${clientid}/connected`                   |
| Client disconnected    | `$SYS/brokers/${node}/clients/${clientid}/disconnected`                |
| Client subscribed      | `$SYS/brokers/${node}/clients/${clientid}/subscribed`                  |
| Client unsubscribed    | `$SYS/brokers/${node}/clients/${clientid}/unsubscribed`                |

::: tip Note

All four event types are enabled by default. To subscribe to them, add the appropriate authorization in the default ACL rules, for example, allowing a specific username to subscribe to `$SYS/#`.
:::

## Capture Client Events with Data Integration (Recommended)

Data Integration provides built-in event topics prefixed with `$events/`. These are more stable than system topics, are not tied to node names, and support data processing in rule SQL before forwarding to custom business topics. By default, clients cannot subscribe to these event topics directly. They must be handled and forwarded through a Data Integration rule. For a full reference of available fields for each event, see [SQL Data Sources and Fields — MQTT Events](../rule_engine/rule_engine_events.md#mqtt-events).

### System Topics vs. Data Integration Event Topics

| Event               | $SYS System Topic                                                      | Data Integration Event Topic   |
| ------------------- | ---------------------------------------------------------------------- | ------------------------------ |
| Client connected    | `$SYS/brokers/${node}/clients/${clientid}/connected`                   | `$events/client/connected`     |
| Client disconnected | `$SYS/brokers/${node}/clients/${clientid}/disconnected`                | `$events/client/disconnected`  |
| Client subscribed   | `$SYS/brokers/${node}/clients/${clientid}/subscribed`                  | `$events/session/subscribed`   |
| Client unsubscribed | `$SYS/brokers/${node}/clients/${clientid}/unsubscribed`                | `$events/session/unsubscribed` |

::: tip Note

New rules should use the namespaced event topics such as `$events/client/connected` and `$events/session/subscribed`. Legacy event topic formats remain supported but are not recommended for new configurations.

:::

### Recommended Architecture

Forward events to business topics through a Data Integration rule, and have business clients subscribe to those topics rather than depending on system topics directly:

```
$events/client/#  or  $events/session/#
  → Data Integration rule SQL (filter, transform)
  → Republish action
  → Business topic (e.g., iot/events/client/connected)
  → Business client subscribes
```

## Configure Data Integration to Forward Client Events

The following examples show how to configure a rule and Republish action for each of the four client event types.

### Client Connected

**Event topic**

```
$events/client/connected
```

**Rule SQL**

```sql
SELECT
  clientid,
  username,
  peername,
  proto_name,
  proto_ver,
  keepalive,
  clean_start,
  expiry_interval,
  connected_at,
  timestamp,
  node
FROM
  "$events/client/connected"
```

**Suggested republish topic**

```
iot/events/client/connected
```

**Payload template**

```json
{
  "event": "client.connected",
  "clientid": "${clientid}",
  "username": "${username}",
  "peername": "${peername}",
  "proto_name": "${proto_name}",
  "proto_ver": ${proto_ver},
  "keepalive": ${keepalive},
  "clean_start": ${clean_start},
  "expiry_interval": ${expiry_interval},
  "connected_at": ${connected_at},
  "timestamp": ${timestamp},
  "node": "${node}"
}
```

### Client Disconnected

**Event topic**

```
$events/client/disconnected
```

**Rule SQL**

```sql
SELECT
  clientid,
  username,
  reason,
  peername,
  connected_at,
  disconnected_at,
  timestamp,
  node
FROM
  "$events/client/disconnected"
```

**Suggested republish topic**

```
iot/events/client/disconnected
```

**Payload template**

```json
{
  "event": "client.disconnected",
  "clientid": "${clientid}",
  "username": "${username}",
  "reason": "${reason}",
  "peername": "${peername}",
  "connected_at": ${connected_at},
  "disconnected_at": ${disconnected_at},
  "timestamp": ${timestamp},
  "node": "${node}"
}
```

### Client Subscribed

**Event topic**

```
$events/session/subscribed
```

**Rule SQL**

```sql
SELECT
  clientid,
  username,
  topic,
  qos,
  sub_props,
  timestamp,
  node
FROM
  "$events/session/subscribed"
```

**Suggested republish topic**

```
iot/events/session/subscribed
```

**Payload template**

```json
{
  "event": "session.subscribed",
  "clientid": "${clientid}",
  "username": "${username}",
  "topic": "${topic}",
  "qos": ${qos},
  "sub_props": ${sub_props},
  "timestamp": ${timestamp},
  "node": "${node}"
}
```

### Client Unsubscribed

**Event topic**

```
$events/session/unsubscribed
```

**Rule SQL**

```sql
SELECT
  clientid,
  username,
  topic,
  qos,
  unsub_props,
  timestamp,
  node
FROM
  "$events/session/unsubscribed"
```

**Suggested republish topic**

```
iot/events/session/unsubscribed
```

**Payload template**

```json
{
  "event": "session.unsubscribed",
  "clientid": "${clientid}",
  "username": "${username}",
  "topic": "${topic}",
  "qos": ${qos},
  "unsub_props": ${unsub_props},
  "timestamp": ${timestamp},
  "node": "${node}"
}
```

## Create a Data Integration Rule

Follow these steps in the EMQX Cloud Console to create a rule and add a Republish action:

1. Go to the **Data Integration** page of your deployment.
2. Under **Data Forwarding**, select **Republish** and click **Create**.
3. Enter the rule SQL in the SQL editor.
4. Click **Next** and add a **Republish** action.
5. Set the target business topic, Payload template, QoS, Retain flag, and any other parameters.
6. Click **Confirm** to save, then use the SQL test tool to verify the rule behaves as expected.

::: tip

To prevent a republished message from triggering the same rule again, enable **Direct Dispatch** in the action configuration. With Direct Dispatch enabled, messages are delivered directly to subscribers without passing through the rule engine, avoiding loop triggers.

:::

## Best Practices

- Use `$events/client/#` and `$events/session/#` as the unified source for client connection, disconnection, subscription, and unsubscription events.
- Use Data Integration rule SQL to filter, transform, and mask event data centrally.
- Republish events to stable business topics such as `iot/events/client/+` and `iot/events/session/+`, so business systems do not depend directly on `$SYS/brokers/${node}/...` internal topic structures.
- Business clients should subscribe only to business topics. Apply independent ACL rules to those topics to enforce access control and isolation.
- Write events to external systems as needed for auditing, querying, monitoring, and alerting.
