On this page

Message Persistence API for C SDK

Message Persistence gives you real-time access to the history of messages published to PubNub. Each message is timestamped to the nearest 10 nanoseconds and stored across multiple availability zones in several geographic locations. You can encrypt stored messages with AES-256 so they are not readable on PubNub’s network. For details, see Message Persistence.

You control how long messages are stored through your account’s retention policy. Options include: 1 day, 7 days, 30 days, 3 months, 6 months, 1 year, or Unlimited.

You can retrieve the following:

  • Messages
  • Message reactions
  • Files (using the File Sharing API)

This SDK has exactly one history-retrieval entry point, pubnub_fetch_messages(). There is no separate deprecated function alongside it, so this page has no "(old)" section to contrast against.

Fetch messages

pubnub_fetch_messages() retrieves stored messages from one or more channels, optionally with metadata, message actions, and file-message details.

Method(s)

pubnub_future_t pubnub_fetch_messages(pubnub_context_t* ctx, const pubnub_fetch_messages_opts_t* opts);
* required
ParameterDescription
channels *
Type: const char*
Default:
Borrowed, NUL-terminated. Comma-separated (e.g. "ch1,ch2,ch3"). When include_message_actions is set, only a single channel is allowed.
start
Type: const char*
Default:
NULL (fetch from the newest available message)
Borrowed, NUL-terminated 17-digit decimal timetoken string. Exclusive. Only messages older than start are returned.
end
Type: const char*
Default:
NULL
Borrowed, NUL-terminated 17-digit decimal timetoken string. Inclusive. Messages newer than or equal to end are returned.
count
Type: uint16_t
Default:
0 (server default: 25 for multi-channel or with-actions requests, 100 for a single channel)
Clamped server-side to those same maximums regardless of what you request.
reverse
Type: uint8_t
Default:
0 (newest first)
Non-zero returns the array oldest first. This controls the actual sort order of the returned messages, not only which end of the range paging starts from.
include_meta
Type: uint8_t
Default:
0 (omit)
Non-zero includes the meta field in each result.
include_uuid
Type: uint8_t
Default:
1 via PUBNUB_FETCH_MESSAGES_OPTS_INIT
Includes the publisher's user_id (reported as uuid on the result).
include_message_type
Type: uint8_t
Default:
1 via PUBNUB_FETCH_MESSAGES_OPTS_INIT
Includes the event-type discriminator on each result.
include_custom_message_type
Type: uint8_t
Default:
0 (omit)
Includes the publisher-supplied custom message type label.
include_message_actions
Type: uint8_t
Default:
0 (omit)
Routes the request to the history-with-actions endpoint. Only a single channel is allowed, and count clamps to 25.
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-initializing this struct by hand (= {0}) silently turns include_uuid and include_message_type off. Always initialize with PUBNUB_FETCH_MESSAGES_OPTS_INIT, which sets those two fields to 1 and zeroes the rest. It is the only history options macro that is not a flat {0}.

C-family contract

  • Header#include <pubnub/features/history.h>
  • Typespubnub_fetch_messages_opts_t, pubnub_fetch_messages_result_t, pubnub_fetch_messages_channel_result_t, pubnub_history_message_result_t
  • Prerequisite — an initialized context with subscribe_key set
  • Feature flagPUBNUB_ENABLE_HISTORY
  • Ownership / lifetimechannels/start/end are borrowed, NUL-terminated strings; every pointer/view field on the result aliases memory owned by the future and is valid only until pubnub_future_release, with a narrower rule for decrypted content (see Returns below)
  • Blocking — never blocks; returns a pubnub_future_t immediately

Sample code

#include <pubnub/client.h>
#include <pubnub/features/history.h>
#include <pubnub/future.h>
#include <pubnub/providers/serialization.h>
#include <pubnub/response.h>

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

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

show all 73 lines

Adapted from examples/history/fetch_messages.c (cooperative polling), with the NULL-checks on serial and msg.message added. The header's own @code example for this function reads msg.message through serial->value_as_string() without either check. The pattern of getting the serialization provider, checking each vtable accessor, and reading through value_as_string() is the same one used for subscribe events. See Receiving messages — reading a JSON payload for the full explanation, including how to read object and array fields.

Returns

pubnub_fetch_messages_result_t          pubnub_fetch_messages_result(pubnub_future_t future);
pubnub_fetch_messages_channel_result_t pubnub_fetch_messages_result_channel_at(pubnub_future_t future, size_t index);
pubnub_history_message_result_t pubnub_fetch_messages_result_message_at(pubnub_future_t future, size_t channel_index, size_t message_index);
pubnub_history_file_result_t pubnub_fetch_messages_result_file_at(pubnub_future_t future, size_t channel_index, size_t message_index);
const pubnub_json_value_t* pubnub_fetch_messages_result_actions_at(pubnub_future_t future, size_t channel_index, size_t message_index);

Reading a result is two-level indexed access, not a single flat list. Call pubnub_fetch_messages_result() first. This triggers lazy parsing of the response. Then loop channels with pubnub_fetch_messages_result_channel_at() up to channel_count, and loop messages within each channel with pubnub_fetch_messages_result_message_at() up to that channel's message_count. Every accessor returns a zero-initialized struct for an out-of-range index or a future that is not ready.

pubnub_fetch_messages_channel_result_t has name (pubnub_string_view_t) and message_count (uint32_t). Channel iteration order does not necessarily match the order channels were requested in. It depends on the server response's own JSON object order. If you need a specific channel's results, search for it by name rather than assuming an index.

pubnub_history_message_result_t has 7 fields:

FieldTypeDescription
message
const pubnub_json_value_t*
JSON-node pointer, not a string. NULL when absent.
timetoken
pubnub_timetoken_t
The server-assigned publish timetoken, as a non-NUL-terminated view over 17-digit decimal text. Print with %.*s.
uuid
pubnub_string_view_t
Publisher's user_id. Empty (.len == 0) when include_uuid was not set.
meta
const pubnub_json_value_t*
JSON-node pointer. NULL when include_meta was not set or the message carried no metadata.
custom_message_type
pubnub_string_view_t
Empty when include_custom_message_type was not set or the publisher did not supply one.
event_type
pubnub_event_type_t
See The pubnub_event_type_t enum below. pubnub_fetch_messages() only ever produces PUBNUB_EVENT_TYPE_MESSAGE, PUBNUB_EVENT_TYPE_FILE, or PUBNUB_EVENT_TYPE_UNKNOWN (the last when include_message_type was not set in the request).
crypto_result
pubnub_res_t
PUBNUB_OK when the message needed no decryption or decrypted successfully. PUBNUB_ERR_CRYPTO when a crypto module is configured but this specific message failed to decrypt.

Every pointer/view field above aliases memory owned by the future and is valid only until pubnub_future_release.

The pubnub_event_type_t enum

The header documents pubnub_event_type_t as a discriminator shared conceptually between history and subscribe, mirroring the server's wire-level event type field. Its full set of values, including the two that never appear on the wire:

ValueMeaningProduced by
PUBNUB_EVENT_TYPE_UNKNOWN (-2)
The field was not populated.
History only, when include_message_type is not set on the request.
PUBNUB_EVENT_TYPE_PRESENCE (-1)
Presence event. SDK-internal, never a wire value.
Not produced by pubnub_fetch_messages().
PUBNUB_EVENT_TYPE_MESSAGE (0)
Regular published message.
pubnub_fetch_messages().
PUBNUB_EVENT_TYPE_SIGNAL (1)
Signal.
Not produced by pubnub_fetch_messages().
PUBNUB_EVENT_TYPE_OBJECTS (2)
App Context (Objects) event.
Not produced by pubnub_fetch_messages().
PUBNUB_EVENT_TYPE_MESSAGE_ACTION (3)
Message action added or removed.
Not produced by pubnub_fetch_messages().
PUBNUB_EVENT_TYPE_FILE (4)
File-sharing event.
pubnub_fetch_messages().

In practice, pubnub_fetch_messages() only ever sets event_type to PUBNUB_EVENT_TYPE_MESSAGE, PUBNUB_EVENT_TYPE_FILE, or PUBNUB_EVENT_TYPE_UNKNOWN. The other values exist because the enum also describes categories the subscribe stream can carry. Subscribe reports its own event type through a different, similarly-named type (see below). Call pubnub_fetch_messages_result_file_at() when event_type == PUBNUB_EVENT_TYPE_FILE to read the file's metadata instead of a generic message body.

Do not confuse pubnub_event_type_t with pubnub_subscribe_message_type_t, the discriminant on the raw subscribe event struct (see The subscribe event struct). pubnub_event_type_t is the field you read on a history result (pubnub_history_message_result_t::event_type, documented above). pubnub_subscribe_message_type_t is the field you read on a subscribe event (pubnub_subscribe_event_t::type). The two enums share similar-looking value names (MESSAGE, SIGNAL, FILE) and mostly the same numbers, but they are distinct types on distinct structs. Never pass one where the other is expected.

Transparent decryption, with a narrower cache rule

When pubnub_config_t::crypto_module is configured (see Provider pointers), message and file-message payloads decrypt automatically on access, so you never call a decrypt function yourself. Decryption failure is per-message, not per-call. If one message in a batch fails to decrypt, the fetch as a whole still succeeds: message keeps the original ciphertext instead of a parsed tree, and crypto_result is set to PUBNUB_ERR_CRYPTO so you can tell the two cases apart.

Check crypto_result rather than assuming a node in message is already plaintext. The decrypted payload is cached only until you access a different message or channel index, not only until pubnub_future_release, which is stricter than the usual "valid until release" rule. Copy out any bytes you need before advancing to the next index.

Other examples

Fetch messages with message actions

Read the actions tree with the same JSON-node pattern as message. The SDK does not provide typed accessors for individual action entries.

#include <pubnub/client.h>
#include <pubnub/features/history.h>
#include <pubnub/future.h>
#include <pubnub/providers/serialization.h>

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

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

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

Fetch messages, async callback

#include <pubnub/client.h>
#include <pubnub/features/history.h>
#include <pubnub/future.h>
#include <pubnub/providers/serialization.h>
#include <pubnub/response.h>

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

static volatile int s_done = 0;

static void on_complete(pubnub_future_t future, pubnub_res_t status, void* user_data)
{
pubnub_context_t* ctx = (pubnub_context_t*)user_data;

show all 80 lines

Adapted from examples/history/fetch_messages_async.c. on_complete releases the future and reads its results itself, so main does not release it a second time.

Fetching further back (paging)

pubnub_fetch_messages_result_t carries a next field (pubnub_timetoken_t) alongside channel_count. When the server has more messages than it returned in one response, next is a non-empty cursor. Pass it as start on the next pubnub_fetch_messages() call to page further back. A zero-length next (.len == 0) means the current response is the last page.

pubnub_fetch_messages_result_t r = pubnub_fetch_messages_result(future);
if (r.next.len > 0) {
/* start requires a NUL-terminated string; next is a view and is not
guaranteed to be one, so copy it into a buffer first. */
char cursor[32];
size_t len = (r.next.len < sizeof(cursor) - 1) ? r.next.len : sizeof(cursor) - 1;
memcpy(cursor, r.next.ptr, len);
cursor[len] = '\0';

pubnub_fetch_messages_opts_t next_opts = PUBNUB_FETCH_MESSAGES_OPTS_INIT;
next_opts.channels = "ch1,ch2";
next_opts.start = cursor;
}

You can also page manually by re-issuing the call with start set to the oldest timetoken you saw in the previous response. Because start is exclusive, this does not re-fetch the message at that boundary.

Error responses

See Status Events for the full pubnub_res_t catalog and the pubnub_response_service_error() pattern for server-side error detail. Specific to fetch: validation failures include a NULL channels, more than one channel combined with include_message_actions, a missing subscribe_key, or a full request queue. On any of these, the returned future carries an immediate error code, readable via pubnub_future_status() without polling or awaiting.

Delete messages

pubnub_delete_messages() removes messages from a single channel's stored history.

Method(s)

pubnub_future_t pubnub_delete_messages(pubnub_context_t* ctx, const pubnub_delete_messages_opts_t* opts);
* required
ParameterDescription
channel *
Type: const char*
Default:
Borrowed, NUL-terminated. Single channel only — commas are not allowed.
start
Type: const char*
Default:
NULL (delete from the beginning of stored history)
Borrowed, NUL-terminated 17-digit decimal timetoken string. Exclusive. Only messages published after start are deleted. The message at start itself is kept.
end
Type: const char*
Default:
NULL (delete up to the most recent message)
Borrowed, NUL-terminated 17-digit decimal timetoken string. Exclusive. Only messages published before end are deleted. The message at end itself is kept.
timeout_ms
Type: uint32_t
Default:
0 (inherits context default)
A non-zero value overrides the context-level timeout for this call only.

Zero-initialize with PUBNUB_DELETE_MESSAGES_OPTS_INIT, which expands to a plain {0}. Unlike fetch's options struct, there is no non-zero field to preserve here.

danger
Delete's end is exclusive — fetch's end is inclusive, and the old SDK's delete end was inclusive too

pubnub_fetch_messages()'s end is inclusive: the message at that timetoken is returned. pubnub_delete_messages()'s end is exclusive: the message at that timetoken is kept, not deleted. Code that assumes the two functions share one rule leaves exactly one message undeleted at the end boundary. The same happens if you port a delete call from the earlier C-Core SDK, where delete's end was inclusive. To delete a specific message, set end to a timetoken one greater than that message's own timetoken, or set end to the following message's timetoken.

C-family contract

  • Header#include <pubnub/features/history.h>
  • Typespubnub_delete_messages_opts_t
  • Prerequisite — an initialized context with subscribe_key set
  • Feature flagPUBNUB_ENABLE_HISTORY
  • Ownership / lifetimechannel/start/end are borrowed, NUL-terminated strings
  • Blocking — never blocks; returns a pubnub_future_t immediately

Sample code

#include <pubnub/client.h>
#include <pubnub/features/history.h>
#include <pubnub/future.h>
#include <pubnub/response.h>

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

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

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

Adapted from examples/history/delete_messages.c (blocking pubnub_await).

Returns

pubnub_delete_messages() has no result accessor of its own. Success is signaled by the future's status alone: check pubnub_future_status(future) == PUBNUB_OK (or the return value of pubnub_await(), as shown above). There is no data to extract on success or failure beyond the error detail described below.

Error responses

See Status Events for the full pubnub_res_t catalog. The header requires only that subscribe_key be set on the context. It states no other immediate-validation-error contract for this function, unlike fetch and message counts.

Delete-From-History add-on

Deleting stored messages may also require the Delete-From-History setting enabled for your key in the Admin Portal. See Message Persistence. This SDK's history.h does not mention any additional requirement, such as a secret_key, for pubnub_delete_messages() to succeed. If your account still enforces one, it is a server-side/product requirement, not something the client header documents or enforces.

Message counts

pubnub_message_counts() counts messages published to one or more channels since a given timetoken, useful for showing an "unread" badge.

Method(s)

pubnub_future_t pubnub_message_counts(pubnub_context_t* ctx, const pubnub_message_counts_opts_t* opts);
* required
ParameterDescription
channels *
Type: const char*
Default:
Borrowed, NUL-terminated. Comma-separated.
timetoken
Type: const char*
Default:
Borrowed, NUL-terminated. A single timetoken applied to every channel in channels. Counts messages published after this timetoken. Mutually exclusive with channels_timetokens.
channels_timetokens
Type: const char*
Default:
Borrowed, NUL-terminated. Comma-separated, one timetoken per channel, in the same order as channels. Mutually exclusive with timetoken.
timeout_ms
Type: uint32_t
Default:
0 (inherits context default)
A non-zero value overrides the context-level timeout for this call only.

Exactly one of timetoken or channels_timetokens must be non-NULL. The header does not state whether the counted boundary is inclusive or exclusive of the supplied timetoken. Unlike fetch and delete, it uses neither word. Do not assume either behavior without confirming it against a live response.

Zero-initialize with PUBNUB_MESSAGE_COUNTS_OPTS_INIT, which expands to a plain {0}.

C-family contract

  • Header#include <pubnub/features/history.h>
  • Typespubnub_message_counts_opts_t, pubnub_message_counts_result_t, pubnub_message_counts_channel_result_t
  • Prerequisite — an initialized context with subscribe_key set
  • Feature flagPUBNUB_ENABLE_HISTORY
  • Ownership / lifetimechannels/timetoken/channels_timetokens are borrowed, NUL-terminated strings; result view fields alias memory owned by the future, valid until pubnub_future_release
  • Blocking — never blocks; returns a pubnub_future_t immediately

Sample code

#include <pubnub/client.h>
#include <pubnub/features/history.h>
#include <pubnub/future.h>
#include <pubnub/response.h>

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

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

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

Adapted from examples/history/message_counts.c (blocking pubnub_await).

Returns

pubnub_message_counts_result_t          pubnub_message_counts_result(pubnub_future_t future);
pubnub_message_counts_channel_result_t pubnub_message_counts_result_channel_at(pubnub_future_t future, size_t index);

Call pubnub_message_counts_result() first to trigger lazy parsing, then loop with pubnub_message_counts_result_channel_at() up to channel_count. pubnub_message_counts_channel_result_t has name (pubnub_string_view_t) and count (uint32_t, the number of messages published after the supplied timetoken). As with fetch's channel accessor, iteration order does not necessarily match the order channels were requested in. Search by name rather than assuming an index. Every accessor returns a zero-initialized struct for an out-of-range index or a future that is not ready.

Other examples

Per-channel timetokens

pubnub_message_counts_opts_t opts = PUBNUB_MESSAGE_COUNTS_OPTS_INIT;
opts.channels = "ch1,ch2,ch3";
opts.channels_timetokens = "17001111111111111,17001222222222222,17001333333333333";

Error responses

See Status Events for the full pubnub_res_t catalog. Setting both timetoken and channels_timetokens, or neither, fails with PUBNUB_ERR_INVALID_ARGUMENT.