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.
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 subscribes to the entity's presence 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.
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.
Returns
pubnub_subscription_create() returns a pubnub_subscription_t handle, or an invalid handle on failure. 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 new entry is auto-activated immediately, without a separate subscribe() call. The same is true when merging with pubnub_subscription_set_add_subscription_set().
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 each member's underlying entry is freed only once nothing else references it.
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.
pubnub_subscription_set_add_subscription_set() also warns that a PUBNUB_ERR_QUEUE_FULL mid-merge may leave a partial merge in place. Check the return value rather than assuming the operation is atomic.
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 linesReturns
pubnub_subscription_set_create() returns a pubnub_subscription_set_t handle, or PUBNUB_SUBSCRIPTION_SET_INVALID on failure. 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_QUEUE_FULLif the set is at capacityPUBNUB_ERR_NOT_INITIALIZEDfrom_subscribe()/_unsubscribe()if subscribe support is not compiled in
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 active subscription on the context |
pubnub_subscription_add_listener(sub, ...) | Only the entity that sub was created from |
pubnub_subscription_set_add_listener(set, ...) | Only entities that are members of set |
Each pubnub_add_listener()/pubnub_subscription_add_listener()/pubnub_subscription_set_add_listener() call returns PUBNUB_LISTENER_HANDLE_INVALID if the combined listener pool (PUBNUB_CFG_MAX_SUBSCRIBE_LISTENERS, shared across all three registration levels) is already at capacity. 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 if the listener pool is full. 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 wildcard or channel-group pattern that matched, when different from channel. |
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, 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_size()/array_get()to walk an array. - 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.