Presence Events
PubNub sends Presence events when users come online or go offline in your app.
You can receive these events in the client or use webhooks to keep a user's online and offline status up to date. Use presence events to track active and inactive users and the total occupancy of the channel.
Presence webhooks
PubNub can invoke a REST endpoint on your server when presence events occur. For more information, refer to Events & Actions.
Presence event types
There are five main types of presence events:
Events | Description |
---|---|
join | Sent when a user subscribes to a channel. |
leave | Sent when a user unsubscribes from a channel. |
timeout | Sent when a subscriber is inactive on a channel for a set period. Default is 300 seconds for most SDKs. Configure the interval with presenceTimeout . Optionally send heartbeats using heartbeatInterval to keep connections active and avoid timeouts. |
state-change | Sent when a user's state changes. |
interval | Sent in interval mode with occupancy and optional deltas. |
Timeout detection
Presence timeout detection is not region-dependent. PubNub SDKs don't actively monitor connectivity. A disconnection is detected when subscribe requests time out (typically 300 seconds) or when the next request fails. Local network conditions drive timing differences, not PubNub's system.
Implicit vs. explicit Presence events
When you work with Presence, your app can signal that users are online in two ways. Think of a coffee shop: you can wave every few minutes (explicit) or people will see you when you move around (implicit).
First, understand how Presence tracking works. PubNub uses a timer for each user to decide when to mark them offline. The key is what resets this timer and how you configure the timeout.
PubNub tracks user activity automatically. Each subscribe call tells PubNub the user is active. This is implicit Presence and it runs by default. You can disable implicit heartbeats in the Presence Management page in the Admin Portal by clearing the Subscribe Heartbeats
checkbox.
PubNub runs a timer per user. Each implicit heartbeat from the Subscribe API resets the timer. If a user is silent for the full duration, PubNub marks the user offline and sends a timeout
event. The presenceTimeout
setting controls this duration. It applies to both implicit heartbeats (Subscribe API) and explicit heartbeats (Presence Heartbeat API).
Why configure anything if PubNub tracks activity automatically? You may need faster disconnect detection than the default server ping interval (~280 seconds). Two settings determine when users are marked offline.
Configuration settings
Before we dive deeper, let's understand the two main configuration options and how they work together:
-
presenceTimeout
(alsopresenceHeartbeatValue
ordurationUntilTimeout
in some SDKs) is the server-side timer. It sets how long PubNub waits without a subscribe call before marking a user offline. Default is about 300 seconds. Any explicit (heartbeat API) or implicit (subscribe API) heartbeat resets this timer. -
heartbeatInterval
(sometimespresenceHeartbeatInterval
) is the client-side ping frequency. It controls dedicated "I'm still here" signals via the heartbeat API.0
(default) sends no explicit pings and relies on implicit heartbeats. Any positive value (min 3 seconds) sends explicit heartbeats at that interval.
Think of it this way: presenceTimeout
is like a countdown clock on PubNub's servers, and heartbeatInterval
is like setting an alarm on your phone to reset that clock before it runs out.
Now let's see how these work in practice. If you set your presenceTimeout
to 300 seconds and a user's client sends an implicit heartbeat (via subscribe API) after 4 minutes of being idle, PubNub resets their timer back to zero. They won't time out until they've been completely inactive for another full timeout period. This is actually pretty smart - if someone is actively subscribed to your app, why mark them as offline?
When implicit Presence isn't enough
Relying only on implicit Presence can delay disconnect detection. By default, the server expects the client to respond to a published message or an empty server ping every ~280 seconds. These server pings reset the long‑poll and act as implicit heartbeats. You may wait up to the full presenceTimeout
(~300 seconds) before detecting a disconnect.
Explicit heartbeats help here. Set heartbeatInterval
shorter than the server ping (for example, 120 seconds). The SDK then sends "I'm still here" signals more often. These explicit heartbeats reset the same Presence timeout and detect disconnects faster.
// Let users rely on natural activity to stay present
{
presenceTimeout: 300, // 5 minutes until timeout
heartbeatInterval: 0 // No explicit heartbeats
}
// Send regular "I'm alive" signals regardless of activity
{
presenceTimeout: 300, // 5 minutes until timeout
heartbeatInterval: 120 // Heartbeat every 2 minutes
}
Cost vs. accuracy trade-off
Every heartbeat is an API call. API calls incur cost. 1,000 users with a 60‑second heartbeat create 1,440,000 calls per day. If the default server ping (~280 seconds) is enough for your use case, explicit heartbeats may be unnecessary.
If you need rapid disconnect detection (for example, trading or emergency apps), use shorter heartbeat intervals for faster updates.
Real-world scenarios
Here's how to configure these settings for different types of applications:
Chat applications
Most chat users are naturally active, so you can usually rely on implicit Presence with server pings. Set heartbeatInterval: 0
and let the default server ping interval (~280 seconds) maintain user presence. If someone's connection drops for your full presenceTimeout
period, they're probably actually disconnected.
Collaborative tools
For applications needing faster disconnection detection than the default ~280-second server ping interval, a shorter heartbeat interval like 120 seconds provides quicker notification when users disconnect. This might cost more but enables faster presence updates.
Mobile applications
Battery life matters. You might use a hybrid approach with a longer presenceTimeout
(like 600 seconds) and longer heartbeatInterval
(like 240 seconds). This gives inactive users more time before timing out while still sending occasional heartbeats.
Critical applications
Trading platforms, emergency communication systems, or real-time monitoring tools need immediate notification when someone disconnects. Use shorter intervals like heartbeatInterval: 30
with presenceTimeout: 120
for rapid detection.
Mixed strategies
Implicit Presence works alongside any explicit heartbeats you configure. This actually works in your favor. Even with heartbeatInterval: 0
, if users are actively subscribing, they'll stay present. With heartbeats enabled, both implicit heartbeats (via subscribe API) and explicit heartbeats (via presence heartbeat API) will keep the Presence timer reset, giving you the most reliable tracking possible.
The key insight is that presenceTimeout
works the same way regardless of your heartbeat settings - it's simply the maximum time PubNub waits to hear any sign of life (whether from implicit heartbeats via subscribe API or explicit heartbeats via presence heartbeat API) before marking someone as offline.
Understanding this balance between automatic implicit heartbeats (via subscribe API) and intentional explicit heartbeats (via presence heartbeat API) helps you design a Presence system that matches both your user behavior patterns and your budget constraints.
Add Presence listeners
Receiving presence events requires a Presence listener. Presence events will be received for all channels within a subscription to which you added the listener and enabled Presence events.
User ID / UUID
User ID is also referred to as UUID
/uuid
in some APIs and server responses but holds the value of the userId
parameter you set during initialization.
Event Data | Description |
---|---|
action | The Presence event action type: join, leave, timeout, state-change, interval |
channel | The channel on which the Presence action happened |
occupancy | The total number of subscribers on the channel when the event occurred |
uuid | The User ID of the client that published the message |
timetoken | The timetoken when the Presence action took place (when PubNub published the event) |
data | The state of the client that changed |
subscription | The channel group or wildcard subscription pattern that the channel belongs to (if applicable) |
- JavaScript
- Swift
- Objective-C
- Java
- C#
- Python
- Kotlin
subscription.addListener({
presence: function (p) {
const action = p.action; // Can be join, leave, timeout, state-change, or interval
const channelName = p.channel; // Channel to which the message belongs
const occupancy = p.occupancy; // Number of users subscribed to the channel
const state = p.state; // User state
const channelGroup = p.subscription; // Channel group or wildcard subscription match, if any
const publishTime = p.timestamp; // Publish timetoken
const timetoken = p.timetoken; // Current timetoken
const uuid = p.uuid; // UUIDs of users who are subscribed to the channel
}
}
subscription.onPresence = { presenceChange in
for action in presenceChange.actions {
switch action {
case let .join(uuids):
print("The following list of occupants joined at \(presenceChange.timetoken): \(uuids)")
case let .leave(uuids):
print("The following list of occupants left at \(presenceChange.timetoken): \(uuids)")
case let .timeout(uuids):
print("The following list of occupants timed-out at \(presenceChange.timetoken): \(uuids)")
case let .stateChange(uuid, state):
print("\(uuid) changed their presence state to \(state) at \(presenceChange.timetoken)")
}
}
}
- (void)pubnub:(PubNub *)pubnub didReceivePresenceEvent:(PNPresenceEventResult *)event {
NSString *action = event.data.presenceEvent;
NSString *channel = event.data.channel;
NSString *occupantUserId = event.data.presence.uuid;
NSString *eventTimetoken = event.data.presence.timetoken;
NSInteger *occupancy = event.data.presence.occupancy;
NSDictionary *state = event.data.presence.state;
}
@Override
public void presence(PubNub pubnub, PNPresenceEventResult presence) {
String action = presence.getEvent();
String channel = presence.getChannel();
String occupantUserId = presence.getUuid();
String eventTimetoken = presence.getTimetoken();
String occupancy = presence.getOccupancy();
Object state = presence.getState();
}
Subscription subscription1 = pubnub.Channel("channelName").Subscription()
SubscribeCallbackExt eventListener = new SubscribeCallbackExt(
delegate (Pubnub pn, PNPresenceEventResult e) {
string action = event.GetEvent();
string channel = event.GetChannel();
string occupantUserId = event.GetPublisher();
long eventTimetoken = event.GetTimetoken();
int occupancy = event.GetOccupancy();
object state = event.GetUserMetaData();
}
);
subscription1.AddListener(subscribeCallback)
show all 16 lines# Add event-specific listeners
# without closure
def on_presence(listener):
def presence_callback(presence):
print(f"\033[0;32mPresence received on: {listener}: \t{presence.uuid} {presence.event}s "
f"{presence.subscription or presence.channel}\033[0m")
return presence_callback
subscription.on_presence = on_presence
# add generic listener
class PrintListener(SubscribeCallback):
show all 20 linessubscription.addListener(object : EventListener {
override fun presence(pubnub: PubNub, presence: PNPresenceEventResult) {
// Handle presence updates
println("Presence: ${presence.uuid} - ${presence.event}")
}
})
Subscribe to Presence channel
When you enable the receivePresenceEvents
option (name may vary by SDK) and subscribe to a channel, the SDK automatically subscribes you to the relevant Presence channels.
Subscription with Presence
To receive Presence events, you subscribe with Presence and have Presence enabled on your keyset. Make sure you configure Presence to track Presence-related events for all or selected channels (through Presence Management rules).
Presence channels are sister channels where presence events are published. If you don't wish to subscribe to all presence channels in your list, you can subscribe to individual channels by appending -pnpres
to the channel name. For example, the presence channel for ch1
is ch1-pnpres
.
Subscribe to presence channel only
To subscribe only to the Presence channel and not the main channel (to avoid increasing its occupancy, for example), subscribe to the main channel's -pnpres
channel.
After you create the subscription with the receivePresenceEvents
option enabled and add Presence listeners, call subscribe()
to start receiving Presence events in real time as users join and leave channels.
- JavaScript
- Swift
- Objective-C
- Java
- C#
- Python
- Kotlin
// create a subscription from a channel entity
const channel = pubnub.channel('channel_1')
const subscription1 = channel.subscription({ receivePresenceEvents: true });
// subscribe and start receiving real-time updates
subscription1.subscribe();
// create a subscription from a channel entity
let subscription1 = pubnub.channel("channelName").subscription(options: ReceivePresenceEvents())
// subscribe and start receiving real-time updates
subscription1.subscribe()
[self.pubnub subscribeToChannels: @[@"chats.room1", @"chats.room2"] withPresence:NO];
pubnub.subscribe()
.channels(Arrays.asList("chats.room1", "chats.room2"))
.withPresence().execute();
SubscriptionSet subscriptionSet = pubnub.Subscription(
new string[] {"chats.room1", "chats.room2"},
SubscriptionOptions.ReceivePresenceEvents
)
subscription_set1 = pubnub.subscription_set(channels=["chats.room1", "chats.room2"])
subscription_set1.subscribe(with_presence = True)
// create a subscription from a channel entity
val subscription1 = pubnub.channel("channelName").subscription(SubscriptionOptions.receivePresenceEvents())
// subscribe and start receiving real-time updates
subscription1.subscribe()
If you want to subscribe to a bunch of channels but only want to listen for presence events on some of them, you can create two subscription sets: one with the receivePresenceEvents
option enabled for the channels you want to receive Presence updates, and one without the option for channels you don't want to receive the updates. For more information about subscription sets, refer to Subscription types.
Presence event modes
The channel presence mode indicates when presence events are triggered for that channel. There are two modes: announce and interval. The mode is determined by the occupancy count (total actively subscribed clients on that channel) in relation to the Presence Announce Max setting on the Admin Portal.
This feature prevents high occupancy channels from becoming too noisy with presence events. If you require the announce mode to be in effect past 100 occupants, please contact PubNub Support.
Announce mode
If the channel occupancy is less than the Announce Max setting (defaults to 20), the channel is in announce mode. In this mode, join
, leave
, timeout
and state-change
events are sent to subscribed clients as and when they're triggered.
User ID / UUID
User ID is also referred to as UUID
/uuid
in some APIs and server responses but holds the value of the userId
parameter you set during initialization.
- Join
- Leave
- timeout
- State Change
{
"action": "join",
"channel": "chats.room1",
"subscribedChannel": "chats.room1-pnpres",
"timetoken": "15119466699655811",
"occupancy": 2,
"uuid": "user123",
"timestamp": 1511946669
}
{
"action": "leave",
"channel": "chats.room1",
"subscribedChannel": "chats.room1-pnpres",
"timetoken": "15119466699655811",
"occupancy": 2,
"uuid": "user123",
"timestamp": 1511946669
}
{
"action": "timeout",
"channel": "chats.room1",
"subscribedChannel": "chats.room1-pnpres",
"timetoken": "15119466699655811",
"occupancy": 2,
"uuid": "user123",
"timestamp": 1511946669
}
{
"action": "state-change",
"channel": "room-1",
"subscription": null,
"actualChannel": null,
"subscribedChannel": "room-1-pnpres",
"state": {
"mood": "grumpy"
},
"timetoken": "15119477895378127",
"occupancy": 5,
"uuid": "user1",
"timestamp": 1511947789
}
Interval mode
When a channel's occupancy exceeds the Announce Max setting, the channel switches to interval mode. In this mode, the join
, leave
, and timeout
events are replaced by an interval
event sent every few seconds with the total occupancy on the channel. You configure the Interval setting on the settings page.
Triggering state-change events
The state-change
events are always triggered regardless of which presence mode is active on a channel.
Presence deltas
Additionally, you can enable the Presence Deltas setting from the Admin Portal. When this flag is enabled, interval
events will also include a list of clients (User IDs) that joined, left or timed-out since the last interval
event. The following is a simple representation of a Presence Delta event payload.
{
"action": "interval",
"channel": "chats.megachat",
"occupancy": 27,
"join": ["user123","user88"],
"leave": ["user20", "user11", "user14"],
"timeout": ["user42"],
"subscribedChannel": "chats.megachat-pnpres",
"timestamp": 1511947739,
"timetoken": "15119477396210903",
"hereNowRefresh": false
}
Here now refresh flag
Both the Presence Deltas on a channel in Interval mode and the Presence Interval Webhooks behave similarly when the payload size exceeds the maximum limit.
If the presence delta data increases the payload beyond the maximum publish size (32KB), the excess data is removed, and a hereNowRefresh
flag is included in the payload. This flag indicates that you should use the Here Now API to retrieve the list of currently active User IDs.