Message Actions API for C SDK
Message actions attach small pieces of metadata, such as receipts or emoji reactions, to a previously published message without republishing it. An action is not a new message: it always references an existing message by timetoken on a channel.
Message reactions
"Message reactions" is the same API used for a specific purpose: emoji or social reactions on a message. There is no separate reactions function. Everything on this page applies equally to receipts, reactions, or any other custom action type you define.
This page covers the direct message-actions API: pubnub_add_message_action(), pubnub_get_message_actions(), and pubnub_remove_message_action(), plus how the same action data arrives through the subscribe listener. All three functions live in #include <pubnub/features/message_actions.h>, gated by PUBNUB_ENABLE_MESSAGE_ACTIONS. Each returns a pubnub_future_t, consumed with any of the SDK's three async styles: cooperative polling, blocking pubnub_await(), or callback-driven pubnub_async(). See Environment Setup for details.
Every sample on this page includes the specific headers it needs. #include <pubnub/pubnub.h> also exists as a convenience umbrella that pulls in the whole compiled-in API surface at once; it's useful for quick scripts, but the explicit per-header includes shown here make each sample's actual dependencies self-evident.
Add message action
Requires Message Persistence
Enable Message Persistence for your key in the Admin Portal as described in the support article.
pubnub_add_message_action() adds an action to a specific message. The result carries the same action back with its server-assigned action_timetoken. Capture that value if you plan to remove the action later.
Method(s)
pubnub_future_t pubnub_add_message_action(pubnub_context_t* ctx,
const pubnub_add_message_action_opts_t* opts);
pubnub_add_message_action_opts_t fields:
| Parameter | Description |
|---|---|
channel *Type: const char*Default: — | Borrowed, NUL-terminated. Channel the target message was published on. |
message_timetoken *Type: const char*Default: — | Borrowed, NUL-terminated. The parent message's timetoken, as a decimal string. |
type *Type: const char*Default: — | Borrowed, NUL-terminated. Free-form action type, up to 15 characters, not a closed set of values. |
value *Type: const char*Default: — | Borrowed, NUL-terminated. Action value, passed as a plain string; no manual JSON-quote wrapping is required. |
timeout_msType: uint32_tDefault: 0 → inherits pubnub_config_t::transaction_timeout_ms | Non-zero overrides the context default for this call only. |
Initialize with PUBNUB_ADD_MESSAGE_ACTION_OPTS_INIT (equivalent to {0} for this struct).
type is a free-form string, not an enum
type accepts any string up to 15 characters — "reaction", "receipt", or any label your application defines. There is no closed set of allowed values to choose from.
C-family contract
- Header —
#include <pubnub/features/message_actions.h> - Types —
pubnub_add_message_action_opts_t,pubnub_add_message_action_result_t,pubnub_message_action_t - Prerequisite — an initialized context
- Feature flag —
PUBNUB_ENABLE_MESSAGE_ACTIONS - Ownership / lifetime — all four required string fields are borrowed, NUL-terminated; the result's
pubnub_message_action_tfields alias memory owned by the future and are valid only untilpubnub_future_release - Blocking — never blocks; returns a
pubnub_future_timmediately
Sample code
#include <pubnub/client.h>
#include <pubnub/error.h>
#include <pubnub/features/message_actions.h>
#include <pubnub/future.h>
#include <pubnub/response.h>
#include <stdio.h>
int main(void)
{
pubnub_config_t cfg = pubnub_config_defaults();
cfg.subscribe_key = "demo";
cfg.publish_key = "demo";
cfg.user_id = "example-add-action";
show all 52 linesAdapted from examples/message_actions/add_message_action.c (cooperative polling).
Returns
pubnub_add_message_action_result() returns a pubnub_add_message_action_result_t:
| Field | Type | Description |
|---|---|---|
action | pubnub_message_action_t | The added action, with server-assigned timetokens. |
pubnub_message_action_t fields (also used by Get message actions):
| Field | Type | Description |
|---|---|---|
type | pubnub_string_view_t | Action type, e.g. "reaction". |
value | pubnub_string_view_t | Action value, e.g. "thumbs_up". |
uuid | pubnub_string_view_t | User ID of the publisher who added the action. |
action_timetoken | pubnub_string_view_t | Server-assigned timetoken identifying this action. There is no separate action ID. Use this value in Remove message action. |
message_timetoken | pubnub_string_view_t | Timetoken of the parent message the action was added to. |
Every pubnub_string_view_t field aliases internal buffers and remains valid only until pubnub_future_release() is called on the owning future. Copy out any bytes you need after that point.
Other examples
Add message action, blocking
#include <pubnub/client.h>
#include <pubnub/features/message_actions.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 = "example-add-action-blocking";
pubnub_context_t* ctx = pubnub_create(&cfg);
show all 40 linesError responses
| Condition | Result |
|---|---|
opts is NULL | PUBNUB_ERR_INVALID_ARGUMENT |
channel is NULL or empty | PUBNUB_ERR_INVALID_ARGUMENT |
message_timetoken is NULL or empty | PUBNUB_ERR_INVALID_ARGUMENT |
type is NULL or empty | PUBNUB_ERR_INVALID_ARGUMENT |
type exceeds 15 characters | PUBNUB_ERR_INVALID_ARGUMENT |
value is NULL or empty | PUBNUB_ERR_INVALID_ARGUMENT |
These checks run synchronously before any request is sent. On validation failure, pubnub_add_message_action() returns PUBNUB_FUTURE_INVALID instead of a future tied to a real request. PUBNUB_FUTURE_INVALID is still a valid value to pass to pubnub_future_status(), which reports PUBNUB_ERR_INVALID_ARGUMENT for it. So the same check you use for any other failure catches this case too, and you do not need to compare the future against the sentinel directly. For the full pubnub_res_t catalog and how to read server-side error detail, see The pubnub_res_t result catalog and Retrieving server error detail.
Remove message action
Requires Message Persistence
Enable Message Persistence for your key in the Admin Portal as described in the support article.
No action ID
An action has no ID of its own. Removal is identified by the triple {channel, message_timetoken, action_timetoken}: the channel name, the parent message's timetoken, and the action's own server-assigned action_timetoken from a prior Add message action result.
pubnub_remove_message_action() removes a previously added action. On success, the response body is empty. There is no result accessor for this function.
Server-enforced removal rule
Only the user who originally added an action may remove it. The server enforces this rule. The client SDK does not check it locally before sending the request.
Method(s)
pubnub_future_t
pubnub_remove_message_action(pubnub_context_t* ctx,
const pubnub_remove_message_action_opts_t* opts);
pubnub_remove_message_action_opts_t fields:
| Parameter | Description |
|---|---|
channel *Type: const char*Default: — | Borrowed, NUL-terminated. |
message_timetoken *Type: const char*Default: — | Borrowed, NUL-terminated. The parent message's timetoken. |
action_timetoken *Type: const char*Default: — | Borrowed, NUL-terminated. The action's own timetoken to remove — typically the action_timetoken captured from a prior add result. |
timeout_msType: uint32_tDefault: 0 → inherits pubnub_config_t::transaction_timeout_ms | Non-zero overrides the context default for this call only. |
Initialize with PUBNUB_REMOVE_MESSAGE_ACTION_OPTS_INIT (equivalent to {0} for this struct).
C-family contract
- Header —
#include <pubnub/features/message_actions.h> - Types —
pubnub_remove_message_action_opts_t - Prerequisite — an initialized context
- Feature flag —
PUBNUB_ENABLE_MESSAGE_ACTIONS - Ownership / lifetime — all three required string fields are borrowed, NUL-terminated
- Blocking — never blocks; returns a
pubnub_future_timmediately
Sample code
#include <pubnub/client.h>
#include <pubnub/error.h>
#include <pubnub/features/message_actions.h>
#include <pubnub/future.h>
#include <pubnub/response.h>
#include <stdio.h>
int main(void)
{
pubnub_config_t cfg = pubnub_config_defaults();
cfg.subscribe_key = "demo";
cfg.publish_key = "demo";
cfg.user_id = "example-remove-action";
show all 45 linesAdapted from examples/message_actions/remove_message_action.c (cooperative polling). That example uses a hardcoded action_timetoken literal. The sample below chains a real one from an add call instead.
Returns
pubnub_remove_message_action() has no result accessor of its own. Success is signaled by the future's status alone: check pubnub_future_status(fut) == PUBNUB_OK. There is no data to extract on success.
Other examples
Chain an add result into a remove
remove_message_action.c uses a hardcoded action_timetoken literal. The sample below instead chains a real one captured from an add call.
#include <pubnub/client.h>
#include <pubnub/features/message_actions.h>
#include <pubnub/future.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
pubnub_config_t cfg = pubnub_config_defaults();
cfg.publish_key = "demo";
cfg.subscribe_key = "demo";
cfg.user_id = "example-chain-add-remove";
show all 68 linesError responses
| Condition | Result |
|---|---|
opts is NULL | PUBNUB_ERR_INVALID_ARGUMENT |
channel, message_timetoken, or action_timetoken is NULL or empty | PUBNUB_ERR_INVALID_ARGUMENT |
On any of these, pubnub_remove_message_action() returns PUBNUB_FUTURE_INVALID rather than a future tied to a real request. Checking pubnub_future_status() still reports PUBNUB_ERR_INVALID_ARGUMENT for it, so the usual status check catches this case without a separate comparison against the sentinel. See The pubnub_res_t result catalog for the full set of possible result values.
Get message actions
Requires Message Persistence
Enable Message Persistence for your key in the Admin Portal as described in the support article.
pubnub_get_message_actions() retrieves actions added to messages on a channel.
Method(s)
pubnub_future_t pubnub_get_message_actions(pubnub_context_t* ctx,
const pubnub_get_message_actions_opts_t* opts);
pubnub_get_message_actions_opts_t fields:
| Parameter | Description |
|---|---|
channel *Type: const char*Default: — | Borrowed, NUL-terminated. |
startType: const char*Default: NULL (omit) | Borrowed, NUL-terminated 17-digit decimal timetoken string. Exclusive upper bound: only actions with timetokens less than start are returned. Same rule as start on pubnub_fetch_messages(). |
endType: const char*Default: NULL (omit) | Borrowed, NUL-terminated 17-digit decimal timetoken string. Inclusive lower bound: actions with timetokens greater than or equal to end are returned. Same rule as end on pubnub_fetch_messages(). |
limitType: uint32_tDefault: 0 → server default | Maximum results to return. The header states no upper ceiling. |
timeout_msType: uint32_tDefault: 0 → inherits pubnub_config_t::transaction_timeout_ms | Non-zero overrides the context default for this call only. |
Initialize with PUBNUB_GET_MESSAGE_ACTIONS_OPTS_INIT (equivalent to {0} for this struct).
C-family contract
- Header —
#include <pubnub/features/message_actions.h> - Types —
pubnub_get_message_actions_opts_t,pubnub_get_message_actions_result_t,pubnub_message_action_t - Prerequisite — an initialized context
- Feature flag —
PUBNUB_ENABLE_MESSAGE_ACTIONS - Ownership / lifetime —
channel/start/endare borrowed, NUL-terminated strings; everypubnub_string_view_tfield on the result and every action returned by the indexed accessor aliases memory owned by the future and is valid only untilpubnub_future_release - Blocking — never blocks; returns a
pubnub_future_timmediately
Copy pagination cursors before releasing the future
The result's more_start and more_end fields are views into the completed future's internal storage. If you release the future before copying them out, the next call reads freed memory. Copy more_start/more_end into your own buffers first, release the future, then reassign opts.start/opts.end to point at your buffers before issuing the next call.
Sample code
#include <pubnub/client.h>
#include <pubnub/error.h>
#include <pubnub/features/message_actions.h>
#include <pubnub/future.h>
#include <pubnub/response.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
pubnub_config_t cfg = pubnub_config_defaults();
cfg.subscribe_key = "demo";
cfg.user_id = "example-get-actions";
show all 85 linesAdapted from examples/message_actions/get_message_actions.c (cooperative polling with a pagination loop). Note the order: cursor bytes are copied into start_buf/end_buf before pubnub_future_release(fut) runs.
Returns
pubnub_get_message_actions_result() returns a pubnub_get_message_actions_result_t:
| Field | Type | Description |
|---|---|---|
count | uint32_t | Number of actions in this page; the upper bound for pubnub_get_message_actions_result_action_at(). |
has_more | uint8_t | Non-zero when the server indicates more results exist beyond this page. This is the only signal that another page exists. There is no total count of remaining actions. |
more_start | pubnub_string_view_t | Server-recommended cursor. Copy into your own buffer and pass as opts.start for the next call. |
more_end | pubnub_string_view_t | Server-recommended cursor. Copy into your own buffer and pass as opts.end for the next call. |
more_limit | uint32_t | Server-recommended limit for the next call. |
Read individual actions with pubnub_get_message_actions_result_action_at(future, index), where index is in [0, count). An out-of-range or invalid index returns a zero-initialized pubnub_message_action_t. See the pubnub_message_action_t field table under Add message action for its fields.
Other examples
Get message actions, single page
A single call without a pagination loop, consumed with blocking pubnub_await():
#include <pubnub/client.h>
#include <pubnub/features/message_actions.h>
#include <pubnub/future.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
pubnub_config_t cfg = pubnub_config_defaults();
cfg.subscribe_key = "demo";
cfg.user_id = "example-get-actions-single-page";
pubnub_context_t* ctx = pubnub_create(&cfg);
show all 41 linesGet message actions, async callback
#include <pubnub/client.h>
#include <pubnub/error.h>
#include <pubnub/features/message_actions.h>
#include <pubnub/future.h>
#include <pubnub/response.h>
#include <stdint.h>
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#define PN_DOC_SLEEP_MS(ms) Sleep((DWORD)(ms))
#else
#include <unistd.h>
#define PN_DOC_SLEEP_MS(ms) usleep((unsigned)(ms) * 1000U)
#endif
show all 86 linesAdapted from examples/message_actions/get_message_actions_async.c. On platforms with PUBNUB_CFG_THREAD_SAFETY=1, pubnub_async() starts a background thread that drives I/O automatically. On embedded targets without threads, replace the sleep loop with a call to pubnub_process(ctx) in a loop.
Error responses
| Condition | Result |
|---|---|
opts is NULL | PUBNUB_ERR_INVALID_ARGUMENT |
channel is NULL or empty | PUBNUB_ERR_INVALID_ARGUMENT |
On either of these, pubnub_get_message_actions() returns PUBNUB_FUTURE_INVALID rather than a future tied to a real request. pubnub_future_status() still reports PUBNUB_ERR_INVALID_ARGUMENT for it. No maximum value is enforced or documented for limit in this SDK version. For the full pubnub_res_t catalog and server-error retrieval pattern, see The pubnub_res_t result catalog and Retrieving server error detail.
Receiving message actions in real time
Beyond calling pubnub_get_message_actions(), a subscribed context also receives message-action events live, the same way it receives messages, signals, and presence events. When another client adds or removes an action on a channel this context is subscribed to, the subscribe event engine delivers an event of type PUBNUB_EVENT_TYPE_MESSAGE_ACTION. pubnub_subscribe_event_message_action() extracts that event into a typed struct.
This is a third, independently-shaped representation of "a message action", distinct from pubnub_message_action_t (the direct API above) and from the raw JSON node returned inside fetched history (see Message actions inside fetched history). There is no conversion function between any of the three.
Method(s)
pubnub_res_t pubnub_subscribe_event_message_action(
pubnub_context_t* ctx,
const pubnub_subscribe_event_t* event,
pubnub_subscribe_message_action_event_t* out);
typedef enum pubnub_message_action_type {
PUBNUB_MESSAGE_ACTION_ADDED = 0, /* A reaction or action was added to a message. */
PUBNUB_MESSAGE_ACTION_REMOVED = 1 /* A reaction or action was removed from a message. */
} pubnub_message_action_type_t;
pubnub_subscribe_message_action_event_t fields:
| Field | Type | Description |
|---|---|---|
event | pubnub_message_action_type_t | Whether the action was added or removed. |
channel | pubnub_string_view_t | Channel on which the action occurred. |
subscription | pubnub_string_view_t | Subscription match pattern. |
publisher | pubnub_string_view_t | Publisher of the original message. |
message_timetoken | pubnub_string_view_t | Timetoken of the message being acted on. |
action_timetoken | pubnub_string_view_t | Timetoken when the action itself was created. |
type | pubnub_string_view_t | Action type, e.g. "reaction". |
value | pubnub_string_view_t | Action value, e.g. an emoji string. |
C-family contract
- Header —
#include <pubnub/features/subscribe.h>and#include <pubnub/features/subscribe_types.h> - Types —
pubnub_message_action_type_t,pubnub_subscribe_message_action_event_t - Prerequisite — a live subscription delivering events to a listener callback
- Feature flag —
PUBNUB_ENABLE_SUBSCRIBE, independent ofPUBNUB_ENABLE_MESSAGE_ACTIONS— this extractor works even in a build where the direct message-actions API is compiled out - Ownership / lifetime —
outis caller-owned stack memory the extractor copies into, but itspubnub_string_view_tfields alias the raw event's backing storage, valid only for the duration of the listener callback. This is narrower than the future-release rule that governs the direct-API structs above. Do not retain these views past the callback. - Blocking — synchronous; called directly inside the listener callback, no future involved
Returns PUBNUB_OK on success, PUBNUB_ERR_INVALID_ARGUMENT if any argument is NULL or the event is not a message-action event, and PUBNUB_ERR_SERIALIZATION on parse failure.
Sample code
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);
}
}
This extractor is one of six typed subscribe-event extractors. For the full worked example, registering a listener, creating a subscription, and wiring this callback into it, see Message action in Publish & Subscribe, which owns the general listener-registration mechanics.
Returns
There is no separate result type beyond the out struct populated above. Read its fields directly, subject to the callback-duration lifetime described in the C-family contract.
Message actions inside fetched history
pubnub_fetch_messages() can embed each message's actions in its own result when the request sets include_message_actions = 1. That path returns a raw, untyped pubnub_json_value_t* node from pubnub_fetch_messages_result_actions_at(), not a pubnub_message_action_t. The two features compile fully independently of each other (PUBNUB_ENABLE_MESSAGE_ACTIONS has no effect on PUBNUB_ENABLE_HISTORY or vice versa). See Fetch messages with message actions in Storage & Playback for that field, the accessor, and how to walk the resulting JSON tree. This page does not duplicate that content.