Publish/Subscribe API for C SDK

Publish and signal are single-shot, asynchronous request/response operations. Subscribe is a long-lived streaming operation built on four cooperating concepts: entities (channel/channel-group/metadata handles), subscriptions (an activation of one entity), subscription sets (a group of subscriptions managed together), and listeners (callback structs that receive events). There is exactly one subscribe model in this SDK, with no legacy variant to choose between.

For higher-level conceptual details, refer to Connection Management and Publish Messages.

The typical lifecycle, in order, is: create an entity → create a subscription from it → register listeners → activate the subscription → read events in your listener callbacks → unsubscribe → destroy the subscription → remove the listener → destroy the context. Destroying an entity is safe as soon as a subscription has been created from it. The two do not need to be destroyed together. Getting the rest of this order wrong is the most common resource-management mistake on this page, so each sample below shows a complete, correctly ordered teardown.

Publish​

pubnub_publish() sends a message to all channel subscribers. The message is replicated across the network and delivered to every client currently subscribed to that channel.

Method(s)​

pubnub_future_t pubnub_publish(pubnub_context_t* ctx, const pubnub_publish_opts_t* opts);
* required
ParameterDescription
channel *
Type: const char*
Default:
—
Borrowed, NUL-terminated.
message
Type: const char*
Default:
—
Borrowed. Valid JSON string. Leave message_len at 0 for a NUL-terminated string, or set it for a length-counted one. Setting both message and message_value is PUBNUB_ERR_INVALID_ARGUMENT.
message_len
Type: size_t
Default:
0 (calls strlen)
Byte count when message is not NUL-terminated.
message_value
Type: pubnub_json_value_t*
Default:
—
Borrowed — the SDK serializes it during the call but does not consume it. Destroy it with pubnub_json_destroy() after pubnub_publish() returns. Setting both message and message_value is PUBNUB_ERR_INVALID_ARGUMENT.
method
Type: pubnub_publish_method_t
Default:
PUBNUB_PUBLISH_METHOD_GET
PUBNUB_PUBLISH_METHOD_GET risks hitting the URL-length/message-size ceiling from percent-encoding. PUBNUB_PUBLISH_METHOD_POST sends the message in the request body and risks size growth only from encryption.
compress
Type: pubnub_publish_compress_t
Default:
PUBNUB_PUBLISH_COMPRESS_DEFAULT
Request-body compression preference. Only takes effect when method == PUBNUB_PUBLISH_METHOD_POST; ignored for GET, which has no body to compress. DEFAULT follows the compile-time PUBNUB_ENABLE_REQUEST_COMPRESSION toggle, YES requests compression, NO opts this call out. When compression applies, the body is gzip-encoded with a Content-Encoding: gzip header.
store
Type: pubnub_publish_store_t
Default:
PUBNUB_PUBLISH_STORE_ACCOUNT_DEFAULT
Whether to persist the message in Message Persistence. Set to PUBNUB_PUBLISH_STORE_YES to force storage regardless of the account default, or PUBNUB_PUBLISH_STORE_NO to force skipping it.
ttl
Type: unsigned int
Default:
0 (account default)
Hours to retain the message. Ignored when store == PUBNUB_PUBLISH_STORE_NO.
meta
Type: const char*
Default:
—
Borrowed. Valid JSON string used for stream filtering. Setting both meta and meta_value is PUBNUB_ERR_INVALID_ARGUMENT.
meta_len
Type: size_t
Default:
0 (calls strlen)
Byte count when meta is not NUL-terminated.
meta_value
Type: pubnub_json_value_t*
Default:
—
Borrowed, same destroy-after-call contract as message_value.
custom_message_type
Type: const char*
Default:
—
Borrowed, NUL-terminated. Must be 3-50 characters.
timeout_ms
Type: uint32_t
Default:
0 (inherits pubnub_config_t::transaction_timeout_ms)
A non-zero value overrides the context-level timeout for this call only.

Zero-initialize with PUBNUB_PUBLISH_OPTS_INIT (expands to {0}) so every unspecified field takes its protocol-safe default.

C-family contract:

  • Header — #include <pubnub/features/publish.h>
  • Types — pubnub_publish_opts_t, pubnub_publish_store_t, pubnub_publish_method_t, pubnub_publish_compress_t
  • Prerequisite — an initialized context with publish_key, subscribe_key, and user_id set
  • Feature flag — PUBNUB_ENABLE_PUBLISH
  • Ownership / lifetime — message/meta are borrowed, NUL-terminated or length-counted strings; message_value/meta_value are borrowed JSON trees the caller must destroy after the call returns
  • Blocking — never blocks; returns a pubnub_future_t immediately

Sample code​

#include <pubnub/client.h>
#include <pubnub/features/publish.h>
#include <pubnub/future.h>

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

int main(void)
{
pubnub_config_t cfg = pubnub_config_defaults();
cfg.publish_key = "demo";
cfg.subscribe_key = "demo";
cfg.user_id = "my_unique_user_id";

pubnub_context_t* ctx = pubnub_create(&cfg);
show all 38 lines

This sample is adapted from examples/publish/sync.c (blocking pubnub_await). The SDK also ships examples/publish/async.c (callback) and examples/publish/cooperative.c (cooperative polling). These are the same call, consumed the three ways described in Environment Setup.

Returns​

pubnub_timetoken_t pubnub_publish_result_timetoken(pubnub_future_t future);

Call this only after the future completes with PUBNUB_OK. It returns the timetoken the server assigned to the published message.

Other examples​

Publish a JSON value tree​

Instead of building a JSON string by hand, construct a pubnub_json_value_t* tree with PUBNUB_JSON_OBJ/PUBNUB_JSON_KV_* (gated by PUBNUB_CFG_JSON_HELPERS) and pass it as message_value. Adapted from examples/publish/value.c.

#include <pubnub/client.h>
#include <pubnub/features/publish.h>
#include <pubnub/future.h>
#include <pubnub/json.h>
#include <pubnub/json_macros.h>

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

int main(void)
{
pubnub_config_t cfg = pubnub_config_defaults();
cfg.publish_key = "demo";
cfg.subscribe_key = "demo";
cfg.user_id = "my_unique_user_id";
show all 48 lines
Macro argument evaluation

Every PUBNUB_JSON_KV_* macro evaluates its serial argument (and, for _STR, its string argument) more than once. Pass only side-effect-free expressions.

Publish several messages concurrently​

Multiple in-flight futures can be tracked in an array and awaited independently. Adapted from examples/publish/concurrent.c.

#include <pubnub/client.h>
#include <pubnub/features/publish.h>
#include <pubnub/future.h>

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

#define MESSAGE_COUNT 3

int main(void)
{
pubnub_config_t cfg = pubnub_config_defaults();
cfg.publish_key = "demo";
cfg.subscribe_key = "demo";
cfg.user_id = "my_unique_user_id";
show all 40 lines

Error responses​

pubnub_res_t is the universal status type for this SDK; see Status Events for the full catalog. Two behaviors specific to publish are worth calling out:

  • Setting both message and message_value (or both meta and meta_value) fails immediately with PUBNUB_ERR_INVALID_ARGUMENT, readable via pubnub_future_status() without polling or awaiting.
  • Request capacity is two-tiered: while the in-flight pool (PUBNUB_CFG_MAX_IN_FLIGHT_REQUESTS) has room, a publish starts immediately. Once it is exhausted, a new publish is placed in a pending queue (PUBNUB_CFG_MAX_PENDING_REQUESTS) and its future reports PUBNUB_IN_PROGRESS, not an error, until a slot frees up. PUBNUB_ERR_QUEUE_FULL is reserved for the case where both the in-flight pool and the pending queue are full.

Signal​

pubnub_signal() sends a signal, a small, unpersisted message, to all subscribers of a channel. Signals are always sent over HTTP GET and are never stored in Message Persistence.

FeatureSignalMessage
Payload size64 bytes (server-enforced)Up to 32 KiB
PersistenceNever storedCan be stored
Metadata (meta)Not supportedSupported
HTTP methodAlways GETGET or POST

Method(s)​

pubnub_future_t pubnub_signal(pubnub_context_t* ctx, const pubnub_signal_opts_t* opts);
* required
ParameterDescription
channel *
Type: const char*
Default:
—
Borrowed, NUL-terminated.
message
Type: const char*
Default:
—
Borrowed. Valid JSON string. Setting both message and message_value is PUBNUB_ERR_INVALID_ARGUMENT.
message_len
Type: size_t
Default:
0 (calls strlen)
Byte count when message is not NUL-terminated.
message_value
Type: pubnub_json_value_t*
Default:
—
Borrowed; destroy with pubnub_json_destroy() after the call returns.
custom_message_type
Type: const char*
Default:
—
Borrowed, NUL-terminated, 3-50 characters.
timeout_ms
Type: uint32_t
Default:
0 (inherits context default)
Non-zero overrides the context-level timeout.

There is no meta, store, ttl, or method field. Signals carry none of publish's persistence options. Zero-initialize with PUBNUB_SIGNAL_OPTS_INIT.

C-family contract:

  • Header — #include <pubnub/features/signal.h>
  • Types — pubnub_signal_opts_t
  • Prerequisite — an initialized context with publish_key, subscribe_key, and user_id set
  • Feature flag — PUBNUB_ENABLE_SIGNAL
  • Ownership / lifetime — same borrow rules as publish's message/message_value
  • Blocking — never blocks; returns a pubnub_future_t immediately

Sample code​

#include <pubnub/client.h>
#include <pubnub/features/signal.h>
#include <pubnub/future.h>

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

int main(void)
{
pubnub_config_t cfg = pubnub_config_defaults();
cfg.publish_key = "demo";
cfg.subscribe_key = "demo";
cfg.user_id = "my_unique_user_id";

pubnub_context_t* ctx = pubnub_create(&cfg);
show all 31 lines

Adapted from examples/signal/cooperative.c, consumed here with blocking pubnub_await for brevity. examples/signal/async.c shows the callback style.

Returns​

pubnub_timetoken_t pubnub_signal_result_timetoken(pubnub_future_t future);

Call this only after the future completes with PUBNUB_OK.

Entities​

Entities are thin, opaque handles that identify what to subscribe to. They carry no operations of their own. Every publish, signal, and subscribe function takes a context plus plain string identifiers, never an entity handle.

pubnub_entity_t pubnub_channel(pubnub_context_t* ctx, const char* name);
pubnub_entity_t pubnub_channel_group(pubnub_context_t* ctx, const char* name);
pubnub_entity_t pubnub_channel_metadata(pubnub_context_t* ctx, const char* id);
pubnub_entity_t pubnub_user_metadata(pubnub_context_t* ctx, const char* id);

void pubnub_entity_destroy(pubnub_entity_t entity);
const char* pubnub_entity_name(pubnub_entity_t entity);
pubnub_subscribe_entity_type_t pubnub_entity_type(pubnub_entity_t entity);

pubnub_entity_type() returns a pubnub_subscribe_entity_type_t identifying which factory produced the entity:

ValueMeaningProduced by
PUBNUB_SUBSCRIBE_CHANNEL (0)Regular channel.pubnub_channel()
PUBNUB_SUBSCRIBE_CHANNEL_GROUP (1)Channel group, resolved server-side.pubnub_channel_group()
PUBNUB_SUBSCRIBE_CHANNEL_METADATA (2)Channel metadata entity (App Context).pubnub_channel_metadata()
PUBNUB_SUBSCRIBE_USER_METADATA (3)User metadata entity (App Context).pubnub_user_metadata()

Do not confuse this enum with pubnub_subscribe_message_type_t (see The subscribe event struct). Both share the PUBNUB_SUBSCRIBE_ prefix, but one identifies what kind of entity you subscribed to and the other identifies what kind of event arrived. They are different types with different value sets.

Unlike most const char* fields in this SDK, the name/id argument to each factory function is copied by the SDK, not borrowed. The caller does not need to keep it alive past the call. Channel names have a server limit of 2048 characters. Channel-group names are limited to 92 characters.

Each factory returns NULL when:

  • ctx is NULL, or has no subscribe_key or user_id.
  • name or id is NULL, empty, or longer than 65535 bytes.
  • The entity table is full, meaning PUBNUB_CFG_MAX_SUBSCRIBE_CHANNELS distinct entities already exist. Retrying cannot succeed until you destroy an entity. Rebuild the SDK with a larger value if you need more.
  • The entity already has the maximum number of references.
  • A memory allocation fails.

A full entity table is also reported through the logger when logging is enabled.

An entity handle may be destroyed immediately after creating a subscription from it. pubnub_subscription_create()'s own doc states this explicitly, and both examples/subscribe/callback.c and examples/arena_echo/arena_echo.c do exactly that. There is no need to keep the entity alive for the lifetime of the subscription.

Create a subscription​

A subscription activates one entity so it starts receiving events. Creating a subscription does not itself start receiving events. Call pubnub_subscription_subscribe() to activate it.

Method(s)​

pubnub_subscription_t pubnub_subscription_create(pubnub_entity_t entity, const pubnub_subscription_opts_t* opts);
void pubnub_subscription_destroy(pubnub_subscription_t sub);
pubnub_res_t pubnub_subscription_subscribe(pubnub_subscription_t sub);
pubnub_res_t pubnub_subscription_unsubscribe(pubnub_subscription_t sub);
* required
ParameterDescription
with_presence
Type: uint8_t
Default:
0
1 also receives presence events for the entity (its -pnpres channel). Silently ignored for metadata entities.

Passing NULL for opts to pubnub_subscription_create() is valid and uses the defaults. Zero-initialize an explicit struct with PUBNUB_SUBSCRIPTION_OPTS_INIT.

with_presence is a per-subscription setting. Each handle decides for itself whether its listeners receive presence events. A channel name that ends in .*, such as chat.*, matches events from the channels under that prefix without any other setup. Passing an entity or subscription handle you already destroyed is undefined behavior.

pubnub_subscription_destroy() unsubscribes the handle first when it is active and not a member of a subscription set. Its channels then leave presence. The call also detaches every listener registered on that handle, and you can call it from inside a listener callback. The handle is invalid as soon as the call returns, even if it is still a member of a set.

C-family contract:

  • Header — #include <pubnub/features/subscribe.h>
  • Types — pubnub_subscription_t, pubnub_subscription_opts_t, pubnub_entity_t
  • Prerequisite — an entity handle created from the same context
  • Feature flag — PUBNUB_ENABLE_SUBSCRIBE
  • Ownership / lifetime — pubnub_subscription_create() consumes nothing from the entity; both handles can coexist, but the subscription itself must eventually be released with pubnub_subscription_destroy()
  • Blocking — pubnub_subscription_subscribe()/_unsubscribe() return pubnub_res_t directly (no future); they do not block on network I/O to return, they only enqueue the state change

Sample code​

#include <pubnub/client.h>
#include <pubnub/features/subscribe.h>
#include <pubnub/features/subscribe_types.h>

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

int main(void)
{
pubnub_config_t cfg = pubnub_config_defaults();
cfg.publish_key = "demo";
cfg.subscribe_key = "demo";
cfg.user_id = "my_unique_user_id";

pubnub_context_t* ctx = pubnub_create(&cfg);
show all 39 lines

Adapted from examples/subscribe/callback.c.

The code below checks the result of pubnub_subscription_create() against PUBNUB_SUBSCRIPTION_INVALID.

1

Returns​

pubnub_subscription_create() returns a pubnub_subscription_t handle. On failure it returns PUBNUB_SUBSCRIPTION_INVALID, a NULL-valued handle. Compare the result against this sentinel before you use it. The call fails when:

  • entity is NULL.
  • The entity no longer refers to a live subscribe registry entry, for example after the context's subscribe state was torn down.
  • The entity already has the maximum number of references.
  • The subscription table is full, meaning PUBNUB_CFG_MAX_SUBSCRIPTIONS live subscriptions already exist. Retrying cannot succeed until you destroy a subscription. Rebuild the SDK with a larger value if you need more.
  • A memory allocation fails.

A full subscription table is also reported through the logger when logging is enabled.

pubnub_subscription_subscribe()/_unsubscribe() return PUBNUB_OK on success, or PUBNUB_ERR_INVALID_ARGUMENT if sub is NULL.

Subscription sets​

A subscription set groups multiple subscriptions so they can be subscribed to, unsubscribed from, and listened to together.

Method(s)​

pubnub_subscription_set_t pubnub_subscription_set_create(pubnub_context_t* ctx);
void pubnub_subscription_set_destroy(pubnub_subscription_set_t set);

pubnub_res_t pubnub_subscription_set_add_subscription(pubnub_subscription_set_t set, pubnub_subscription_t sub);
pubnub_res_t pubnub_subscription_set_remove_subscription(pubnub_subscription_set_t set, pubnub_subscription_t sub);
pubnub_res_t pubnub_subscription_set_add_subscription_set(pubnub_subscription_set_t target, pubnub_subscription_set_t other);
pubnub_res_t pubnub_subscription_set_remove_subscription_set(pubnub_subscription_set_t target, pubnub_subscription_set_t other);

pubnub_res_t pubnub_subscription_set_subscribe(pubnub_subscription_set_t set);
pubnub_res_t pubnub_subscription_set_unsubscribe(pubnub_subscription_set_t set);

A subscription set has no options struct. pubnub_subscription_set_create() takes only a context. Membership is managed entirely through the add/remove/merge functions above.

C-family contract:

  • Header — #include <pubnub/features/subscribe.h>
  • Types — pubnub_subscription_set_t
  • Prerequisite — one or more pubnub_subscription_t handles created on the same context
  • Feature flag — PUBNUB_ENABLE_SUBSCRIBE
  • Ownership / lifetime — a subscription can belong to a set and be subscribed to individually at the same time; each acquisition (entity, subscription, set membership) holds an independent reference, and the underlying channel entry is released only when every reference is gone
  • Blocking — none of these functions block; each returns pubnub_res_t directly

The same subscription can be a member of a set and subscribed individually at the same time. The shared registry entry tracks a reference count across all acquisitions, and a presence "leave" is sent only when the last reference is released. If a set is already subscribed when a new subscription is added to it, that member starts receiving events immediately, without a separate subscribe() call. The same is true when merging with pubnub_subscription_set_add_subscription_set().

pubnub_subscription_set_remove_subscription() makes the set drop its reference to the subscription. If that was the last reference (you already destroyed your own handle and no other set holds it), the handle is freed. If the set is subscribed and the subscription was its last member for the channel, the SDK sends a presence leave. pubnub_subscription_set_remove_subscription_set() works the same way for each member found in other, and leaves other unchanged.

Destroying a set does not require destroying its members first: pubnub_subscription_set_destroy() releases the set's reference to every member in one call, and a member handle is freed only once nothing else references it. If the set was subscribed and held the last active reference to a channel, the SDK sends a presence leave. The call also detaches every listener registered on the set, and you can call it from inside a listener callback.

A set holds at most PUBNUB_CFG_MAX_SUBSCRIPTIONS_PER_SET members, and a context holds at most PUBNUB_CFG_MAX_SUBSCRIPTION_SETS live sets.

Asymmetric not-found behavior

pubnub_subscription_set_remove_subscription() returns PUBNUB_ERR_INVALID_ARGUMENT if the subscription is not a member of the set. This is a hard error. pubnub_subscription_set_remove_subscription_set() silently skips any entries from other that are not found in target. This is not an error. Do not assume the two functions behave the same way on a missing entry.

A failed pubnub_subscription_set_add_subscription_set() leaves target unchanged. There is no partial merge. Check the return value to learn why it failed.

Sample code​

pubnub_subscription_set_create() returns PUBNUB_SUBSCRIPTION_SET_INVALID (a NULL-valued pubnub_subscription_set_t) if set creation fails. Check the returned handle against this sentinel before using it. Passing an invalid set to any of the other functions on this page returns PUBNUB_ERR_INVALID_ARGUMENT.

pubnub_entity_t channel_a = pubnub_channel(ctx, "channel_a");
pubnub_entity_t channel_b = pubnub_channel(ctx, "channel_b");

pubnub_subscription_t sub_a = pubnub_subscription_create(channel_a, NULL);
pubnub_subscription_t sub_b = pubnub_subscription_create(channel_b, NULL);
pubnub_entity_destroy(channel_a);
pubnub_entity_destroy(channel_b);

pubnub_subscription_set_t set = pubnub_subscription_set_create(ctx);
if (PUBNUB_SUBSCRIPTION_SET_INVALID == set) {
printf("subscription set creation failed\n");
/* clean up sub_a/sub_b and return */
}

pubnub_subscription_set_add_subscription(set, sub_a);
show all 17 lines

The code below handles PUBNUB_ERR_LIMIT_REACHED when a subscription set is full.

1

Returns​

pubnub_subscription_set_create() returns a pubnub_subscription_set_t handle. It returns PUBNUB_SUBSCRIPTION_SET_INVALID when ctx is NULL, when the set table is full (PUBNUB_CFG_MAX_SUBSCRIPTION_SETS live sets), or when a memory allocation fails. A full set table is also reported through the logger when logging is enabled. Every other function above returns pubnub_res_t:

  • PUBNUB_OK on success (including "already a member" for adds, which is de-duplicated rather than rejected)
  • PUBNUB_ERR_INVALID_ARGUMENT for a NULL/invalid handle, a cross-context handle, or (for single-subscription removal only) a not-found member
  • PUBNUB_ERR_LIMIT_REACHED from pubnub_subscription_set_add_subscription() if the set already holds PUBNUB_CFG_MAX_SUBSCRIPTIONS_PER_SET members, and from pubnub_subscription_set_add_subscription_set() if the merge would push target past that limit. Retrying cannot succeed. Rebuild the SDK with a larger value.
  • PUBNUB_ERR_QUEUE_FULL for any other add or merge failure, for example when a handle reached its maximum reference count
  • PUBNUB_ERR_OUT_OF_MEMORY from pubnub_subscription_set_add_subscription() if the subscribe state cannot be allocated
  • PUBNUB_ERR_NOT_INITIALIZED from _subscribe()/_unsubscribe() if subscribe support is not compiled in

List active subscriptions and sets​

These functions read the subscribe state of a context. They tell you which subscriptions and subscription sets are active right now, and which subscriptions belong to a set.

Method(s)​

pubnub_res_t pubnub_subscriptions(
pubnub_context_t* ctx,
pubnub_subscription_t* out,
size_t max_count,
size_t* out_count);

pubnub_res_t pubnub_subscription_sets(
pubnub_context_t* ctx,
pubnub_subscription_set_t* out,
size_t max_count,
size_t* out_count);

pubnub_res_t pubnub_subscription_set_subscriptions(
pubnub_subscription_set_t set,
pubnub_subscription_t* out,
show all 17 lines
* required
ParameterDescription
ctx
Type: pubnub_context_t*
Default:
n/a
Initialized context. Borrowed.
set
Type: pubnub_subscription_set_t
Default:
n/a
Subscription set whose members you want to list. Borrowed.
out *
Type: pubnub_subscription_t* or pubnub_subscription_set_t*
Default:
n/a
Array you allocate. The function writes handles into it, starting at index 0.
max_count *
Type: size_t
Default:
n/a
Capacity of out, in elements.
out_count
Type: size_t*
Default:
NULL
Receives the total number of matching items, even when that is larger than max_count. Pass NULL if you do not need it.

What each function lists:

  • pubnub_subscriptions() lists the subscriptions on the context that are currently subscribed. A subscription you created but have not subscribed is not listed.
  • pubnub_subscription_sets() lists the subscription sets on the context that are currently subscribed. A set you created but have not subscribed is not listed.
  • pubnub_subscription_set_subscriptions() lists every member of the set, whether or not the set is subscribed.

Several handles can target the same channel, so the number of results can be larger than the number of distinct channels.

C-family contract:

  • Header: #include <pubnub/features/subscribe.h>
  • Types: pubnub_subscription_t, pubnub_subscription_set_t
  • Feature flag: PUBNUB_ENABLE_SUBSCRIBE
  • Ownership / lifetime: the handles written to out are borrowed. They stay valid until the subscription or set is destroyed. Do not pass a listed handle to pubnub_subscription_destroy() or pubnub_subscription_set_destroy() unless you own it, because the call can free a handle that your own code still uses. The handles from pubnub_subscription_set_subscriptions() are the set's real member handles, so the same rule applies.
  • Thread safety: each call holds the context lock while it copies handles, so you can call it from any thread. The result is a snapshot. Another thread can subscribe, unsubscribe, or destroy a handle right after the call returns, so a listed handle can be stale by the time you use it.
  • Blocking: none of these functions perform network I/O.
  • Limits: size out with PUBNUB_CFG_MAX_SUBSCRIPTIONS for pubnub_subscriptions(), PUBNUB_CFG_MAX_SUBSCRIPTION_SETS for pubnub_subscription_sets(), and PUBNUB_CFG_MAX_SUBSCRIPTIONS_PER_SET for pubnub_subscription_set_subscriptions() to never truncate. See Environment setup for the defaults per build profile.

Sample code​

n/a. A runnable example for these functions is not published yet.

Returns​

All three functions return a pubnub_res_t:

  • PUBNUB_OK on success. This includes the case where the context has no subscribe state, in which *out_count is 0.
  • PUBNUB_ERR_BUFFER_TOO_SMALL when max_count is smaller than the number of matching items. The function fills out with the first max_count handles and still sets *out_count to the full total. Allocate a buffer of that size and call again.
  • PUBNUB_ERR_INVALID_ARGUMENT when ctx (or set) or out is NULL. For pubnub_subscription_set_subscriptions(), also when the set was already destroyed. In these cases *out_count is set to 0 when you pass it.

Read only the first min(*out_count, max_count) entries of out. The function does not write to the rest of the array.

For the full result-code catalog, see Status Events.

Listeners​

A listener is a struct of optional callback fields. Register it at one of three levels, and it receives the events that level scopes to.

typedef struct pubnub_subscribe_listener {
pubnub_subscribe_status_cb_t on_status; /* const pubnub_subscribe_status_event_t*, void* */
pubnub_subscribe_message_cb_t on_message; /* const pubnub_subscribe_event_t*, void* */
pubnub_subscribe_signal_cb_t on_signal; /* const pubnub_subscribe_event_t*, void* */
pubnub_subscribe_presence_cb_t on_presence; /* const pubnub_subscribe_event_t*, void* */
pubnub_subscribe_message_action_cb_t on_message_action; /* const pubnub_subscribe_event_t*, void* */
pubnub_subscribe_app_context_cb_t on_app_context; /* const pubnub_subscribe_event_t*, void* */
pubnub_subscribe_file_cb_t on_file; /* const pubnub_subscribe_event_t*, void* */
void* user_data;
} pubnub_subscribe_listener_t;

Every callback field is optional and individually nullable. Set only the ones you need. None of them receives the context as a parameter: every callback takes only a const-pointer event struct and the user_data you supplied at registration. If a callback needs the context, for example to call one of the typed event extractors, carry it yourself through user_data.

on_status receives a pubnub_subscribe_status_event_t*. The other six callbacks all receive the same pubnub_subscribe_event_t* (see The subscribe event struct). Both event structs are valid only for the duration of the callback invocation. Copy out any fields you need afterward.

Method(s)​

pubnub_listener_handle_t pubnub_add_listener(pubnub_context_t* ctx, const pubnub_subscribe_listener_t* listener);
void pubnub_remove_listener(pubnub_context_t* ctx, pubnub_listener_handle_t handle);

pubnub_listener_handle_t pubnub_subscription_add_listener(pubnub_subscription_t sub, const pubnub_subscribe_listener_t* listener);
void pubnub_subscription_remove_listener(pubnub_subscription_t sub, pubnub_listener_handle_t handle);

pubnub_listener_handle_t pubnub_subscription_set_add_listener(pubnub_subscription_set_t set, const pubnub_subscribe_listener_t* listener);
void pubnub_subscription_set_remove_listener(pubnub_subscription_set_t set, pubnub_listener_handle_t handle);
Registration levelScope of data events (message/signal/presence/message action/app context/file)
pubnub_add_listener(ctx, ...)Context-global. Every event from all active subscriptions, plus status events.
pubnub_subscription_add_listener(sub, ...)Only the entity that sub was created from, and only while sub is subscribed or a subscribed set contains it. Adding sub to an already subscribed set starts delivery immediately. The SDK detaches the listener when you destroy sub.
pubnub_subscription_set_add_listener(set, ...)Only entities that are members of set, and only while set is subscribed. The listener stays silent while the set is unsubscribed. A member is delivered even if its own subscription handle is not subscribed. The SDK detaches the listener when you destroy set.

Each pubnub_add_listener()/pubnub_subscription_add_listener()/pubnub_subscription_set_add_listener() call returns PUBNUB_LISTENER_HANDLE_INVALID when:

  • ctx, sub, set, or listener is NULL.
  • The subscription or set no longer refers to a live subscribe registry entry, for example after the context's subscribe state was torn down.
  • The combined listener pool is full, meaning PUBNUB_CFG_MAX_SUBSCRIBE_LISTENERS live listeners already exist (the limit is shared across all three registration levels). Retrying cannot succeed until you remove a listener. Rebuild the SDK with a larger value if you need more.
  • A memory allocation for the subscribe state fails.

A full listener pool is also reported through the logger when logging is enabled. Passing a destroyed subscription or set handle is undefined behavior. Register listeners before or after calling subscribe(). No ordering constraint applies.

on_status delivery

Register on_status through pubnub_add_listener(). Only context-global listeners receive status events. A per-subscription or per-set listener's on_status field is ignored. See Status Events for the full status/connection-state model.

C-family contract:

  • Header — #include <pubnub/features/subscribe.h>
  • Types — pubnub_subscribe_listener_t, pubnub_listener_handle_t
  • Feature flag — PUBNUB_ENABLE_SUBSCRIBE
  • Ownership / lifetime — the listener struct itself is read at registration time; the SDK does not require it to remain live afterward, but any user_data it points to must stay valid for as long as the listener is registered
  • Thread / callback context — callbacks run while the event loop is being driven (Environment Setup covers cooperative, blocking, and callback consumption styles); keep callbacks fast and non-reentrant
  • Blocking — registration/removal never block

Sample code​

#include <pubnub/client.h>
#include <pubnub/features/subscribe.h>
#include <pubnub/features/subscribe_types.h>

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

/* on_status receives no context parameter, so carry it through user_data. */
typedef struct {
pubnub_context_t* ctx;
} app_state_t;

static void on_status(const pubnub_subscribe_status_event_t* event, void* user_data)
{
app_state_t* state = (app_state_t*)user_data;
show all 48 lines

Adapted from examples/subscribe/callback.c, with the user_data-carries-ctx idiom made explicit since on_status itself receives no context parameter.

Returns​

pubnub_add_listener()/pubnub_subscription_add_listener()/pubnub_subscription_set_add_listener() return a pubnub_listener_handle_t, or PUBNUB_LISTENER_HANDLE_INVALID on failure (see the conditions above). The corresponding _remove_listener() functions have no return value.

The subscribe event struct​

Six of the seven listener callbacks, every one except on_status, receive a const pubnub_subscribe_event_t*. This is the raw dispatched event. Pass it to the matching typed event extractor to get a struct with fields specific to that event's type.

typedef struct pubnub_subscribe_event {
pubnub_subscribe_message_type_t type;
pubnub_string_view_t channel;
pubnub_string_view_t subscription;
pubnub_string_view_t publisher;
const pubnub_json_value_t* payload;
pubnub_string_view_t custom_message_type;
const pubnub_json_value_t* user_metadata;
pubnub_timetoken_t timetoken;
uint32_t flags;
} pubnub_subscribe_event_t;
FieldTypeDescription
typepubnub_subscribe_message_type_tDiscriminates the event. Pass the event to the extractor listed for this value in the table below.
channelpubnub_string_view_tChannel the event arrived on, with any -pnpres presence suffix removed.
subscriptionpubnub_string_view_tThe subscription match as received from the server, for example a wildcard or a channel-group name.
publisherpubnub_string_view_tPublisher user ID. May be empty for system-generated events.
payloadconst pubnub_json_value_t*Parsed message body (wire field "d"). For messages and signals, this is the published content. For presence, app context, and file events, it is the server-structured event body. NULL when absent.
custom_message_typepubnub_string_view_tCaller-supplied type tag (wire field "cmt"). May be empty.
user_metadataconst pubnub_json_value_t*Parsed meta (wire field "u"). NULL when absent.
timetokenpubnub_timetoken_tPublish timetoken for this event, as a 17-digit decimal string view.
flagsuint32_tWire flags (wire field "f").

Every string-view field and both JSON-node fields (payload, user_metadata) are valid only for the duration of the listener callback that received this event. Copy out any bytes you need before returning.

Discriminant values​

type is a pubnub_subscribe_message_type_t:

ValueMeaningMatching extractor
PUBNUB_SUBSCRIBE_PRESENCE (-1)Presence event (join, leave, timeout, state change, interval). SDK-assigned from the -pnpres channel suffix, not a wire value.pubnub_subscribe_event_presence
PUBNUB_SUBSCRIBE_MESSAGE (0)Regular published message. Default when the wire "e" field is absent.pubnub_subscribe_event_message
PUBNUB_SUBSCRIBE_SIGNAL (1)Signal.pubnub_subscribe_event_signal
PUBNUB_SUBSCRIBE_APP_CONTEXT (2)App Context event (UUID, channel, or membership metadata change).pubnub_subscribe_event_app_context
PUBNUB_SUBSCRIBE_MESSAGE_ACTION (3)Message action added or removed.pubnub_subscribe_event_message_action
PUBNUB_SUBSCRIBE_FILE (4)File-sharing event.pubnub_subscribe_event_file

Receiving messages — reading a JSON payload​

Every message-like field in every subscribe event struct (payload, message, state, joined/left/timed_out, data) is const struct pubnub_json_value*. This is an opaque JSON-tree node, not a string. Printing it directly as a string will not compile. Use this pattern to read one:

  1. Get the typed event struct from one of the typed event extractors.
  2. Check the JSON-node field for NULL before touching it.
  3. Get the context's serialization provider with pubnub_serialization(ctx).
  4. Check the specific vtable accessor for NULL. Every accessor (value_as_string, object_get, array_get, array_size, array_iter_init, array_iter_next, and so on) is individually optional.
  5. Read the value: value_as_string() for a string leaf, object_get(node, key, keylen) for an object field, array_iter_init()/array_iter_next() to walk an array. array_size()/array_get() also work, but use the iterator when you visit every element. See Serialization.
  6. Copy out any bytes you need. Every pointer this pattern returns is valid only inside the current listener callback.
#include <pubnub/client.h>
#include <pubnub/features/subscribe.h>
#include <pubnub/features/subscribe_types.h>
#include <pubnub/providers/serialization.h>

#include <stdio.h>

static void print_message_text(pubnub_context_t* ctx, const pubnub_subscribe_message_event_t* msg)
{
if (NULL == msg->message) {
return;
}

pubnub_serialization_provider_t* serial = pubnub_serialization(ctx);
if (NULL == serial || NULL == serial->value_as_string) {
show all 25 lines

Two extended cases, both from the same pattern:

/* Object field: read a named key out of a JSON object node. */
static void print_mood(pubnub_serialization_provider_t* serial, const pubnub_json_value_t* state)
{
if (NULL == serial || NULL == serial->object_get || NULL == serial->value_as_string) {
return;
}
const pubnub_json_value_t* mood = serial->object_get(state, "mood", 4);
if (NULL == mood) {
return;
}
size_t len = 0;
const char* val = serial->value_as_string(mood, &len);
if (NULL != val) {
printf("mood: %.*s\n", (int)len, val);
}
show all 34 lines
No shortcut to a raw string

There is no accessor that returns a pubnub_string_view_t over an arbitrary node's raw bytes. value_as_string() only works on nodes that are already string leaves. To re-serialize an arbitrary node back to wire bytes, use serial->serialize(serial, node, buffer, buffer_capacity, &out_len) with a caller-provided output buffer, and check both the returned pubnub_res_t and out_len.

Typed event extractors​

Each extractor validates the event's type and, on success, fills a typed output struct whose fields you read with the pattern above. All six share the same contract:

  • PUBNUB_OK on success
  • PUBNUB_ERR_INVALID_ARGUMENT for a NULL argument or a type mismatch
  • PUBNUB_ERR_SERIALIZATION on parse failure
  • the output struct is zero-initialized on any failure
ExtractorOutput struct
pubnub_subscribe_event_message(ctx, event, out)pubnub_subscribe_message_event_t
pubnub_subscribe_event_signal(ctx, event, out)pubnub_subscribe_signal_event_t
pubnub_subscribe_event_presence(ctx, event, out)pubnub_subscribe_presence_event_t
pubnub_subscribe_event_message_action(ctx, event, out)pubnub_subscribe_message_action_event_t
pubnub_subscribe_event_app_context(ctx, event, out)pubnub_subscribe_app_context_event_t
pubnub_subscribe_event_file(ctx, event, out)pubnub_subscribe_file_event_t

Every pointer/view field in every output struct below is valid only for the duration of the listener callback that produced it. Copy out any bytes you need to keep.

Message​

static void on_message(const pubnub_subscribe_event_t* event, void* user_data)
{
pubnub_context_t* ctx = (pubnub_context_t*)user_data;

pubnub_subscribe_message_event_t msg;
if (PUBNUB_OK != pubnub_subscribe_event_message(ctx, event, &msg)) {
return;
}
if (NULL == msg.message) {
return;
}

pubnub_serialization_provider_t* serial = pubnub_serialization(ctx);
if (NULL == serial || NULL == serial->value_as_string) {
return;
show all 22 lines

pubnub_subscribe_message_event_t has channel, subscription, publisher, timetoken, custom_message_type (string views), plus message and user_metadata (JSON-node pointers).

Signal​

static void on_signal(const pubnub_subscribe_event_t* event, void* user_data)
{
pubnub_context_t* ctx = (pubnub_context_t*)user_data;

pubnub_subscribe_signal_event_t sig;
if (PUBNUB_OK != pubnub_subscribe_event_signal(ctx, event, &sig)) {
return;
}
if (NULL == sig.message) {
return;
}

pubnub_serialization_provider_t* serial = pubnub_serialization(ctx);
if (NULL == serial || NULL == serial->value_as_string) {
return;
show all 22 lines

pubnub_subscribe_signal_event_t has the same shape as the message event, with its JSON-node field also named message.

Presence​

For the full set of Presence operations (here_now, where_now, user state), see Presence. This extractor is documented here only because it is one of the six typed subscribe events.

static void on_presence(const pubnub_subscribe_event_t* event, void* user_data)
{
pubnub_context_t* ctx = (pubnub_context_t*)user_data;

pubnub_subscribe_presence_event_t pres;
if (PUBNUB_OK != pubnub_subscribe_event_presence(ctx, event, &pres)) {
return;
}

pubnub_serialization_provider_t* serial = pubnub_serialization(ctx);
if (NULL == pres.joined || NULL == serial
|| NULL == serial->array_size || NULL == serial->array_get || NULL == serial->value_as_string) {
return;
}
size_t count = serial->array_size(pres.joined);
show all 24 lines

pubnub_subscribe_presence_event_t has action, uuid, channel, subscription, occupancy, timetoken, here_now_refresh, plus state, joined, left, and timed_out as JSON-node pointers. Apply the same pattern to left/timed_out/state as shown above for joined.

Message action​

pubnub_subscribe_message_action_event_t is the one extractor whose fields are all pubnub_string_view_t. There is no JSON node to read here.

static void on_message_action(const pubnub_subscribe_event_t* event, void* user_data)
{
pubnub_context_t* ctx = (pubnub_context_t*)user_data;

pubnub_subscribe_message_action_event_t ma;
if (PUBNUB_OK == pubnub_subscribe_event_message_action(ctx, event, &ma)) {
printf("action type: %.*s, value: %.*s\n",
(int)ma.type.len, ma.type.ptr,
(int)ma.value.len, ma.value.ptr);
}
}

App context​

object_type is an enum, not a string view. See Presence and Configuration for related context on entities. The full set of App Context operations is out of scope for this page.

static void on_app_context(const pubnub_subscribe_event_t* event, void* user_data)
{
pubnub_context_t* ctx = (pubnub_context_t*)user_data;

pubnub_subscribe_app_context_event_t obj;
if (PUBNUB_OK != pubnub_subscribe_event_app_context(ctx, event, &obj)) {
return;
}

const char* type_str;
switch (obj.object_type) {
case PUBNUB_APP_CONTEXT_OBJECT_UUID: type_str = "uuid"; break;
case PUBNUB_APP_CONTEXT_OBJECT_CHANNEL: type_str = "channel"; break;
case PUBNUB_APP_CONTEXT_OBJECT_MEMBERSHIP: type_str = "membership"; break;
default: type_str = "unknown"; break;
show all 18 lines

File​

pubnub_subscribe_file_event_t has channel, subscription, publisher, file_id, file_name (string views), message (a JSON-node pointer), and timetoken.

static void on_file(const pubnub_subscribe_event_t* event, void* user_data)
{
pubnub_context_t* ctx = (pubnub_context_t*)user_data;

pubnub_subscribe_file_event_t file;
if (PUBNUB_OK == pubnub_subscribe_event_file(ctx, event, &file)) {
printf("file_id: %.*s, file_name: %.*s\n",
(int)file.file_id.len, file.file_id.ptr,
(int)file.file_name.len, file.file_name.ptr);
}
}

Subscribe controls​

These functions act on the whole context rather than on an individual subscription or set.

pubnub_res_t                          pubnub_subscribe_disconnect(pubnub_context_t* ctx);
pubnub_res_t pubnub_subscribe_reconnect(pubnub_context_t* ctx);
pubnub_res_t pubnub_subscribe_restore(pubnub_context_t* ctx, pubnub_timetoken_t timetoken);
pubnub_res_t pubnub_subscribe_unsubscribe_all(pubnub_context_t* ctx);
pubnub_subscribe_connection_state_t pubnub_subscribe_state(const pubnub_context_t* ctx);

There is no pubnub_subscribe_async() function. On threaded builds (PUBNUB_CFG_THREAD_SAFETY=1), a background thread starts automatically to drive I/O and deliver listener callbacks once the first subscription is activated, no separate call is needed. On cooperative builds, call pubnub_process(ctx) in your event loop instead.

No file under examples/ calls any of these functions. The signatures and behavior come directly from features/subscribe.h.

pubnub_subscribe_disconnect() stops the subscribe loop for all channels and groups without unregistering them. If the client was actively receiving, listeners get a PUBNUB_SUBSCRIBE_STATUS_DISCONNECTED status event. If it was still handshaking, the handshake is cancelled and no status event fires. Subscriptions stay registered and can be restarted with pubnub_subscribe_reconnect().

pubnub_subscribe_restore(ctx, timetoken) saves timetoken as a resume cursor and emits a SUBSCRIPTION_RESTORED status event. If the context is currently connected, the next receive cycle starts from that cursor without a full re-handshake. If it is stopped or disconnected, the cursor is only saved. Call pubnub_subscribe_reconnect() afterward to actually resume from it. This is the pattern for resuming a subscription across an app restart:

pubnub_subscribe_restore(ctx, saved_timetoken);
pubnub_subscribe_reconnect(ctx);

Passing a zero-length or NULL timetoken emits SUBSCRIPTION_RESTORED using whatever cursor is already stored internally. This is equivalent to pubnub_subscribe_reconnect() for resume position. The pubnub_timetoken_t view passed in is consumed by the call and does not need to outlive it.

C-family contract:

  • Header — #include <pubnub/features/subscribe.h>
  • Feature flag — PUBNUB_ENABLE_SUBSCRIBE
  • Blocking — none of these block

Returns​

The result/status codes these functions can return, and the connection-state machine pubnub_subscribe_state() reports on, are covered in full in Status Events. This page does not re-derive that transition table.

Error handling​

pubnub_res_t is the single status type shared across publish, signal, and subscribe. There are no per-feature error accessors. For the full result-code catalog, connection states, and the pubnub_response_service_error() pattern for server-side error detail, see Status Events.

Was this page useful?

Last updated on