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);
| Parameter | Description |
|---|---|
channel *Type: const char*Default: — | Borrowed, NUL-terminated. |
messageType: 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_lenType: size_tDefault: 0 (calls strlen) | Byte count when message is not NUL-terminated. |
message_valueType: 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. |
methodType: pubnub_publish_method_tDefault: 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. |
compressType: pubnub_publish_compress_tDefault: 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. |
storeType: pubnub_publish_store_tDefault: 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. |
ttlType: unsigned intDefault: 0 (account default) | Hours to retain the message. Ignored when store == PUBNUB_PUBLISH_STORE_NO. |
metaType: const char*Default: — | Borrowed. Valid JSON string used for stream filtering. Setting both meta and meta_value is PUBNUB_ERR_INVALID_ARGUMENT. |
meta_lenType: size_tDefault: 0 (calls strlen) | Byte count when meta is not NUL-terminated. |
meta_valueType: pubnub_json_value_t*Default: — | Borrowed, same destroy-after-call contract as message_value. |
custom_message_typeType: const char*Default: — | Borrowed, NUL-terminated. Must be 3-50 characters. |
timeout_msType: uint32_tDefault: 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, anduser_idset - Feature flag —
PUBNUB_ENABLE_PUBLISH - Ownership / lifetime —
message/metaare borrowed, NUL-terminated or length-counted strings;message_value/meta_valueare borrowed JSON trees the caller must destroy after the call returns - Blocking — never blocks; returns a
pubnub_future_timmediately
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 linesThis 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 linesMacro 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 linesError 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
messageandmessage_value(or bothmetaandmeta_value) fails immediately withPUBNUB_ERR_INVALID_ARGUMENT, readable viapubnub_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 reportsPUBNUB_IN_PROGRESS, not an error, until a slot frees up.PUBNUB_ERR_QUEUE_FULLis 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.
| Feature | Signal | Message |
|---|---|---|
| Payload size | 64 bytes (server-enforced) | Up to 32 KiB |
| Persistence | Never stored | Can be stored |
Metadata (meta) | Not supported | Supported |
| HTTP method | Always GET | GET or POST |
Method(s)
pubnub_future_t pubnub_signal(pubnub_context_t* ctx, const pubnub_signal_opts_t* opts);
| Parameter | Description |
|---|---|
channel *Type: const char*Default: — | Borrowed, NUL-terminated. |
messageType: const char*Default: — | Borrowed. Valid JSON string. Setting both message and message_value is PUBNUB_ERR_INVALID_ARGUMENT. |
message_lenType: size_tDefault: 0 (calls strlen) | Byte count when message is not NUL-terminated. |
message_valueType: pubnub_json_value_t*Default: — | Borrowed; destroy with pubnub_json_destroy() after the call returns. |
custom_message_typeType: const char*Default: — | Borrowed, NUL-terminated, 3-50 characters. |
timeout_msType: uint32_tDefault: 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, anduser_idset - Feature flag —
PUBNUB_ENABLE_SIGNAL - Ownership / lifetime — same borrow rules as publish's
message/message_value - Blocking — never blocks; returns a
pubnub_future_timmediately
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 linesAdapted 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:
| Value | Meaning | Produced 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:
ctxisNULL, or has nosubscribe_keyoruser_id.nameoridisNULL, empty, or longer than 65535 bytes.- The entity table is full, meaning
PUBNUB_CFG_MAX_SUBSCRIBE_CHANNELSdistinct 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);
| Parameter | Description |
|---|---|
with_presenceType: uint8_tDefault: 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 withpubnub_subscription_destroy() - Blocking —
pubnub_subscription_subscribe()/_unsubscribe()returnpubnub_res_tdirectly (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 linesAdapted 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:
entityisNULL.- 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_SUBSCRIPTIONSlive 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_thandles 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_tdirectly
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 linesThe 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_OKon success (including "already a member" for adds, which is de-duplicated rather than rejected)PUBNUB_ERR_INVALID_ARGUMENTfor aNULL/invalid handle, a cross-context handle, or (for single-subscription removal only) a not-found memberPUBNUB_ERR_LIMIT_REACHEDfrompubnub_subscription_set_add_subscription()if the set already holdsPUBNUB_CFG_MAX_SUBSCRIPTIONS_PER_SETmembers, and frompubnub_subscription_set_add_subscription_set()if the merge would pushtargetpast that limit. Retrying cannot succeed. Rebuild the SDK with a larger value.PUBNUB_ERR_QUEUE_FULLfor any other add or merge failure, for example when a handle reached its maximum reference countPUBNUB_ERR_OUT_OF_MEMORYfrompubnub_subscription_set_add_subscription()if the subscribe state cannot be allocatedPUBNUB_ERR_NOT_INITIALIZEDfrom_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| Parameter | Description |
|---|---|
ctxType: pubnub_context_t*Default: n/a | Initialized context. Borrowed. |
setType: pubnub_subscription_set_tDefault: 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_tDefault: n/a | Capacity of out, in elements. |
out_countType: 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
outare borrowed. They stay valid until the subscription or set is destroyed. Do not pass a listed handle topubnub_subscription_destroy()orpubnub_subscription_set_destroy()unless you own it, because the call can free a handle that your own code still uses. The handles frompubnub_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
outwithPUBNUB_CFG_MAX_SUBSCRIPTIONSforpubnub_subscriptions(),PUBNUB_CFG_MAX_SUBSCRIPTION_SETSforpubnub_subscription_sets(), andPUBNUB_CFG_MAX_SUBSCRIPTIONS_PER_SETforpubnub_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_OKon success. This includes the case where the context has no subscribe state, in which*out_countis0.PUBNUB_ERR_BUFFER_TOO_SMALLwhenmax_countis smaller than the number of matching items. The function fillsoutwith the firstmax_counthandles and still sets*out_countto the full total. Allocate a buffer of that size and call again.PUBNUB_ERR_INVALID_ARGUMENTwhenctx(orset) oroutisNULL. Forpubnub_subscription_set_subscriptions(), also when the set was already destroyed. In these cases*out_countis set to0when 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 level | Scope 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, orlistenerisNULL.- 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_LISTENERSlive 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_datait 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 linesAdapted 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;
| Field | Type | Description |
|---|---|---|
type | pubnub_subscribe_message_type_t | Discriminates the event. Pass the event to the extractor listed for this value in the table below. |
channel | pubnub_string_view_t | Channel the event arrived on, with any -pnpres presence suffix removed. |
subscription | pubnub_string_view_t | The subscription match as received from the server, for example a wildcard or a channel-group name. |
publisher | pubnub_string_view_t | Publisher user ID. May be empty for system-generated events. |
payload | const 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_type | pubnub_string_view_t | Caller-supplied type tag (wire field "cmt"). May be empty. |
user_metadata | const pubnub_json_value_t* | Parsed meta (wire field "u"). NULL when absent. |
timetoken | pubnub_timetoken_t | Publish timetoken for this event, as a 17-digit decimal string view. |
flags | uint32_t | Wire 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:
| Value | Meaning | Matching 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:
- Get the typed event struct from one of the typed event extractors.
- Check the JSON-node field for
NULLbefore touching it. - Get the context's serialization provider with
pubnub_serialization(ctx). - 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. - 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. - 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 linesTwo 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 linesNo 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_OKon successPUBNUB_ERR_INVALID_ARGUMENTfor aNULLargument or a type mismatchPUBNUB_ERR_SERIALIZATIONon parse failure- the output struct is zero-initialized on any failure
| Extractor | Output 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 linespubnub_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 linespubnub_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 linespubnub_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 linesFile
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.