# Presence (/en/realtime-media/rtm/build/manage-presence-and-metadata/presence/windows)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

In Signaling solutions, it is often important to know a user's current online status. For example, in instant messaging, chat applications, and online collaboration tools, users need to see the availability of their contacts. This information is typically displayed as a status message or icon next to a user's name. Presence features in Signaling SDK enable you to monitor join, leave, and status change notifications for users in a channel. Using Presence, you can:

* Get a list of users currently in a channel and their temporary status data.
* Get a list of channels a specified user has joined or is subscribed to.
* Get, set, or remove user statuses.
* Receive real-time event notifications when users join or leave specified channels.
* Receive user status change event notifications in real time.

## Understand the tech

Presence provides real-time information about the availability, and the current status of users, for effective communication and collaboration. It enables you to retrieve a list of users in a channel, or to query the list of channels for a specific user. The following figure illustrates how you integrate presence features into your app.

**Presence workflow**

![Presence workflow](https://assets-docs.agora.io/images/signaling/presence-workflow.svg)

## Prerequisites

Ensure that you have integrated the Signaling SDK in your project, and implemented the framework functionality from the [SDK quickstart](../../quickstart) page.

<CalloutContainer type="info">
  <CalloutDescription>
    Presence features require Signaling SDK version 2.2.0 or later.
  </CalloutDescription>
</CalloutContainer>

<CalloutContainer type="info">
  <CalloutTitle>
    Enable Presence in Agora Console
  </CalloutTitle>

  <CalloutDescription>
    Starting from v2.3.0, you can enable or disable Presence in Agora Console. Presence is enabled by default. If you disable it, calls to Presence APIs and any subscription options that depend on Presence return an error.
  </CalloutDescription>
</CalloutContainer>

## Implement presence features

Using presence, you can implement the following features:

### Get channel users

To obtain a list of online users in a channel, call `getOnlineUsers`. Depending on your parameter settings, this method returns a list of online user IDs in a channel and their temporary status data, or just the number of online users in the channel. You do not need to join a channel to call this method. This method is applicable to both message channels and stream channels. Use the `channelType` parameter to specify the channel type.

After obtaining the initial online users list, update it in real time through `onPresenceEvent` event notifications.

<CalloutContainer type="info">
  <CalloutTitle>
    Querying Presence right after login
  </CalloutTitle>

  <CalloutDescription>
    Starting from v2.3.0, if Presence is still initializing when a `login` call succeeds, the SDK waits and automatically runs your `getOnlineUsers`, `getUserChannels`, and remote-user `getState` queries once Presence becomes available. Your app does not need to add a fixed delay, temporarily subscribe to a channel, or wait for a Presence event to check whether Presence is ready. A request held in this waiting state may include a short initialization delay before it returns. The request fails if Presence does not become available within a reasonable time, or if a logout, instance release, or connection interruption occurs while the request is waiting.
  </CalloutDescription>
</CalloutContainer>

Refer to the following sample code to query the list of online users in a channel and their current status:

```cpp
PresenceOptions options;
options.includeState = true;
options.includeUserId =true;

uint64_t requestId;
rtmClient->getPresence()->getOnlineUsers("channelName", RTM_CHANNEL_TYPE_MESSAGE, options, requestId);
```

After you call this method, the SDK triggers the `onGetOnlineUsersResult` callback to return the call result.

```cpp
class RtmEventHandler : public IRtmEventHandler {
    void onGetOnlineUsersResult(const uint64_t requestId, const UserState *userStateList, const size_t count, const char *nextPage, RTM_ERROR_CODE errorCode) override {
        if (errorCode != RTM_ERROR_OK) {
            printf("getOnlineUsers failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
        } else {
            printf("getOnlineUsers success\n");
            for (int i = 0 ;i < count; i++) {
                printf("user: %s\n", userStateList[i].userId);
                for (int j = 0 ; j < userStateList[i].statesCount; j++) {
                    printf("key: %s value: %s\n", userStateList[i].states[j].key, userStateList[i].states[j].value);
                }
            }
        }
    }
};
```

The `getOnlineUsers` method retrieves one page of data at a time. Each page contains up to 100 online users. If the channel has more than 100 users, the `nextPage` field in the returned result contains a bookmark for the next page. After each query, check if `nextPage` is not empty to determine if there is more data. To retrieve the next page, set the `page` field in `PresenceOptions` to the value of `nextPage`. Repeat this process until `nextPage` is null. Refer to the following code:

<CalloutContainer type="info">
  <CalloutTitle>
    Querying large channels
  </CalloutTitle>

  <CalloutDescription>
    Starting from v2.3.0, once a channel exceeds 512 online users, the SDK automatically applies large-channel optimization and continuously syncs details for only 512 members. This limit applies only to that continuous sync; a `getOnlineUsers` query still returns the full member list. Use pagination to retrieve it.
  </CalloutDescription>
</CalloutContainer>

```cpp
PresenceOptions options;
options.includeState = true;
options.includeUserId =true;
options.page = "your_Next_Page_Bookmark";

uint64_t requestId;
rtmClient->getPresence()->getOnlineUsers("channelName", RTM_CHANNEL_TYPE_MESSAGE, options, requestId);
```

When there is a large number of users in a channel, you may only care about the total number of online users, and not their identities or temporary status. To get the total channel occupancy, set `includeState` and `includeUserId` in `PresenceOptions` to `false`.

```cpp
PresenceOptions options;
options.includeState = false;
options.includeUserId =false;

uint64_t requestId;
rtmClient->getPresence()->getOnlineUsers("channelName", RTM_CHANNEL_TYPE_MESSAGE, options, requestId);
```

In this case, only the `totalOccupancy` property in the result is valid while all other fields are empty.

<CalloutContainer type="info">
  <CalloutDescription>
    You cannot set `includeState` to `true` and `includeUserId` to `false` at the same time. To get the temporary status of users, you must also retrieve their user IDs.
  </CalloutDescription>
</CalloutContainer>

### Get user channels

The `getUserChannels` method enables you to query which channels a user is currently in. This includes the message channels a user has subscribed to and the stream channels they have joined. This method is particularly useful for tracking user paths. Refer to the following sample code:

```cpp
uint64_t requestId;
rtmClient->getPresence()->getUserChannels("tony", requestId);
```

After you call this method, the SDK triggers the `onGetUserChannelsResult` callback to return the call result.

```cpp
class RtmEventHandler : public IRtmEventHandler {
    void onGetUserChannelsResult(const uint64_t requestId, const ChannelInfo *channels, const size_t count, RTM_ERROR_CODE errorCode) override {
        if (errorCode != RTM_ERROR_OK) {
            printf("getUserChannels failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
        } else {
            printf("getUserChannels success\n");
            for (int i = 0; i < count; i++) {
                printf("channel: %s channel type: %d\n", channels[i].channelName, channels[i].channelType);
            }
        }
    }
};
```

The `getUserChannels` method returns complete query results about the channels and their types that the queried user is in, without pagination.

## User status management

Signaling enables you to set and delete user status messages for the local user in each channel. The SDK notifies other online users in the channel of these changes through event notifications. This feature is useful in use-cases where user status sharing is required, such as real-time synchronization of the user's microphone status, mood, personal signature, score, and message input status.

Signaling does not permanently save the status data. When a user unsubscribes from a channel, times out, or exits a channel, the data is deleted. To save user data permanently, use the [Store user metadata](storage/store-user-metadata) feature.

When a user's temporary status changes, Signaling triggers an `RTM_PRESENCE_EVENT_TYPE_REMOTE_STATE_CHANGED` event notification in real-time. Users who set `withPresence = true` when joining the channel, receive the event notification.

### Set status

Using presence, you can set the temporary user status for the local user. When you set the status before subscribing to or joining a channel, the data is cached on the client and does not take effect immediately. The status is updated and corresponding event notifications are triggered when you subscribe to or join a channel. The `setState` method applies to both message and stream channels; use the `channelType` parameter to specify the channel type.

```cpp
std::vector<StateItem> items;
StateItem item;
item.key = "Mode";
item.value = "Happy";
items.emplace_back(item);

uint64_t requestId;
rtm_client->getPresence()->setState("channelName", RTM_CHANNEL_TYPE_MESSAGE, items.data(), items.size(), requestId);
```

After you call this method, the SDK triggers the `onPresenceSetStateResult` callback to return the API call result.

```cpp
class RtmEventHandler : public IRtmEventHandler {
    void onPresenceSetStateResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
        if (errorCode != RTM_ERROR_OK) {
            printf("SetState failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
        } else {
            printf("SetState success\n");
        }
    }
};
```

When using `setState` to set temporary user status, if the specified key already exists, its value is overwritten by the new value. If the specified key does not exist, a new key/value pair is added.

### Get status

To obtain the temporary user status set by a user in a specified channel, use the `getState` method:

```cpp
uint64_t requestId;
rtm_client->getPresence()->getState("channelName", RTM_CHANNEL_TYPE_MESSAGE, "tony", requestId);
```

After you call this method, the SDK triggers the `onPresenceGetStateResult` callback to return the API call result.

```cpp
class RtmEventHandler : public IRtmEventHandler {
    void onPresenceGetStateResult(const uint64_t requestId, const UserState &state, RTM_ERROR_CODE errorCode) override {
        if (errorCode != RTM_ERROR_OK) {
            printf("GetState failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
        } else {
            printf("GetState user id: %s success\n", state.userId);
            for (int i = 0; i < state.statesCount; i++) {
                printf("key: %s, value: %s\n", state.states[i].key, state.states[i].value);
            }
        }
    }
};
```

Use the `getState` method to obtain the temporary status of other online users in the channel. If the queried user is not present in the specified channel, an error message is returned by the SDK.

<CalloutContainer type="info">
  <CalloutTitle>
    Querying remote user state
  </CalloutTitle>

  <CalloutDescription>
    Starting from v2.3.0, if Presence is still starting up when `login` succeeds, the SDK automatically waits and retries a remote `getState` call once Presence becomes available, as described in [Querying Presence right after login](#get-channel-users). `getState` also resolves for users outside the synced 512-member set in large channels. Handle the result as you would any other asynchronous call, including failure.
  </CalloutDescription>
</CalloutContainer>

### Delete status

Each user can set up to 32 key/value pairs in a channel. To remove items that are no longer needed, call `removeState` with a list of keys. The `removeState` method only deletes temporary user status data for the local user.

```cpp
std::vector<const char*> keys;
keys.push_back("Mode");
keys.push_back("Mic");

uint64_t requestId;
rtmClient->getPresence()->removeState("channelName", RTM_CHANNEL_TYPE_MESSAGE, keys.data(), keys.size(), requestId);
```

After you call this method, the SDK triggers the `onPresenceRemoveStateResult` callback to return the API call result.

```cpp
class RtmEventHandler : public IRtmEventHandler {
void onPresenceRemoveStateResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
    if (errorCode != RTM_ERROR_OK) {
        printf("RemoveState failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
    } else {
        printf("RemoveState success\n");
    }
}
};
```

Both the `setState` and `removeState` methods trigger `RTM_PRESENCE_EVENT_TYPE_REMOTE_STATE_CHANGED` event notifications. Users who join the channel with `withPresence` set to `true` receive event notifications containing full data of the user's temporary status.

## Receive presence event notifications

A presence event notification returns the [PresenceEvent](/en/api-reference/api-ref/signaling#configpresenceeventpropsag_platform) data structure, which includes the [RTM\_PRESENCE\_EVENT\_TYPE.](/en/api-reference/api-ref/signaling#enumvpresencetypepropsag_platform) parameter.

To receive presence event notifications, implement an event listener. See [event listeners](/en/api-reference/api-ref/signaling#event-listeners) for details. In addition, set the `withPresence` parameter to `true` when subscribing to or joining a channel.

### Event notification modes

The presence event notification mode determines how subscribed users receive event notifications. There are two notification modes:

* `Announce`: Real-time notification mode
* `Interval`: Scheduled notification mode

You set the **Max number of instant event** value in Agora Console to specify the condition for switching between the two modes. The scheduled notification mode helps prevent event noise that results from a large number of online users in the channel. See [Presence configuration](../../enable-signaling#presence-configuration) for details.

From v2.3.0, Signaling adds a large-channel optimization strategy on top of these notification modes, with a default threshold of 512 users:

* When the number of online users is 512 or fewer, existing notification behavior applies.
* When the number of online users exceeds 512, the SDK automatically applies the optimization strategy. Your app does not need to enable, disable, or listen for a mode switch.
* In this mode, the client continuously synchronizes details for up to 512 members. Joins, leaves, timeouts, and status changes for other members are aggregated into a channel-wide occupancy change instead.
* Your app receives the aggregated change through the existing `Interval` event and can read the total number of online users from `interval.totalOccupancy`. This value may be delayed slightly.
* When the number of online users drops back down, the SDK automatically reverts to the standard notification behavior. Your app does not need to manage this transition.

#### Real-time notification mode

If the number of instant notifications in the channel is less than the **Max number of instant event** value, presence event notifications operate in real-time notification mode. In this mode, `RTM_PRESENCE_EVENT_TYPE_REMOTE_JOIN_CHANNEL`, `RTM_PRESENCE_EVENT_TYPE_REMOTE_LEAVE_CHANNEL`, `RTM_PRESENCE_EVENT_TYPE_REMOTE_TIMEOUT`, and `RTM_PRESENCE_EVENT_TYPE_REMOTE_STATE_CHANGED` notifications are sent to the client in real-time.

<CodeBlockTabs defaultValue="Join">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="Join">
      Join
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Leave">
      Leave
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Timeout">
      Timeout
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Snapshot">
      Snapshot
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="State change">
      State change
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="Join">
    ```js
    {
          type: RTM_PRESENCE_EVENT_TYPE_REMOTE_JOIN_CHANNEL;
          channelType: RTM_CHANNEL_TYPE_MESSAGE;
          channelName: "test_channel";
          publisher: "publisher_name";
          states: [];
          interval: [];
          snapshot: [];
          timestamp: 1710487149497;
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Leave">
    ```js
    {
          eventType: RTM_PRESENCE_EVENT_TYPE_REMOTE_LEAVE_CHANNEL;
          channelType: RTM_CHANNEL_TYPE_MESSAGE;
          channelName: "test_channel";
          publisher: "publisher_name";
          stateChanged: [];
          interval: [];
          snapshot: [];
          timestamp: 1710487149497;
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Timeout">
    ```js
    {
          eventType: RTM_PRESENCE_EVENT_TYPE_REMOTE_TIMEOUT;
          channelType: RTM_CHANNEL_TYPE_MESSAGE;
          channelName: "test_channel";
          publisher: "publisher_name";
          stateChanged: [];
          interval: [];
          snapshot: [];
          timestamp: 1710487149497;
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Snapshot">
    ```js
    {
          eventType: RTM_PRESENCE_EVENT_TYPE_SNAPSHOT;
          channelType: RTM_CHANNEL_TYPE_MESSAGE;
          channelName: "test_channel";
          publisher: "";
          stateChanged: [];
          interval: [];
          snapshot: [
              { userId: "user_a", states: {}},
              { userId: "user_b", states: { key_1: "value_1" }},
              { userId: "yourSelf", states: {}},
          ];
          timestamp: 1710487149497;
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="State change">
    ```js
    {
          eventType: RTM_PRESENCE_EVENT_TYPE_REMOTE_STATE_CHANGED;
          channelType: RTM_CHANNEL_TYPE_MESSAGE;
          channelName: "test_channel";
          publisher: "publisher_name";
          states: {
              "key_1": "value_1",
          };
          interval: [];
          snapshot: [];
          timestamp: 1710487149497;
    }
    ```
  </CodeBlockTab>
</CodeBlockTabs>

#### Scheduled notification mode

When the number of online users in the channel exceeds the **Max number of instant event** value, the channel switches to the scheduled notification mode. In this mode, `RTM_PRESENCE_EVENT_TYPE_REMOTE_JOIN_CHANNEL`, `RTM_PRESENCE_EVENT_TYPE_REMOTE_LEAVE_CHANNEL`, `RTM_PRESENCE_EVENT_TYPE_REMOTE_TIMEOUT`, and `RTM_PRESENCE_EVENT_TYPE_REMOTE_STATE_CHANGED` events are replaced by `RTM_PRESENCE_EVENT_TYPE_INTERVAL` events and sent to all users in the channel at specific time intervals. Users receive the following notification:

```js
{
      "type": "RTM_PRESENCE_EVENT_TYPE_INTERVAL",
      "channelType": "RTM_CHANNEL_TYPE_MESSAGE",
      "channelName": "Chat_room",
      "publisher": "Tony",
      "interval": {
          "remote_join": ["Tony", "Lily"],
          "remote_leave": ["Jason"],
          "remote_timeout": ["Wendy"],
          "remote_state_change": [
              {
                  "userId": "Harvard",
                  "states": [
                      { "Mic": "False" },
                      { "Position": "Washington" }
                  ]
              },
              {
                  "userId": "Harvard",
                  "states": [
                      { "Mic": "False" },
                      { "Position": "Washington" }
                  ]
              }
          ]
      },
      "snapshot": {
          "userStateList": []
      },
      "timestamp": 1710487149497
}
```

## Reference

This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

### API reference

* [`getOnlineUsers`](/en/api-reference/api-ref/signaling#getonlineusers)
* [`getUserChannels`](/en/api-reference/api-ref/signaling#getuserchannels)
* [`setState`](/en/api-reference/api-ref/signaling#presencesetstatepropsag_platform)
* [`getState`](/en/api-reference/api-ref/signaling#presencegetstatepropsag_platform)
* [`removeState`](/en/api-reference/api-ref/signaling#presenceremovestatepropsag_platform)
* [Event listeners](/en/api-reference/api-ref/signaling#event-listeners)

    
  
      
  
      
  
