On this page

Presence API for C SDK

Presence lets you track who is online, query and set custom per-user state on a channel, and observe join/leave/timeout activity as it happens. Learn more about the feature itself in the Presence overview.

The C SDK exposes presence through two unrelated mechanisms, and the rest of this page is organized around that split:

  1. Request/response operations: pubnub_here_now(), pubnub_where_now(), pubnub_set_state(), and pubnub_get_state(). Each call returns a pubnub_future_t, exactly like any other feature entry point (see Publish & Subscribe for the shared async model).
  2. Pushed presence events: join, leave, timeout, state-change, and interval notifications delivered through the subscribe listener's on_presence callback. These arrive only if the subscription was created with with_presence enabled.

A third topic, heartbeat, is not an operation at all. It has no callable function and no listener callback, and is driven entirely by configuration on pubnub_config_t, described in Heartbeat below.

Here now

Requires Presence

pubnub_here_now() requires that the Presence add-on is enabled for your key in the Admin Portal.

pubnub_here_now() returns the current occupancy of one or more channels: the connected user IDs (unless suppressed) and, optionally, each user's presence state.

Method(s)

pubnub_future_t pubnub_here_now(pubnub_context_t*             ctx,
const pubnub_here_now_opts_t* opts);

ctx is the initialized context, borrowed for the call. opts is a pointer to the options below, also borrowed for the call.

pubnub_here_now_opts_t fields:

* required
ParameterDescription
channels
Type: const char*
Default:
none
Comma-separated channel names, borrowed, NUL-terminated.
channel_groups
Type: const char*
Default:
NULL
Comma-separated channel-group names, borrowed, NUL-terminated.
include_uuids
Type: uint8_t
Default:
1 (via PUBNUB_HERE_NOW_OPTS_INIT)
Include each occupant's user ID in the response.
include_state
Type: uint8_t
Default:
0
Include each occupant's presence state in the response.
limit
Type: uint32_t
Default:
0 → server default of 100, clamped to 1000
Maximum number of occupants to return per channel. Setting limit above 0 enables occupant-level pagination alongside offset.
offset
Type: uint32_t
Default:
0
Zero-based index of the first occupant to return.
timeout_ms
Type: uint32_t
Default:
0 → falls back to pubnub_config_t::transaction_timeout_ms
Per-request timeout override.

At least one of channels or channel_groups must be non-NULL and non-empty.

No global here-now

A doc comment on pubnub_here_now() describes passing NULL for both channels and channel_groups to query occupancy across every channel. The implementation does not support this: leaving both fields empty returns PUBNUB_ERR_INVALID_ARGUMENT. Always supply at least one.

C-family contract

  • Header#include <pubnub/features/presence.h>
  • Typespubnub_here_now_opts_t, pubnub_here_now_result_t, pubnub_here_now_channel_result_t, pubnub_here_now_occupant_result_t
  • Feature flagPUBNUB_ENABLE_PRESENCE
  • Ownership / lifetimechannels/channel_groups are borrowed for the call only. Result string views (channel names, user IDs, state) stay valid until pubnub_future_release().
  • Blocking — never blocks; returns a pubnub_future_t immediately. Consume it by cooperative polling, blocking await, or callback (see Environment Setup).

Sample code

#include "pubnub/pubnub.h"

#include <stdio.h>

int main(void)
{
/* 1. Configure the client. */
pubnub_config_t cfg = pubnub_config_defaults();
cfg.subscribe_key = "demo";
cfg.user_id = "example-here-now";

pubnub_context_t* ctx = pubnub_create(&cfg);
if (NULL == ctx) {
printf("pubnub_create failed\n");
return 1;
show all 60 lines

Returns

pubnub_here_now_result(future) returns a pubnub_here_now_result_t:

FieldTypeDescription
total_occupancy
uint32_t
Aggregate occupancy across every queried channel.
total_channels
uint32_t
Server-reported total channel count.
channel_count
uint32_t
Iteration bound for pubnub_here_now_result_channel_at().

Iterate channels with pubnub_here_now_result_channel_at(future, index) for index in [0, channel_count), returning a pubnub_here_now_channel_result_t:

FieldTypeDescription
name
pubnub_string_view_t
Channel name.
occupancy
uint32_t
Occupancy of this channel.
occupant_count
uint32_t
Iteration bound for pubnub_here_now_result_occupant_at().

Iterate occupants with pubnub_here_now_result_occupant_at(future, ch_index, occ_index) for occ_index in [0, occupant_count), returning a pubnub_here_now_occupant_result_t:

FieldTypeDescription
uuid
pubnub_string_view_t
Occupant's user ID.
state
pubnub_string_view_t
Raw JSON state text, or {NULL,0} when absent.
note
This state is a string, not a parsed tree

pubnub_here_now_occupant_result_t.state is a raw pubnub_string_view_t: the JSON bytes as sent by the server, not a parsed node. This is different from set_state/get_state below, whose state fields are const pubnub_json_value_t* parsed trees. To read here-now state as structured data, parse it yourself with the serialization provider's parse() entry.

All fields, and every indexed accessor, zero-initialize when the future is invalid, not ready, or carries an error, or when an index is out of range. Every string view returned by these accessors stays valid until pubnub_future_release() is called on the future. That is longer-lived than the presence-event fields covered in Presence events below.

Error responses

Reading the future's status after it completes can produce:

ResultCause
PUBNUB_ERR_INVALID_ARGUMENT
opts is NULL; the context is invalid or uninitialized; or both channels and channel_groups are empty.
PUBNUB_ERR_NOT_INITIALIZED
The context has no allocator configured (should not occur in normal use).
PUBNUB_ERR_OUT_OF_MEMORY
Allocation failed while building the request.

For the full result-value catalog and how to read a server-side error in detail, see Status Events.

Other examples

Async callback variant

All four presence request/response operations support the same async pattern: submit the call, register a completion callback with pubnub_async(), and read the result inside that callback instead of polling.

#include "pubnub/pubnub.h"

#include <stdio.h>
#include <stdlib.h>

static volatile int s_done;

static void on_here_now_complete(pubnub_future_t future,
pubnub_res_t status,
void* user_data)
{
(void)user_data;

if (PUBNUB_OK == status) {
pubnub_here_now_result_t r = pubnub_here_now_result(future);
show all 64 lines

Where now

Requires Presence

pubnub_where_now() requires that the Presence add-on is enabled for your key in the Admin Portal.

pubnub_where_now() returns the list of channels a given user ID is currently present on.

Timeout events

If the application restarts, or a device reconnects, within the heartbeat window, no timeout event fires for the previous session.

Method(s)

pubnub_future_t pubnub_where_now(pubnub_context_t*              ctx,
const pubnub_where_now_opts_t* opts);

ctx is the initialized context, borrowed for the call. opts is a pointer to the options below, also borrowed for the call.

pubnub_where_now_opts_t fields:

* required
ParameterDescription
uuid
Type: const char*
Default:
NULL → the context's own user_id
User ID to look up, borrowed, NUL-terminated.
timeout_ms
Type: uint32_t
Default:
0 → falls back to pubnub_config_t::transaction_timeout_ms
Per-request timeout override.

C-family contract

  • Header#include <pubnub/features/presence.h>
  • Typespubnub_where_now_opts_t, pubnub_where_now_result_t
  • Feature flagPUBNUB_ENABLE_PRESENCE
  • Ownership / lifetimeuuid is borrowed for the call only. Result string views stay valid until pubnub_future_release().
  • Blocking — never blocks; same future model as Here now.

Sample code

#include "pubnub/pubnub.h"

#include <stdio.h>

int main(void)
{
/* 1. Configure the client. */
pubnub_config_t cfg = pubnub_config_defaults();
cfg.subscribe_key = "demo";
cfg.user_id = "example-where-now";

pubnub_context_t* ctx = pubnub_create(&cfg);
if (NULL == ctx) {
printf("pubnub_create failed\n");
return 1;
show all 47 lines

Returns

pubnub_where_now_result(future) returns a pubnub_where_now_result_t:

FieldTypeDescription
channel_count
uint32_t
Number of channels the user is present on; iteration bound for pubnub_where_now_result_channel_at().

Iterate channels with pubnub_where_now_result_channel_at(future, index) for index in [0, channel_count), returning a pubnub_string_view_t channel name. Zero-initialized ({NULL,0}) when out of range or the future is not ready. Valid until pubnub_future_release().

Error responses

Reading the future's status after it completes can produce:

ResultCause
PUBNUB_ERR_INVALID_ARGUMENT
opts is NULL, or the context is invalid or uninitialized.
PUBNUB_ERR_NOT_INITIALIZED
The context has no allocator configured.

User state

Requires Presence

pubnub_set_state() and pubnub_get_state() require that the Presence add-on is enabled for your key in the Admin Portal.

A user can attach a custom JSON state object (score, typing status, location) to their presence on one or more channels. State is not persisted: once the client disconnects, the state is gone. See Presence state for the conceptual model.

Set state

Method(s)

pubnub_future_t pubnub_set_state(pubnub_context_t*              ctx,
const pubnub_set_state_opts_t* opts);

ctx is the initialized context, borrowed for the call. opts is a pointer to the options below, also borrowed for the call.

pubnub_set_state_opts_t fields:

* required
ParameterDescription
channels
Type: const char*
Default:
NULL
Comma-separated channel names, borrowed.
channel_groups
Type: const char*
Default:
NULL
Comma-separated channel-group names, borrowed.
state
Type: const char*
Default:
NULL
Raw JSON-object string, borrowed.
state_len
Type: size_t
Default:
0 → SDK calls strlen(state)
Length of state in bytes.
state_value
Type: pubnub_json_value_t*
Default:
NULL
JSON value tree, borrowed during the call only.
timeout_ms
Type: uint32_t
Default:
0
Per-request timeout override.

At least one of channels or channel_groups is required.

danger
Set exactly one of state or state_value

The header documents only that setting both state and state_value to non-NULL is an error. The implementation enforces more than that: leaving both fields NULL is rejected with PUBNUB_ERR_INVALID_ARGUMENT as well. Set exactly one, never zero and never two.

C-family contract

  • Header#include <pubnub/features/presence.h>
  • Typespubnub_set_state_opts_t, pubnub_set_state_result_t, pubnub_json_value_t, pubnub_serialization_provider_t
  • Feature flagPUBNUB_ENABLE_PRESENCE
  • Ownership / lifetimechannels/channel_groups/state are borrowed for the call only. If you use state_value, the SDK serializes the tree synchronously inside pubnub_set_state() before the call returns — you can call pubnub_json_destroy() on the tree immediately afterward, without waiting for the future to complete or be released.
  • Blocking — never blocks; same future model as Here now.

Sample code: raw JSON string

#include "pubnub/pubnub.h"

#include <stdio.h>

int main(void)
{
/* 1. Configure the client. */
pubnub_config_t cfg = pubnub_config_defaults();
cfg.subscribe_key = "demo";
cfg.user_id = "example-set-state";

pubnub_context_t* ctx = pubnub_create(&cfg);
if (NULL == ctx) {
printf("pubnub_create failed\n");
return 1;
show all 51 lines

Sample code: JSON value tree

#include "pubnub/pubnub.h"

#include "pubnub/json_macros.h"

#include <stdio.h>

int main(void)
{
/* 1. Configure the client. */
pubnub_config_t cfg = pubnub_config_defaults();
cfg.subscribe_key = "demo";
cfg.user_id = "example-set-state-value";

pubnub_context_t* ctx = pubnub_create(&cfg);
if (NULL == ctx) {
show all 63 lines

Returns

pubnub_set_state_result(future) returns a pubnub_set_state_result_t:

FieldTypeDescription
state
const pubnub_json_value_t*
Server-echoed state object, NULL when the server did not echo state. Valid until pubnub_future_release(). Walk it with the serialization provider's object_get().

state is a single aggregate object regardless of how many channels were targeted. There is no per-channel confirmed-state accessor for set_state.

Error responses

ResultCause
PUBNUB_ERR_INVALID_ARGUMENT
opts is NULL; the context is invalid; both channels/channel_groups are empty; or state/state_value are not set to exactly one.
PUBNUB_ERR_PROVIDER_MISSING
The serialization provider, or its serialize entry, is unavailable.
PUBNUB_ERR_SERIALIZATION
Serialization of state_value produced zero bytes.
PUBNUB_ERR_OUT_OF_MEMORY
Allocation failed while building the request.

Get state

Method(s)

pubnub_future_t pubnub_get_state(pubnub_context_t*              ctx,
const pubnub_get_state_opts_t* opts);

ctx is the initialized context, borrowed for the call. opts is a pointer to the options below, also borrowed for the call.

pubnub_get_state_opts_t fields:

* required
ParameterDescription
channels
Type: const char*
Default:
NULL
Comma-separated channel names, borrowed.
channel_groups
Type: const char*
Default:
NULL
Comma-separated channel-group names, borrowed.
uuid
Type: const char*
Default:
NULL → the context's own user_id
User ID to query, borrowed.
timeout_ms
Type: uint32_t
Default:
0
Per-request timeout override.

At least one of channels or channel_groups is required.

C-family contract

  • Header#include <pubnub/features/presence.h>
  • Typespubnub_get_state_opts_t, pubnub_get_state_result_t, pubnub_get_state_channel_result_t
  • Feature flagPUBNUB_ENABLE_PRESENCE
  • Ownership / lifetimechannels/channel_groups/uuid are borrowed for the call only. Result views and state pointers stay valid until pubnub_future_release().
  • Blocking — never blocks; same future model as Here now.

Sample code

#include "pubnub/pubnub.h"

#include <stdio.h>

int main(void)
{
/* 1. Configure the client. */
pubnub_config_t cfg = pubnub_config_defaults();
cfg.subscribe_key = "demo";
cfg.user_id = "example-get-state";

pubnub_context_t* ctx = pubnub_create(&cfg);
if (NULL == ctx) {
printf("pubnub_create failed\n");
return 1;
show all 57 lines

Returns

pubnub_get_state_result(future) returns a pubnub_get_state_result_t:

FieldTypeDescription
channel_count
uint32_t
Number of channels with state entries; iteration bound for pubnub_get_state_result_channel_at().

Iterate with pubnub_get_state_result_channel_at(future, index) for index in [0, channel_count), returning a pubnub_get_state_channel_result_t:

FieldTypeDescription
channel
pubnub_string_view_t
Channel name.
state
const pubnub_json_value_t*
User's state on this channel, NULL when no state is set. Walk with the serialization provider's object_get().

Error responses

ResultCause
PUBNUB_ERR_INVALID_ARGUMENT
opts is NULL; the context is invalid; or both channels/channel_groups are empty.
PUBNUB_ERR_NOT_INITIALIZED
The context has no allocator configured.

Presence events

Presence events are pushed to your subscribe listener as they happen, independently of the request/response operations above. General listener registration, including the pubnub_add_listener() pattern and thread/callback context for global listeners, is covered in Publish & Subscribe and Status Events. This section covers only what is presence-specific.

To receive presence events on a channel, create its subscription with with_presence set on pubnub_subscription_opts_t:

static void subscribe_with_presence(pubnub_context_t* ctx)
{
pubnub_entity_t entity = pubnub_channel(ctx, "demo_channel");
pubnub_subscription_t sub =
pubnub_subscription_create(entity, &(pubnub_subscription_opts_t){ .with_presence = 1 });
pubnub_entity_destroy(entity);
pubnub_subscription_subscribe(sub);
}

with_presence also subscribes to the channel's implicit <channel>-pnpres presence channel. It is silently ignored when the entity is a channel-metadata or user-metadata object.

Register an on_presence callback on a pubnub_subscribe_listener_t, then call pubnub_subscribe_event_presence() inside it to decode the generic subscribe event into a typed pubnub_subscribe_presence_event_t. The callback itself receives a const pubnub_subscribe_event_t*. See The subscribe event struct for its fields and the full list of discriminant values.

C-family contract

  • Header#include <pubnub/features/subscribe.h> and #include <pubnub/features/subscribe_types.h>
  • Typespubnub_subscribe_presence_event_t, pubnub_presence_action_t, pubnub_subscribe_presence_cb_t
  • Feature flagPUBNUB_ENABLE_SUBSCRIBE; presence events are available whenever subscribe is compiled in, independent of PUBNUB_ENABLE_PRESENCE.
  • Thread / callback context — same as every other subscribe listener callback: fires inside pubnub_process() in cooperative mode, or on the background thread on threaded builds (PUBNUB_CFG_THREAD_SAFETY=1), where I/O is driven automatically without any additional call. Keep callbacks fast and non-reentrant. See Status Events for the full model.

pubnub_subscribe_presence_event_t fields:

FieldTypePopulated forDescription
action
pubnub_presence_action_t
always
PUBNUB_PRESENCE_JOIN, _LEAVE, _TIMEOUT, _STATE_CHANGE, _INTERVAL.
uuid
pubnub_string_view_t
join/leave/timeout/state-change
May be empty for interval events.
channel
pubnub_string_view_t
always
Channel the event occurred on.
subscription
pubnub_string_view_t
always
Wildcard or channel-group match pattern.
occupancy
uint32_t
always
Current occupancy count.
timetoken
pubnub_string_view_t
always
Publish timetoken of the event.
state
const pubnub_json_value_t*
when carrying state
NULL when absent. JSON-node pointer, walk with the serialization provider's object_get().
joined
const pubnub_json_value_t*
PUBNUB_PRESENCE_INTERVAL only
JSON array of user IDs that joined since the last interval; NULL when the action is not interval, or when the server-side delta feature is unavailable.
left
const pubnub_json_value_t*
PUBNUB_PRESENCE_INTERVAL only
Same shape as joined, for users that left.
timed_out
const pubnub_json_value_t*
PUBNUB_PRESENCE_INTERVAL only
Same shape as joined, for users that timed out.
here_now_refresh
uint8_t
PUBNUB_PRESENCE_INTERVAL
1 = the joined/left/timed_out arrays were omitted because the payload exceeded roughly 30 KB. Call Here now for the full occupant list. 0 = the arrays are present (subject to the delta-availability note above).
Shorter-lived than the request/response results

Every field in pubnub_subscribe_presence_event_t is valid only for the duration of the listener callback. That is strictly shorter-lived than the here-now/where-now/set-state/get-state results above, which stay valid until pubnub_future_release(). Copy any bytes you need before the callback returns. Never retain these pointers.

No public control over interval deltas

No configuration field, option, or function in the public headers enables or disables the joined/left/timed_out delta arrays. Their availability on PUBNUB_PRESENCE_INTERVAL events depends on a server-side setting the client cannot currently control. Always check these pointers for NULL before use, and treat here_now_refresh as the authoritative signal for whether the arrays are complete.

Sample code

#include "pubnub/pubnub.h"

#include <stdio.h>

static void print_uuid_array(pubnub_serialization_provider_t* serial,
const pubnub_json_value_t* arr,
const char* label)
{
if (NULL == arr || NULL == serial || NULL == serial->array_size
|| NULL == serial->array_get || NULL == serial->value_as_string) {
return;
}

size_t n = serial->array_size(arr);
for (size_t i = 0; i < n; ++i) {
show all 52 lines

Register it, together with an entity subscription created with with_presence = 1, using pubnub_add_listener() as shown in Publish & Subscribe.

Returns

pubnub_subscribe_event_presence(ctx, event, out) returns a pubnub_res_t:

ResultCause
PUBNUB_OK
out populated successfully.
PUBNUB_ERR_INVALID_ARGUMENT
ctx, event, or out is NULL, or event's type is not presence.
PUBNUB_ERR_SERIALIZATION
The event payload failed to parse.

Heartbeat

Heartbeat has no callable function and no listener callback. There is no pubnub_heartbeat(), and no event struct reports individual heartbeat outcomes. It is entirely driven by three fields on pubnub_config_t: presence_timeout, heartbeat_interval, and suppress_leave_events. Their types and defaults are covered in Configuration. This section covers only their presence-specific behavior.

Recurring heartbeat is off unless you set heartbeat_interval

heartbeat_interval at 0 (its default, and what pubnub_config_defaults() leaves it at) disables the recurring client-side heartbeat. The SDK doesn't derive an interval from presence_timeout. When a subscription joins, the SDK still sends one initial heartbeat to announce the client, but no timer repeats it, so the server times the client out after presence_timeout seconds.

To keep the client present, set heartbeat_interval to a non-zero number of seconds, shorter than presence_timeout. presence_timeout only sets the server-side heartbeat= value (the SDK uses 300 seconds when it's 0). It isn't a client-side scheduling fallback.

Once heartbeat is configured, no separate call starts it: creating and activating a subscription (pubnub_subscription_create() followed by pubnub_subscription_subscribe()) is enough. The SDK's internal presence manager sends heartbeat requests automatically every heartbeat_interval seconds for as long as the subscription stays active, alongside the subscribe long-poll itself. There is no way to observe individual heartbeat successes or failures from application code. They surface only indirectly, through the subscription's own status events (see Status Events).

suppress_leave_events controls only whether the SDK sends an explicit leave request when a subscription ends. When set, the server relies solely on presence_timeout expiry to detect the client's departure.