On this page

C Environment Setup

The PubNub C SDK is built with CMake (version 3.16 or later). Instead of hand-editing a config header, you select a compile-time build profile that fixes the feature set, provider backends, and buffer sizing for your target, then override individual settings as needed. This page covers prerequisites, build profiles and presets, provider and feature-flag selection, consuming the SDK from your own project, the three ways to consume an asynchronous call, thread safety and context lifecycle, and memory tuning for constrained targets.

Prerequisites

PathToolchainThird-party dependencies
full / minimal on Linux or macOS
CMake ≥ 3.16; a C11 compiler (GCC, Clang, or AppleClang), or C99 with -DPUBNUB_CFG_C99_COMPAT=ON; POSIX threads
libcurl ≥ 7.66 (used if already installed, otherwise fetched and built automatically); OpenSSL; cJSON (fetched automatically)
full / minimal on Windows
CMake ≥ 3.16; MSVC
Same as above, plus the system bcrypt library
embedded (FreeRTOS)
CMake ≥ 3.16; your own cross-compiler and a CMAKE_TOOLCHAIN_FILE (this repository does not provide one)
jsmn (fetched automatically); no OpenSSL or cJSON
ESP-IDF component build
The ESP-IDF toolchain (idf.py) with the IDF_PATH environment variable set
mbedTLS, lwIP, and FreeRTOS (all supplied by ESP-IDF itself); jsmn, and miniz if compression is enabled, downloaded by SHA-256-pinned URL

Every core, provider, and example target sets its C standard individually (C_STANDARD_REQUIRED ON, C_EXTENSIONS OFF), so there is no single global CMAKE_C_STANDARD to override. Pass -DPUBNUB_CFG_C99_COMPAT=ON at configure time if your target compiler cannot support C11. The default GCC/Clang warning baseline is -Wall -Wextra -Wpedantic -Werror plus several stricter checks, so warnings fail your build the same way they fail CI's.

CI-verified platforms

Only Linux (GCC and Clang), macOS (Clang), and Windows (MSVC) run in continuous integration, using the curl and socket transports. The embedded profile and the ESP-IDF component build are source-verified: the code exists and is internally consistent, but no CI job cross-compiles for FreeRTOS or builds against real embedded hardware. Validate those two paths on real hardware before you ship against them.

Build profiles and presets

The SDK ships exactly three named profiles, selected with -DPUBNUB_PROFILE=<name>. Each profile fixes a coherent combination of feature flags, provider backends, and buffer sizes. Passing an unrecognized name is a configure-time error.

ProfilePurposeFeature flagsProvidersNotable tunables
full
All features enabled, hosted platform providers
All 13 optional features ON, including presence, history, message actions, signal, Access Manager, App Context, files, channel groups, crypto, push, time, and compression
transport curl, serialization cjson, crypto openssl, logger stdout, allocator stdlib, platform posix (or windows)
PUBNUB_CFG_NO_HEAP=0, thread safety ON, 16 in-flight requests, 32 KB response buffer
minimal
Publish and subscribe only, hosted providers
Publish, subscribe, retry, secure transport, compression, and request-body compression ON; all other optional features OFF
transport curl, serialization cjson, crypto none, logger stdout, allocator stdlib, platform posix (or windows)
Same response buffer size as full, but 8 in-flight requests, a smaller subscribe batch size, and smaller DNS caches
embedded
Publish and subscribe with an arena allocator, no crypto, FreeRTOS platform, no-heap friendly
Only publish, subscribe, and secure transport ON; retry is explicitly OFF
transport socket, serialization jsmn, crypto none, logger none, allocator arena, platform freertos
PUBNUB_CFG_NO_HEAP=1, thread safety OFF, drastically smaller buffers, arena pool size auto-computed

A profile only supplies defaults. Any -D you pass explicitly on the command line always wins over the profile's value for that variable.

Two additional presets exist in CMakePresets.json for internal use rather than as consumer-facing profiles:

  • dev: turns on every feature flag individually with real provider backends (curl, OpenSSL, cJSON, posix) plus AddressSanitizer and UndefinedBehaviorSanitizer, for local development and testing. It does not set PUBNUB_PROFILE at all.
  • ci-lint: a full-based preset with the socket transport, used to generate compile_commands.json for clang-tidy and cppcheck in CI.

Neither dev nor ci-lint is intended as a target for shipping code. Use full, minimal, or embedded for your application.

No "bare-metal" profile

Comments in the SDK's generated config header mention a fourth "bare-metal profile" for FPU-less Cortex-M0/M0+ targets with reduced JSON limits. That profile is not implemented in cmake/profiles.cmake. Only full, minimal, and embedded exist. Do not pass -DPUBNUB_PROFILE=bare-metal. It fails with an unknown-profile error.

Configure, build, and test

The commands below come from the SDK's CMakePresets.json, CMakeLists.txt, example CMakeLists.txt files, and CI workflow definitions. Use them as your starting point and adjust paths for your checkout.

Host default (full preset)

cmake --preset full
cmake --build --preset full
ctest --preset full

Minimal preset

cmake --preset minimal
cmake --build --preset minimal
ctest --preset minimal

Embedded preset (host-buildable subset only)

cmake --preset embedded -DPUBNUB_BUILD_EXAMPLES=ON
cmake --build --preset embedded

The embedded preset's own description states "Set CMAKE_TOOLCHAIN_FILE for your target board". This two-line sequence configures the embedded profile's feature and provider selection (arena allocator, socket transport, jsmn, FreeRTOS platform provider), but still produces a host binary. A real cross-compiled build also requires -DCMAKE_TOOLCHAIN_FILE=<path-to-your-toolchain>, which this repository does not supply or template.

Dev preset (real backends with sanitizers)

cmake --preset dev
cmake --build --preset dev
ctest --preset dev

Building one example target

cmake --preset full -DPUBNUB_BUILD_EXAMPLES=ON
cmake --build --preset full --target example_publish_sync

Example target names follow the pattern example_<feature>_<style>, for instance example_publish_cooperative, example_publish_sync, example_publish_async, and example_arena_echo.

Provider backends

Six independently swappable provider families cover transport, serialization, crypto, platform, logging, and allocation. Each is a PUBNUB_PROVIDER_<FAMILY> cache variable, validated at configure time.

FamilyCache defaultKnown values
PUBNUB_PROVIDER_TRANSPORT
curl
curl, socket, custom
PUBNUB_PROVIDER_SERIALIZATION
cjson
cjson, jsmn, custom
PUBNUB_PROVIDER_CRYPTO
openssl
openssl, mbedtls, none, custom
PUBNUB_PROVIDER_LOGGER
none (overridden to stdout by the full and minimal profiles)
stdout, none, custom
PUBNUB_PROVIDER_ALLOCATOR
stdlib
stdlib, arena, custom
PUBNUB_PROVIDER_PLATFORM
(none, must be set by a profile or explicitly)
posix, windows, freertos, custom
Resolved logger default

PUBNUB_PROVIDER_LOGGER's bare cache default is none, but both the full and minimal profiles override it to stdout. You only see none if you configure providers by hand without selecting a profile.

An unset or unrecognized provider name fails configure with a message naming the family and its known values. A custom provider also requires PUBNUB_PROVIDER_<FAMILY>_DIR pointing at a directory containing a CMakeLists.txt that defines a pubnub_provider_<family> static library target.

Configure-time provider conflicts

CMake enforces two cross-family rules before the build proceeds:

cJSON requires the stdlib allocator. cJSON's memory hooks are global state, incompatible with a per-context allocator. Selecting cjson serialization together with any allocator other than stdlib fails configure with:

[PubNub] Invalid provider combination: PUBNUB_PROVIDER_SERIALIZATION=cjson requires PUBNUB_PROVIDER_ALLOCATOR=stdlib (got '<value>'). cJSON exposes its memory hooks as global state, which is incompatible with per-context allocator providers. Two resolutions: (1) switch serialization to jsmn (-DPUBNUB_PROVIDER_SERIALIZATION=jsmn), or (2) switch allocator to stdlib (-DPUBNUB_PROVIDER_ALLOCATOR=stdlib).

This is why the only profile that selects the arena allocator (embedded) also selects jsmn, and why both profiles that select cjson (full, minimal) also select stdlib.

Access Manager requires the crypto provider. PUBNUB_ENABLE_PAM=ON without PUBNUB_ENABLE_CRYPTO=ON fails configure with [PubNub] PUBNUB_ENABLE_PAM requires PUBNUB_ENABLE_CRYPTO. PAM request signing depends on the crypto provider. This is why the only profile with Access Manager on (full) also has crypto on.

PUBNUB_SOCKET_TLS_BACKEND is not a general consumer-facing cache variable. The Zephyr module and the ESP-IDF component path set it internally to select the socket transport's TLS implementation (openssl or mbedtls). This is independent of PUBNUB_PROVIDER_CRYPTO, since secure transport and payload encryption are resolved separately. You do not set this variable directly in a standard hosted or embedded-profile build.

Feature flags

Every optional capability is gated behind a PUBNUB_ENABLE_* compile-time flag, resolved to 0/1 in the generated configuration header so #if PUBNUB_ENABLE_X always compiles regardless of setting.

Flag groupfullminimalembedded
PUBNUB_ENABLE_PUBLISH, PUBNUB_ENABLE_SUBSCRIBE, PUBNUB_ENABLE_SECURE_TRANSPORT
ON
ON
ON
PUBNUB_ENABLE_RETRY
ON
ON
OFF
PUBNUB_ENABLE_COMPRESSION, _REQUEST_COMPRESSION
ON
ON
OFF
PUBNUB_ENABLE_PRESENCE, _HISTORY, _MESSAGE_ACTIONS, _SIGNAL, _PAM, _APP_CONTEXT, _FILES, _CHANNEL_GROUPS, _CRYPTO, _PUSH_NOTIFICATIONS, _TIME
ON
OFF
OFF
PUBNUB_ENABLE_FILESYSTEM, _IPV6, _PROXY
ON
ON
OFF
PUBNUB_ENABLE_CPP_WRAPPER
OFF
OFF
OFF

Two effects worth planning around:

  • Enabling PUBNUB_ENABLE_HISTORY silently raises PUBNUB_CFG_HTTP_MAX_QUERY_PARAMS to at least 13 if it was lower. pubnub_fetch_messages() needs up to 8 feature-specific query parameters plus 5 middleware-injected ones.
  • PUBNUB_CFG_PIPELINE_MAX_MIDDLEWARES has a computed floor of 3 (pnsdk identification, user ID, and auth token, always present) plus 1 if Access Manager is on (signature) plus 1 if retry is on (retry) plus 1 if request compression is on (compression). Setting it explicitly below that floor is a configure-time error. Leaving it unset lets the floor apply silently. See Middleware chaining for what each of these six middlewares does.

Compile-time tunables (PUBNUB_CFG_*)

Beyond feature selection, a large set of PUBNUB_CFG_* CMake cache variables controls buffer sizes, counts, and timeouts. All are compile-time only: they size fixed arrays and struct fields, so changing one requires a rebuild. Several also have a hard #error floor enforced in the generated header if you set them too low. A representative subset:

TunableDefaultFloor
PUBNUB_CFG_REQUEST_BUFFER_SIZE
4096
≥ 256
PUBNUB_CFG_RESPONSE_BUFFER_SIZE
32768
≥ 256
PUBNUB_CFG_MAX_SUBSCRIBE_CHANNELS
64
≥ 1
PUBNUB_CFG_MAX_IN_FLIGHT_REQUESTS
4
PUBNUB_CFG_RETRY_DELAY_MS / PUBNUB_CFG_RETRY_MAX_DELAY_MS
2000 / 150000
≥ 100 / ≥ retry delay
PUBNUB_CFG_MAX_LOG_MESSAGE_SIZE
512
0 (disabled) or ≥ 64
PUBNUB_CFG_PIPELINE_MAX_MIDDLEWARES
8
≥ 3 + Access Manager + retry

Setting PUBNUB_CFG_MAX_LOG_MESSAGE_SIZE=0 removes pubnub_log_text_formatted() from the build entirely, rather than merely disabling it at runtime. This is a compile-time public-API change, the same category of effect as PUBNUB_CFG_NO_HEAP (see Memory and footprint tuning).

The full pubnub_config_t runtime field reference, including retry policy, proxy, and TCP keepalive struct fields, is documented in the Configuration reference. This page covers only the compile-time build configuration.

Consuming the SDK from your own project

The SDK source is on GitHub at pubnub/c. There is no installable CMake package for this SDK: the source tree defines no install(), no export(), and no find_package(pubnub) support. The only pattern demonstrated in the SDK's own examples is vendoring the source tree and adding it as a CMake subdirectory in the same configure pass as your application:

add_subdirectory(third_party/pubnub-c)
add_executable(my_app main.c)
target_link_libraries(my_app PRIVATE pubnub)

pubnub is a library target (static by default, or shared when built with -DPUBNUB_BUILD_SHARED=ON) that aggregates the core library, every enabled feature module, and all six selected provider backends. Linking it is sufficient. Set your profile and any provider overrides at the top-level configure command exactly as shown in Configure, build, and test. They apply to the vendored SDK the same way they apply to its own examples.

FetchContent and git submodules

Fetching the SDK with CMake's FetchContent or vendoring it as a git submodule is expected to work by the same add_subdirectory mechanism, since both ultimately add the SDK's CMakeLists.txt to your configure pass. The SDK README shows a FetchContent setup. Neither pattern has an example in the SDK's own source tree, so confirm it in your own build before you rely on it. Release tags use a v prefix, so pin to v1.0.0, not 1.0.0.

By default the pubnub target builds as a static library. Set -DPUBNUB_BUILD_SHARED=ON to build it as a shared library instead. Every public function is exported through the PUBNUB_API macro in that mode. PUBNUB_BUILD_SHARED is rejected at configure time on the embedded profile, on PUBNUB_CFG_NO_HEAP=1 builds, on Apple device platforms, and on the ESP-IDF and Zephyr paths.

Calling patterns

Every feature entry point, publish, subscribe, and so on, returns a pubnub_future_t value. There is one asynchronous model, not separate sync and callback builds. You choose how to consume that value per call site, with no build configuration or header choice involved. Three functions from <pubnub/future.h> provide the three styles.

StyleHow it worksBest forConsiderations
Cooperative polling
Call pubnub_process() repeatedly and check pubnub_future_is_ready() until it returns true
Single-threaded programs, and the only style guaranteed to work on every provider combination, including builds with no thread-safety support
You own the event loop. Nothing runs unless you call pubnub_process()
Blocking await
pubnub_await() blocks the calling thread until the future is terminal
Simple, linear request/response code where blocking the calling thread is acceptable
Never starts a background thread on its own. If a background thread is already running (because a prior pubnub_async() call started one), sleeps briefly between readiness checks. Otherwise it drives pubnub_process() cooperatively, blocking up to PUBNUB_CFG_MAX_POLL_MS per iteration
Callback
pubnub_async() registers a function that fires exactly once, either inline immediately if the future is already complete, or later when the transaction finishes
Event-driven programs and integrations with an existing reactor/event loop
Exactly one callback per future. Calling pubnub_async() twice on the same future is undefined behavior. Lazily starts a background thread if the platform provider supports one, so the callback can fire without you calling pubnub_process() yourself. Without a background thread, the callback only fires the next time you call pubnub_process() and the future is ready

Regardless of style, call pubnub_future_release() exactly once per future. Releasing from inside the future's own completion callback is safe, and releasing an in-flight future is safe too, since reclamation is deferred to the next pubnub_process() tick. pubnub_future_cancel() can cancel any pending future but does not itself release it. Some feature calls validate their arguments before ever contacting PubNub and return the sentinel PUBNUB_FUTURE_INVALID on failure instead of a real future. Compare against PUBNUB_FUTURE_INVALID rather than checking future.ctx or another field directly.

Cooperative polling

#include <pubnub/client.h>
#include <pubnub/features/publish.h>
#include <pubnub/future.h>

#include <stdio.h>

int main(void)
{
pubnub_config_t cfg = pubnub_config_defaults();
cfg.subscribe_key = "sub-key";
cfg.publish_key = "pub-key";
cfg.user_id = "device-042";

pubnub_context_t *ctx = pubnub_create(&cfg);
if (ctx == NULL) {
show all 35 lines

Blocking await

#include <pubnub/client.h>
#include <pubnub/features/publish.h>
#include <pubnub/future.h>

#include <stdio.h>

int main(void)
{
pubnub_config_t cfg = pubnub_config_defaults();
cfg.subscribe_key = "sub-key";
cfg.publish_key = "pub-key";
cfg.user_id = "device-042";

pubnub_context_t *ctx = pubnub_create(&cfg);
if (ctx == NULL) {
show all 33 lines

Callback

#include <pubnub/client.h>
#include <pubnub/features/publish.h>
#include <pubnub/future.h>

#include <stdio.h>

static volatile int s_publish_done = 0;

static void on_publish_done(pubnub_future_t future, pubnub_res_t status, void *user_data)
{
(void)user_data;
/* Check status before touching any result accessor: on PUBNUB_ERR_CANCELLED
(for example, during context teardown) result data is unavailable and
accessors return zero-initialized values. */
if (status == PUBNUB_OK) {
show all 58 lines

Thread safety and context lifecycle

Separate contexts are always safe to use concurrently from different threads. Same-context concurrent use requires external synchronization unless you compile with PUBNUB_CFG_THREAD_SAFETY=1 (the full profile's default, explicitly OFF in embedded), which adds a per-context mutex.

The SDK has two lifecycle models. They differ in more than which functions you call:

LifecycleFunctionsStorageConfig string ownership
Heap-allocated
pubnub_create() / pubnub_destroy()
SDK-allocated on the heap
pubnub_create() deep-copies every const char* field in your pubnub_config_t. You may free or reuse your originals immediately after the call
Caller-provided
pubnub_init() / pubnub_deinit()
A buffer you supply, at least PUBNUB_CONTEXT_SIZE bytes (or pubnub_context_size())
pubnub_init() borrows every const char* field. You must keep them alive for the context's entire lifetime

pubnub_create()/pubnub_destroy() are compiled out of the public API entirely when PUBNUB_CFG_NO_HEAP=1. This is the default in the embedded profile and in the ESP-IDF component build unless overridden. Code that calls pubnub_create() fails to compile against such a build. Only the caller-provided lifecycle is available.

One rule holds on both paths, regardless of heap or no-heap. The six provider pointer fields (allocator, transport, serialization, platform, crypto_module, logger) are always borrowed references. You must keep the structures they point to valid for as long as any context initialized with that configuration exists.

Tearing down a context, on either path, joins any background thread it started, delivers a cancelled result to every outstanding future callback, and only then releases its resources. After pubnub_destroy() or pubnub_deinit() returns, no further callback fires for that context. It is always safe to free or reuse memory the context depended on once teardown returns.

Six fields remain mutable after initialization, each through its own runtime setter rather than by writing the struct directly: the user ID, the auth token, the origin hostname, the per-context log severity threshold, and the primary and secondary DNS servers. You change the DNS servers as a pair, through a single setter. Every other field is fixed for the context's lifetime. See the Configuration reference for the setters and the rest of the runtime-configurable fields, and Status Events for what a cancelled-on-teardown result looks like to your listener.

Memory and footprint tuning for constrained targets

No-heap operation and static context storage

With PUBNUB_CFG_NO_HEAP=1, the only lifecycle is pubnub_init()/pubnub_deinit() on memory you supply. PUBNUB_CONTEXT_SIZE is a compile-time upper bound that the SDK's CMake configuration sets. It isn't computed from your profile or target architecture, and the configure step prints the value in effect ([PubNub] PUBNUB_CONTEXT_SIZE: <n> bytes). The SDK source asserts at compile time that the real context struct fits within it, so a successful SDK build guarantees the constant is large enough. Don't hard-code the byte count in your own code. Size static storage with the macro and confirm it at runtime with pubnub_context_size(), as the example below does.

#include <pubnub/client.h>

#include <stddef.h>
#include <stdint.h>

static PUBNUB_ALIGNAS(max_align_t) uint8_t s_ctx_mem[PUBNUB_CONTEXT_SIZE];

int main(void)
{
if (sizeof(s_ctx_mem) < pubnub_context_size()) {
return 1; /* PUBNUB_CONTEXT_SIZE too small for this build - rebuild required */
}
pubnub_context_t *ctx = (pubnub_context_t *)s_ctx_mem;

pubnub_config_t cfg = pubnub_config_defaults();
show all 28 lines

PUBNUB_ALIGNAS(max_align_t) is not optional. Without it, a plain uint8_t array has 1-byte alignment, and casting it to a pubnub_context_t* is undefined behavior. On a strictly-aligned target such as a Cortex-M0, that undefined behavior manifests as a hard fault rather than a warning.

Arena allocator

For targets with no heap at all, the PUBNUB_PROVIDER_ALLOCATOR=arena provider (the embedded profile's default) replaces every SDK-side allocation with a two-zone static pool. No heap function is ever called. Zone A is a set of fixed-size, purpose-tagged slot pools that serve request/response/scratch buffers directly, without touching the heap. Zone B is a bump allocator with a free list, sized by PUBNUB_CFG_ARENA_ALLOC_BUDGET, that serves smaller per-request objects. PUBNUB_CFG_ARENA_POOL_SIZE is the combined pool: Zone A's fixed slots plus the Zone B budget. Neither has a fixed default byte count: both are derived automatically from your buffer, concurrency, and feature settings by pubnub_compute_arena_sizes(), and the resolved values are printed to the CMake configure log.

The arena has one hard constraint: an arena instance serves exactly one context. Sharing an arena across multiple contexts is undefined behavior, because tearing down one context rewinds the bump cursor and invalidates pointers the other context still holds.

Pool sizing is auto-computed from your buffer and concurrency tunables. You do not need to hand-pick a pool size for a standard embedded-profile build:

#include <pubnub/client.h>
#include <pubnub/features/publish.h>
#include <pubnub/future.h>
#include <pubnub/providers/allocator_arena.h>

#include <stddef.h>
#include <stdint.h>

static uint8_t s_pool[PUBNUB_CFG_ARENA_POOL_SIZE];
static pubnub_arena_allocator_t s_arena;
static PUBNUB_ALIGNAS(max_align_t) uint8_t s_ctx_mem[PUBNUB_CONTEXT_SIZE];

int main(void)
{
pubnub_allocator_provider_t *alloc = pubnub_arena_allocator_init(&s_arena, s_pool, sizeof(s_pool));
show all 48 lines

If you set PUBNUB_CFG_ARENA_POOL_SIZE explicitly without also setting PUBNUB_CFG_ARENA_ALLOC_BUDGET, the build derives the other value for you. If you set neither, both are computed from your feature and buffer configuration. The SDK's own arena_echo example (examples/arena_echo) follows exactly this pattern: a static pool, a static context buffer, pubnub_init() rather than pubnub_create(), and a cooperative pubnub_process() loop. It is a complete worked reference if you need to adapt it.

Other flags the embedded profile turns on for footprint, beyond the allocator itself: PUBNUB_CFG_MINIMAL_FORMATTER=1 (a minimal built-in number formatter instead of the libc snprintf family), PUBNUB_CFG_RES_STR=OFF (drops the diagnostic status-string table), PUBNUB_CFG_JSON_HELPERS=OFF and PUBNUB_CFG_JSON_DOUBLE=OFF (drop optional JSON code paths), and PUBNUB_LOG_MIN_LEVEL=NONE with PUBNUB_CFG_MAX_LOG_MESSAGE_SIZE=0 (drop logging, including removing the formatted-log function from the build). See Logging for the logging subsystem these flags affect.

ESP-IDF component build

Not CI-tested

The ESP-IDF path is a separate, hand-maintained configuration path (cmake/PubnubESP.cmake), entered automatically when ESP_PLATFORM is defined by the ESP-IDF build system, before the SDK's normal CMakeLists.txt even runs. It duplicates rather than reuses the embedded profile's defaults, and no CI job builds it or exercises real ESP32 hardware. It's source-verified only, so validate it on real hardware before you ship.

The ESP-IDF path always uses the arena allocator, jsmn serialization, the socket transport, the FreeRTOS platform provider, and the stdout logger. None of these are selectable via PUBNUB_PROVIDER_* on this path. If you enable the crypto feature, it always pulls in the mbedTLS crypto provider. openssl and none are not reachable here. PUBNUB_CFG_NO_HEAP defaults to 1 on this path unless your component wrapper overrides it, which differs from the host build's default of off.

Enable individual features by setting them before including the ESP helper script, following the SDK's own convention:

# components/pubnub/CMakeLists.txt — thin wrapper, component name fixed to "pubnub"
set(PUBNUB_ENABLE_PRESENCE 1)
set(PUBNUB_SDK_DIR "${CMAKE_CURRENT_LIST_DIR}/../../third_party/pubnub-c")
include("${PUBNUB_SDK_DIR}/cmake/PubnubESP.cmake")

The wrapper's own file location, not an explicit component-name setting, is what fixes the component name to pubnub. Building and flashing then follows standard ESP-IDF project conventions:

cd examples/arena_echo_esp32
idf.py set-target esp32
idf.py build
idf.py -p <PORT> flash monitor

If PUBNUB_ENABLE_COMPRESSION is on, or serialization is jsmn (always true on this path), the build downloads a small number of upstream headers over the network, verified by SHA-256 pin, the first time you configure. For air-gapped builds, place the pinned header at the path the configure step prints and re-run.

Next steps

  • Configuration: the full pubnub_config_t field reference, retry policy, proxy, and TCP keepalive defaults.
  • Status Events: the connection-state model and the full status/result catalog.
  • Logging: log levels, the custom logger interface, and compile-time log stripping.