On this page

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>
  • Typespubnub_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 flagPUBNUB_ENABLE_PUBLISH
  • Ownership / lifetimemessage/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 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);
* 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>
  • Typespubnub_signal_opts_t
  • Prerequisite — an initialized context with publish_key, subscribe_key, and user_id set
  • Feature flagPUBNUB_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.

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 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>
  • Typespubnub_subscription_t, pubnub_subscription_opts_t, pubnub_entity_t
  • Prerequisite — an entity handle created from the same context
  • Feature flagPUBNUB_ENABLE_SUBSCRIBE
  • Ownership / lifetimepubnub_subscription_create() consumes nothing from the entity; both handles can coexist, but the subscription itself must eventually be released with pubnub_subscription_destroy()
  • Blockingpubnub_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.

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>
  • Typespubnub_subscription_set_t
  • Prerequisite — one or more pubnub_subscription_t handles created on the same context
  • Feature flagPUBNUB_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 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 lines

Returns

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_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_QUEUE_FULL if the set is at capacity
  • PUBNUB_ERR_NOT_INITIALIZED from _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 levelScope 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>
  • Typespubnub_subscribe_listener_t, pubnub_listener_handle_t
  • Feature flagPUBNUB_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 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;
FieldTypeDescription
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:

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, 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_size()/array_get() to walk an array.
  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 flagPUBNUB_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.