Presence Events
PubNub triggers presence events as users come online or go offline from the application.
Clients can receive these events directly or use webhooks to keep a user's online/offline status up to date. Use these events to keep track of active/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 | Fires when a user subscribes to a channel. |
leave | Fires when a user unsubscribes from a channel. |
timeout | Fires when the subscriber doesn't make any activity on a channel in a given period of time. By default, this occurs after 300 seconds for most SDKs. This interval can be customized on the client using the presenceTimeout property (the name can differ depending on the SDK). Also, you can configure the client to send heartbeat intervals (using the heartbeatInterval property) to ensure periodic pings to avoid timeouts by signaling active connections. |
state-change | Fires anytime the user's state is changed. |
interval | Fires in interval mode to provide an occupancy count and optionally include delta changes. |
Timeout detection
Presence timeout detection is not region-dependent. PubNub SDKs don't actively monitor connectivity - disconnection is detected when subscribe requests timeout (typically 300 seconds) or when the next request fails. Regional timing differences are due to local network conditions, not PubNub's system.
Implicit vs. explicit Presence events
When working with PubNub Presence, it's helpful to understand that there are actually two different ways your application can signal that users are still online. Think of it like this: imagine you're in a coffee shop and want others to know you're still there. You could either wave at people every few minutes (explicit), or just let them notice you whenever you get up to order coffee (implicit).
But first, let's understand how PubNub fundamentally tracks Presence. At its core, PubNub operates on a simple timer system: each user has an invisible countdown timer that determines when they should be marked as "offline." The key insight is understanding what resets this timer and how you can configure the timeout period.
How PubNub tracks users
PubNub automatically tracks user activity as part of its core operation. Every time your client makes a subscribe call to PubNub servers, PubNub treats this as proof that the user is still active. This is called "implicit Presence" and it runs continuously in the background by default. However, implicit heartbeats can be disabled through the Presence Management GUI in the Admin Portal by unchecking the Subscribe Heartbeats
checkbox for the appropriate rule.
This creates an interesting dynamic. PubNub runs an internal timer for each user, and every time it receives an implicit heartbeat (via Subscribe API) from that user, it resets the timer back to zero. Only when a user goes completely silent for the full timer duration does PubNub mark them as offline and trigger a timeout
event. This timer duration is controlled by the presenceTimeout
setting - the same setting that applies to both implicit heartbeats (via Subscribe API) and explicit heartbeats (via Presence Heartbeat API).
So why configure anything if PubNub tracks activity automatically with server pings? The main use case for configuration is when you need faster detection of disconnected users than the default server ping interval of ~280 seconds provides. This is where configuration comes in - you have two main settings that control when users get marked offline.
Configuration settings
Before we dive deeper, let's understand the two main configuration options and how they work together:
-
presenceTimeout
(also calledpresenceHeartbeatValue
ordurationUntilTimeout
in some SDKs) is the server-side timer - it determines how long PubNub's servers wait without receiving a subscribe call from a user before marking them offline. The default is typically around 300 seconds (5 minutes) but can vary by SDK. This timer gets reset every time PubNub receives either an explicit heartbeat (via presence heartbeat API) or an implicit heartbeat (via subscribe API) from that user. -
heartbeatInterval
(sometimes calledpresenceHeartbeatInterval
) is the client-side ping frequency - it controls whether your app proactively sends dedicated "I'm still here" signals (via presence heartbeat API) to reset that server timer. When set to 0 (the default), no explicit pings are sent and you rely purely on implicit heartbeats (via subscribe API) to maintain presence. When set to any positive number (minimum 3 seconds), your SDK automatically sends explicit heartbeat pings 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
The challenge with relying only on implicit Presence comes when you need faster detection of disconnected users. By default, the server expects the client to respond to either an intentionally published message or an empty server "ping" issued every 280 seconds. These server pings reset the long poll subscription and act as implicit heartbeats, keeping their presence timer reset even for inactive users. However, this means you might wait up to your full presenceTimeout
period (default ~300 seconds) before detecting that someone has actually disconnected.
This is where explicit heartbeats come in. When you configure heartbeatInterval
to a value shorter than the server ping interval (e.g., 120 seconds instead of the default ~280 seconds), you're telling the PubNub SDK: "Send more frequent 'I'm still here' signals to detect disconnections faster." These explicit heartbeat signals (via Presence Heartbeat API) work just like the implicit ones (via Subscribe API) - they reset the Presence timeout timer, but allow for quicker detection when users actually go offline.
// 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
Here's where it gets practical. Every heartbeat is an API call, and API calls cost money. If you have 1,000 users and each sends a heartbeat every 60 seconds, that's 1,440,000 extra API calls per day. For most applications where the default server ping interval (~280 seconds) provides adequate disconnection detection, these explicit heartbeats might be unnecessary overhead.
But for applications requiring rapid disconnection detection (like trading platforms or emergency systems), explicit heartbeats with shorter intervals provide faster notification when users actually disconnect, rather than waiting for the full presenceTimeout
period.
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 across SDKs), and subscribe to a channel, the SDK automatically subscribes you 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
If you wish to only subscribe to the presence channel and not the main channel (to avoid increasing its occupancy, for example), subscribe to the main channel's presence -pnpres
channel.
Once you've created the subscription with the receivePresenceEvents
param enabled, and added presence listeners, call subscribe()
to receive presence events. Your client will now 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 goes into interval mode. In this mode, the join
, leave
, timeout
events are replaced by an interval
event which is sent every few seconds with the total occupancy on the channel. The Interval setting is also configurable for 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.