---
source_url: https://www.pubnub.com/docs/sdks/c/api-reference/configuration
title: Configuration API for C SDK
updated_at: 2026-09-24T12:25:41.000Z
sdk_name: PubNub C SDK
sdk_version: 1.0.0
---

# Configuration API for C SDK

PubNub C SDK, use the latest version: 1.0.0

## Documentation index

To discover more PubNub resources:

1. Fetch [PubNub's llms.txt](https://www.pubnub.com/llms-full.txt) for a list of available pages in Markdown format.
2. Identify relevant URLs from that index.
3. Fetch the target pages.

Do not assume a path exists, always check the index first.

C complete configuration reference for building real-time applications on PubNub: context lifecycle, the `pubnub_config_t` field reference, retry policy, proxy, and TCP keepalive.

Every C SDK context is configured through a single struct, `pubnub_config_t`. Populate it with `pubnub_config_defaults()` and then override the fields your application needs, then pass it to one of two lifecycle functions to obtain a context: `pubnub_create()` (heap-allocated) or `pubnub_init()` (caller-provided memory). Both accept the same `pubnub_config_t`, but they differ in how they take ownership of the strings inside it. Read [Initialization](#initialization) before writing any configuration code.

:::warning String ownership depends on which lifecycle function you call
`pubnub_init()` **borrows** every `const char*` field in `pubnub_config_t`. The memory backing `subscribe_key`, `publish_key`, `user_id`, and every other string field must stay valid for as long as the context exists. `pubnub_create()` **deep-copies** those same fields, so the caller can free or reuse the original buffers immediately after the call returns.
The six provider pointer fields (`allocator`, `transport`, `serialization`, `platform`, `crypto_module`, `logger`) are **always borrowed**, on both paths. Mixing these two rules up is the single most common configuration mistake with this SDK.
:::

## Initialization

### Method(s)

The C SDK exposes two mutually exclusive ways to obtain a `pubnub_context_t`. Use exactly one of them per context.

```c
#include <stddef.h>
#include <pubnub/client.h>

pubnub_config_t pubnub_config_defaults(void);

pubnub_context_t* pubnub_create(const pubnub_config_t* config);
void pubnub_destroy(pubnub_context_t* ctx);

pubnub_res_t pubnub_init(pubnub_context_t* ctx, const pubnub_config_t* config);
void pubnub_deinit(pubnub_context_t* ctx);

size_t pubnub_context_size(void);
```

| Function | Parameters | Returns |
| --- | --- | --- |
| `pubnub_config_defaults` | none | A `pubnub_config_t` with five fields set: `transaction_timeout_ms`, `non_transaction_timeout_ms`, `tcp_keepalive` (enabled, 60 s / 20 s / 3 probes), `retry_configuration` (exponential retry for subscribe requests only, every other endpoint group excluded), and `log_level` (`PUBNUB_LOG_LEVEL_INFO`). It does not set `subscribe_key`, `publish_key`, `secret_key`, or `user_id`. You still set those yourself. |
| `pubnub_create` | `config`: required, borrowed for the duration of the call, deep-copied into the new context | A heap-allocated `pubnub_context_t*`, or `NULL` if `config` is `NULL`, fails validation, or allocation fails. Not compiled in when `PUBNUB_CFG_NO_HEAP=1`. |
| `pubnub_destroy` | `ctx`: consumed, safe to pass `NULL` | `void`. `ctx` must not be used after this call. |
| `pubnub_init` | `ctx`: required, caller-owned memory of at least `pubnub_context_size()` bytes. `config`: required, borrowed for the context's lifetime | `PUBNUB_OK`, or an error code (see [Error responses](#error-responses)) |
| `pubnub_deinit` | `ctx`: borrowed, safe to pass `NULL`, an uninitialized context, or an already-deinitialized context | `void`. Does not free `ctx`'s memory. The caller owns that. |
| `pubnub_context_size` | none | The number of bytes `pubnub_init` needs at the memory `ctx` points to. Always greater than `0`. |

**Header:** `#include <pubnub/client.h>` **Types:** `pubnub_config_t`, `pubnub_context_t`, `pubnub_res_t` **Prerequisite:** call `pubnub_config_defaults()` first, then set at minimum `subscribe_key` and `user_id` before calling `pubnub_create`/`pubnub_init`. **Ownership / lifetime:** `pubnub_init` borrows every `const char*` config field. `pubnub_create` deep-copies them. Provider pointer fields are always borrowed. A context obtained from `pubnub_create` is heap-owned by the SDK until `pubnub_destroy`. A context obtained from `pubnub_init` lives in memory the caller owns and must keep valid until `pubnub_deinit`. **Blocking:** synchronous. None of these functions perform network I/O.

:::warning No heap, no pubnub_create
`pubnub_create()` and `pubnub_destroy()` are compiled out entirely when `PUBNUB_CFG_NO_HEAP=1`, the default for the `embedded` CMake profile. On a no-heap build, use `pubnub_init()`/`pubnub_deinit()` with caller-provided memory, shown below. See [Environment Setup](https://www.pubnub.com/docs/sdks/c/environment-setup.md) for build profiles.
:::

### Sample code

Heap-allocated context, using `pubnub_create()`/`pubnub_destroy()`:

```c
#include <stddef.h>
#include <pubnub/client.h>

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

    pubnub_context_t* ctx = pubnub_create(&cfg);
    if (ctx == NULL) {
        /* config was NULL, failed validation, or allocation failed */
        return 1;
    }

    /* ctx is ready — pass it to any feature call, e.g. pubnub_publish() */

    pubnub_destroy(ctx);
    return 0;
}
```

Caller-provided memory, using `pubnub_init()`/`pubnub_deinit()`. Required on builds with `PUBNUB_CFG_NO_HEAP=1`:

```c
#include <stddef.h>
#include <stdint.h>
#include <pubnub/client.h>
#include <pubnub/pubnub_compat.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()) {
        /* PUBNUB_CONTEXT_SIZE is stale relative to this build; enlarge the buffer */
        return 1;
    }

    pubnub_context_t* ctx = (pubnub_context_t*)s_ctx_mem;

    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.subscribe_key = "demo";
    cfg.user_id       = "my_unique_user_id";

    pubnub_res_t res = pubnub_init(ctx, &cfg);
    if (res != PUBNUB_OK) {
        return 1;
    }

    /* ctx is ready — pass it to any feature call, e.g. pubnub_publish() */

    pubnub_deinit(ctx);
    return 0;
}
```

`PUBNUB_ALIGNAS(max_align_t)` on the static buffer is required: without it, casting a plain `uint8_t[]` (natural alignment `1`) to a `pubnub_context_t*` is undefined behavior and can fault on strictly-aligned targets. See [Embedded and no-heap targets](#embedded-and-no-heap-targets) for the full story.

### Returns

`pubnub_create` returns a `pubnub_context_t*` (or `NULL` on failure). `pubnub_init` returns a `pubnub_res_t`. `PUBNUB_OK` means the context is ready to use.

### Error responses

| Value | Returned by | Meaning | Recovery |
| --- | --- | --- | --- |
| `PUBNUB_OK` | `pubnub_init` | Context initialized successfully. | none needed |
| `PUBNUB_ERR_INVALID_ARGUMENT` | `pubnub_init` | `ctx`/`config` is `NULL`, `subscribe_key`/`user_id` is `NULL` or empty, or `pubnub_init` was called twice on the same context without an intervening `pubnub_deinit`. | Fix the caller-side argument, or call `pubnub_deinit` before re-initializing. |
| `PUBNUB_ERR_PROVIDER_MISSING` | `pubnub_init`, `pubnub_create` | A supplied `allocator`, `transport`, `serialization`, or `platform` provider struct is missing a mandatory vtable entry. | Supply the missing vtable function, or leave the field `NULL` to use the compiled-in default. |
| `PUBNUB_ERR_OUT_OF_MEMORY` | `pubnub_init`, `pubnub_create` | An internal allocation (request pool, pending queue, pipeline middleware) failed during setup. Any partial allocation is rolled back. | Reduce concurrent contexts/buffers, or supply a larger/alternate allocator. |
| `PUBNUB_ERR_INTERNAL` | `pubnub_init` | A provider's own `init()` callback returned failure. Already-initialized providers are rolled back in reverse order. | Inspect the failing provider's `init()` implementation. |

`pubnub_create` signals the same first three failure classes by returning `NULL` instead of a `pubnub_res_t`. For the full `pubnub_res_t` catalog, see [Status Events](https://www.pubnub.com/docs/sdks/c/status-events.md).

:::note Teardown and outstanding futures
Both `pubnub_destroy` and `pubnub_deinit` join any background thread, then deliver `PUBNUB_ERR_CANCELLED` to every outstanding future's callback, then release resources. After either call returns, no further callbacks fire for that context. If you hold a `pubnub_future_t` across a `pubnub_destroy`/`pubnub_deinit` call, expect it to complete with `PUBNUB_ERR_CANCELLED`.
:::

## Configuration options

`pubnub_config_t` has 26 top-level fields: 20 value and string fields, plus 6 provider pointer fields. Every field is immutable after the context is created or initialized, **except** `user_id`, `auth_token`, `origin`, `log_level`, and `dns_primary`/`dns_secondary` (changed together as a pair), which have dedicated runtime setters covered in [Runtime updates](#runtime-updates).

String fields follow the ownership rule from [Initialization](#initialization): borrowed by `pubnub_init`, deep-copied by `pubnub_create`. Provider pointer fields are always borrowed, regardless of which lifecycle function you use.

#### Identity and keys

| Parameter | Ownership | Mutable after init |
| --- | --- | --- |
| `subscribe_key` *Type: `const char*`Default: none, rejected if `NULL`/`""` | borrowed / deep-copied | No |
| `publish_key`Type: `const char*`Default: `NULL` | borrowed / deep-copied | No |
| `secret_key`Type: `const char*`Default: `NULL` | borrowed / deep-copied | No |
| `pnsdk_suffix`Type: `const char*`Default: `NULL` (no suffix appended) | borrowed / deep-copied | No |
| `pnsdk_override`Type: `const char*`Default: `NULL` (compile-time SDK identifier unchanged) | borrowed / deep-copied | No |
| `user_id` *Type: `const char*`Default: none, rejected if `NULL`/`""` | borrowed / deep-copied | **Yes**, via `pubnub_set_user_id()` |
| `auth_token`Type: `const char*`Default: `NULL` | borrowed / deep-copied | **Yes**, via `pubnub_set_auth_token()` |

`user_id` must uniquely identify the user or device that connects to PubNub.

It's a UTF-8 encoded string of up to 92 alphanumeric characters.

publish_key
is required only for publish operations and Access Manager signing.
secret_key
is required only for Access Manager signing.

`pnsdk_override` is for wrapper-SDK authors, not application code. When set, it completely replaces the compile-time SDK identifier in the `pnsdk` query parameter sent on every request, instead of leaving it in place and appending after it the way `pnsdk_suffix` does. Use it if you're building a higher-level SDK on top of this C SDK (for example, a Unity or Unreal Engine wrapper) and need PubNub to see your own product identity rather than `PubNub-C`.

#### Network

| Parameter | Ownership | Mutable after init |
| --- | --- | --- |
| `origin`Type: `const char*`Default: `NULL`/`""` resolves to the compile-time origin (`ps.pndsn.com` by default) | borrowed / deep-copied (the resolved default is deep-copied too, even under `pubnub_create`) | **Yes**, via `pubnub_set_origin()` |
| `dns_primary`Type: `const char*`Default: `NULL` (system-discovered DNS servers) | borrowed / deep-copied | **Yes**, via `pubnub_set_dns_servers()`, together with `dns_secondary` |
| `dns_secondary`Type: `const char*`Default: `NULL` | borrowed / deep-copied | **Yes**, via `pubnub_set_dns_servers()`, together with `dns_primary` |
| `transaction_timeout_ms`Type: `unsigned int` (ms)Default: `0` resolves to a compile-time default (10000 ms), floor of 1000 ms | value | No |
| `non_transaction_timeout_ms`Type: `unsigned int` (ms)Default: `0` resolves to a compile-time default (310000 ms), only meaningful when subscribe is compiled in, floor of 1000 ms | value | No |
| `proxy`Type: `pubnub_proxy_config_t`Default: zero-initialized struct → `PUBNUB_PROXY_NONE` (no proxy) | value (string sub-fields follow the same borrow/deep-copy rule) | No |
| `tcp_keepalive`Type: `pubnub_tcp_keepalive_config_t`Default: see [TCP keepalive](#tcp-keepalive). The default is not simply "disabled" | value | No |

To request a custom `origin`, contact support and follow the [request process](https://www.pubnub.com/docs/general/setup/data-security.md#request-process).

#### Presence

| Parameter | Ownership | Mutable after init |
| --- | --- | --- |
| `presence_timeout`Type: `uint32_t` (seconds)Default: `0` → server default (300 s) | value | No |
| `heartbeat_interval`Type: `uint32_t` (seconds)Default: `0` disables automatic client-side heartbeats entirely | value | No |
| `filter_expression`Type: `const char*`Default: `NULL` (no filter) | borrowed / deep-copied | No, immutable even by the field's own doc comment |
| `suppress_leave_events`Type: `uint8_t` (0/1)Default: `0`, leave events are sent | value | No |

`presence_timeout` and `heartbeat_interval` control how the server tracks client [presence](https://www.pubnub.com/docs/general/presence/overview.md). Both are ignored at runtime when the SDK is built without presence support. `filter_expression` restricts subscribe delivery to messages that match a filter. See [Publish messages](https://www.pubnub.com/docs/general/messages/publish.md) for filter expression syntax.

There is no field or callback on `pubnub_config_t` that reports heartbeat success or failure back to your application. Automatic heartbeat is entirely fire-and-forget: the SDK sends it on the schedule these two fields describe, and nothing in the public API tells you whether any individual heartbeat succeeded.

:::warning Leaving heartbeat_interval at its default disables automatic heartbeat
`heartbeat_interval` at `0` (its default) disables automatic client-side heartbeats entirely. There is no auto-computed fallback derived from `presence_timeout`. This is exactly what `pubnub_config_defaults()` produces, since it touches neither field. To get automatic heartbeat, explicitly set `heartbeat_interval` to a nonzero value yourself.
:::

#### Retry

| Parameter | Ownership | Mutable after init |
| --- | --- | --- |
| `retry_configuration`Type: `pubnub_retry_configuration_t`Default: zero-initialized struct → `PUBNUB_RETRY_NONE` (no automatic retries) | value | No |

Full field breakdown in [Retry and reconnection](#retry-and-reconnection).

:::note Two different defaults, depending on how you build the config
A zero-initialized `pubnub_config_t` (for example `= {0}`, or a `pubnub_init()`-based context you didn't run through `pubnub_config_defaults()`) leaves `retry_configuration.policy` at `PUBNUB_RETRY_NONE`: no automatic retry at all. `pubnub_config_defaults()` sets `policy` to `PUBNUB_RETRY_EXPONENTIAL`, but also sets `excluded_endpoints` to every endpoint group except subscribe. The net effect of calling `pubnub_config_defaults()` is exponential retry on subscribe only, with publish, presence, message storage, channel groups, App Context, message reactions, and Access Manager requests all excluded. `PUBNUB_RETRY_LINEAR` is never a default on either path. It's an opt-in value you set yourself.
:::

#### Logging

| Parameter | Ownership | Mutable after init |
| --- | --- | --- |
| `log_level`Type: `pubnub_log_level_t`Default: zero-init → `PUBNUB_LOG_LEVEL_NONE`, `pubnub_config_defaults()` → `PUBNUB_LOG_LEVEL_INFO` | value | **Yes**, via `pubnub_set_log_level()` |

Setting `log_level` at config time takes effect before the first log line the SDK emits during `pubnub_create()`/`pubnub_init()` itself. Calling `pubnub_set_log_level()` afterward has the same effect from that point on. See [Logging](https://www.pubnub.com/docs/sdks/c/logging.md) for the full level catalog and the custom logger interface.

#### Provider pointers

| Parameter | Ownership | Mutable after init |
| --- | --- | --- |
| `allocator`Type: `pubnub_allocator_provider_t*`Default: `NULL` → compiled-in default allocator | always borrowed | No |
| `transport`Type: `pubnub_transport_provider_t*`Default: `NULL` → compiled-in default transport | always borrowed | No |
| `serialization`Type: `pubnub_serialization_provider_t*`Default: `NULL` → compiled-in default serialization | always borrowed | No |
| `platform`Type: `pubnub_platform_provider_t*`Default: `NULL` → compiled-in default platform layer | always borrowed | No |
| `crypto_module`Type: `pubnub_crypto_module_t*`Default: `NULL` → payload encryption disabled, deliberately with no fallback default | borrowed, caller manages the module's lifetime | No |
| `logger`Type: `pubnub_logger_provider_t*`Default: `NULL` → only the built-in default logger is registered | always borrowed | No, but more loggers can be added post-init with `pubnub_logger_add()` |

Full discussion in [Providers](#providers).

## Retry and reconnection

### Method(s)

**Header:** `#include <pubnub/client.h>` **Types:** `pubnub_retry_configuration_t`, `pubnub_retry_policy_t`, `pubnub_endpoint_group_t` **Feature flag:** ignored at runtime when the SDK is built with `PUBNUB_ENABLE_RETRY=0`.

| Parameter | Description |
| --- | --- |
| `policy`Type: `pubnub_retry_policy_t`Default: `PUBNUB_RETRY_NONE` on zero-init, `PUBNUB_RETRY_EXPONENTIAL` via `pubnub_config_defaults()` | `PUBNUB_RETRY_NONE`, `PUBNUB_RETRY_LINEAR`, or `PUBNUB_RETRY_EXPONENTIAL` |
| `delay_ms`Type: `unsigned int`Default: `0` → compile-time default (2000 ms) | Linear: delay between retries. Exponential: base delay. |
| `maximum_delay_ms`Type: `unsigned int`Default: `0` → compile-time default (150000 ms) | Ignored for the linear policy. |
| `maximum_retry`Type: `unsigned int`Default: `0` → compile-time default per policy (10 for linear, 6 for exponential) | Documented maximum: 10. |
| `excluded_endpoints`Type: `unsigned int` (bitmask)Default: `0` → retry all endpoint groups | Bitwise OR of `pubnub_endpoint_group_t` values, below. |

`pubnub_endpoint_group_t` values, one bit per endpoint family:

| Value | Bit |
| --- | --- |
| `PUBNUB_ENDPOINT_MESSAGE_SEND` | `0x01` |
| `PUBNUB_ENDPOINT_SUBSCRIBE` | `0x02` |
| `PUBNUB_ENDPOINT_PRESENCE` | `0x04` |
| `PUBNUB_ENDPOINT_MESSAGE_STORAGE` | `0x08` |
| `PUBNUB_ENDPOINT_CHANNEL_GROUPS` | `0x10` |
| `PUBNUB_ENDPOINT_APP_CONTEXT` | `0x20` |
| `PUBNUB_ENDPOINT_MESSAGE_REACTIONS` | `0x40` |
| `PUBNUB_ENDPOINT_PAM` | `0x80` |
| `PUBNUB_ENDPOINT_FILES` | `0x100` |

### Sample code

```c
#include <pubnub/client.h>

static void configure_retry(void)
{
    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.subscribe_key = "demo";
    cfg.user_id       = "my_unique_user_id";

    /* Retry a failed request up to 5 times, 2 seconds apart, but never retry
       publishes or history fetches — only subscribe and everything else */
    cfg.retry_configuration.policy             = PUBNUB_RETRY_LINEAR;
    cfg.retry_configuration.delay_ms           = 2000;
    cfg.retry_configuration.maximum_retry      = 5;
    cfg.retry_configuration.excluded_endpoints =
        PUBNUB_ENDPOINT_MESSAGE_SEND | PUBNUB_ENDPOINT_MESSAGE_STORAGE;
}
```

The policy/delay/retry-count lines follow the `pubnub_retry_configuration_t` field definitions. The `excluded_endpoints` line follows the OR-ed form shown in the header's own `@code` example.

### Returns

None. `retry_configuration` takes effect the next time the context retries a request. It has no separate return value.

## Proxy configuration

### Method(s)

**Header:** `#include <pubnub/proxy.h>` **Types:** `pubnub_proxy_config_t`, `pubnub_proxy_type_t`, `pubnub_proxy_auth_t` **Feature flag:** the `proxy` field exists regardless of build configuration, but proxy support is compiled in only when `PUBNUB_ENABLE_PROXY=1`.

| Parameter | Description |
| --- | --- |
| `type`Type: `pubnub_proxy_type_t`Default: `PUBNUB_PROXY_NONE` | Also: `PUBNUB_PROXY_HTTP_CONNECT` (RFC 7231 §4.3.6), `PUBNUB_PROXY_SOCKS5` (RFC 1928), or `PUBNUB_PROXY_AUTO` (documented as WPAD/PAC discovery with a fallback to a direct connection, though see transport support below). |
| `auth`Type: `pubnub_proxy_auth_t`Default: `PUBNUB_PROXY_AUTH_NONE` | Also: `PUBNUB_PROXY_AUTH_BASIC` (RFC 7617), `PUBNUB_PROXY_AUTH_DIGEST` (RFC 7616, MD5), or `PUBNUB_PROXY_AUTH_NTLM` (NTLMv2, ASCII-only credentials). See transport support below. |
| `host`Type: `const char*`Default: `NULL` | May be `NULL` only when `type` is `PUBNUB_PROXY_NONE`. Same borrow/deep-copy rule as other string fields. |
| `port`Type: `uint16_t`Default: `0` | For example, `3128` for HTTP CONNECT, `1080` for SOCKS5. |
| `username`Type: `const char*`Default: `NULL` | May be `NULL` only when `auth` is `PUBNUB_PROXY_AUTH_NONE`. Same borrow/deep-copy rule. |
| `password`Type: `const char*`Default: `NULL` | May be `NULL` only when `auth` is `PUBNUB_PROXY_AUTH_NONE`. Same borrow/deep-copy rule. |

Each transport provider decides whether and how it honors a given proxy type. An unsupported type may be rejected at init time or at request time. Support is transport-dependent. The table above documents the `pubnub_proxy_config_t` options, not what any one transport actually does with each value.

**Support on the default curl transport** (the hosted default, per `cmake/providers.cmake`):

| Value | Support on `curl` |
| --- | --- |
| `PUBNUB_PROXY_HTTP_CONNECT` | Supported. |
| `PUBNUB_PROXY_SOCKS5` | Supported. |
| `PUBNUB_PROXY_AUTO` | **Not supported.** Rejected as a transport error. It does not perform WPAD/PAC discovery and does not fall back to a direct connection, despite the field's doc comment. |
| `PUBNUB_PROXY_AUTH_BASIC` | Supported. |
| `PUBNUB_PROXY_AUTH_DIGEST` | **Accepted by the config API but silently ignored.** See warning below. |
| `PUBNUB_PROXY_AUTH_NTLM` | **Accepted by the config API but silently ignored.** See warning below. |

This is transport-specific, not a limit of the `proxy` field itself: a different transport provider may support more (or less) of this surface. The `socket` transport's proxy handling isn't documented here; check its source before relying on it.

:::warning curl silently drops Digest and NTLM proxy authentication
On the default `curl` transport, only `PUBNUB_PROXY_AUTH_BASIC` is wired to send credentials. `PUBNUB_PROXY_AUTH_DIGEST` and `PUBNUB_PROXY_AUTH_NTLM` are accepted without error at configuration time, but the transport never sends the username/password for them. The request proceeds unauthenticated and then fails at the proxy. This looks like a bad-credentials problem, not an unsupported-auth-scheme problem. Verify your `auth` value if a proxied request is failing for no obvious reason.
:::

### Sample code

```c
#include <pubnub/client.h>
#include <pubnub/proxy.h>

static void configure_proxy(void)
{
    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.subscribe_key = "demo";
    cfg.user_id       = "my_unique_user_id";

    cfg.proxy = (pubnub_proxy_config_t){
        .type     = PUBNUB_PROXY_HTTP_CONNECT,
        .auth     = PUBNUB_PROXY_AUTH_BASIC,
        .host     = "proxy.example.com",
        .port     = 3128,
        .username = "proxy_user",
        .password = "proxy_password"
    };
}
```

This follows the designated-initializer form shown in `proxy.h`'s own `@code` block.

:::note No compiled example ships in the SDK
No example program in the SDK exercises a non-default proxy configuration. This snippet is derived from the `pubnub_proxy_config_t` field definitions and the header's designated-initializer form, not from a compiled example program.
:::

### Returns

None. `proxy` takes effect at context creation/initialization. There is no separate return value.

## TCP keepalive

TCP (Transmission Control Protocol) keepalive periodically probes an idle connection so the SDK notices a dead socket instead of waiting indefinitely for a response.

### Method(s)

**Header:** `#include <pubnub/tcp_keepalive.h>` **Types:** `pubnub_tcp_keepalive_config_t`

| Parameter | Description |
| --- | --- |
| `enabled`Type: `uint8_t` (0/1) | Keepalive on/off. |
| `idle_sec`Type: `uint32_t` | Seconds idle before the first probe. |
| `interval_sec`Type: `uint32_t` | Seconds between probes. |
| `probe_count`Type: `uint32_t` | Probes sent before the connection is declared dead. |

Transport providers copy this value at context init time. Mutating the struct afterward on the caller's side has no effect.

:::warning Two different defaults
A `pubnub_tcp_keepalive_config_t` that you zero-initialize yourself is **disabled** (`enabled = 0`). The `tcp_keepalive` value that `pubnub_config_defaults()` returns is **enabled**, using the `PUBNUB_TCP_KEEPALIVE_CONFIG_INIT` macro from `pubnub/tcp_keepalive.h`, which expands to `idle_sec = 60`, `interval_sec = 20`, `probe_count = 3`. Which one your context gets depends entirely on whether you started from `pubnub_config_defaults()`. Use `PUBNUB_TCP_KEEPALIVE_CONFIG_INIT` directly if you want the same enabled defaults without going through `pubnub_config_defaults()`.
:::

### Sample code

```c
#include <pubnub/client.h>

static void configure_tcp_keepalive(void)
{
    pubnub_config_t cfg = pubnub_config_defaults();
    /* cfg.tcp_keepalive is now { enabled=1, idle_sec=60, interval_sec=20, probe_count=3 } */

    cfg.tcp_keepalive.idle_sec     = 30;
    cfg.tcp_keepalive.interval_sec = 10;
    cfg.tcp_keepalive.probe_count  = 4;
}
```

### Returns

None. `tcp_keepalive` is applied when the context is created or initialized.

## Providers

`pubnub_config_t` exposes six provider pointer fields: `allocator`, `transport`, `serialization`, `platform`, `crypto_module`, and `logger`. These let you replace the compiled-in memory, networking, JSON, OS, encryption, and logging backends. This section documents the fields and their validation contract, not how to author a provider. A dedicated providers guide will cover implementing a custom vtable.

### Method(s)

| Parameter | Compiled-in default when `NULL` | Mandatory vtable entries if you supply a provider | Missing-entry error |
| --- | --- | --- | --- |
| `allocator` | e.g. a `stdlib`-backed allocator | `alloc`, `free`, `buf_acquire`, `buf_release` | `PUBNUB_ERR_PROVIDER_MISSING` |
| `transport` | e.g. a `curl`-backed transport | `send`, `poll`, `cancel` | `PUBNUB_ERR_PROVIDER_MISSING` |
| `serialization` | e.g. a `cjson`-backed serializer | `parse`, `serialize`, `value_destroy` | `PUBNUB_ERR_PROVIDER_MISSING` |
| `platform` | e.g. a `posix`-backed platform layer | `monotonic_ms`, `sleep_ms`, `random_bytes` | `PUBNUB_ERR_PROVIDER_MISSING` |
| `crypto_module` | disabled, no fallback default | n/a (module, not a raw vtable) | not validated the same way |
| `logger` | built-in default logger | none confirmed mandatory | not validated the same way |

**Header:** `#include <pubnub/client.h>` **Types:** `pubnub_allocator_provider_t*`, `pubnub_transport_provider_t*`, `pubnub_serialization_provider_t*`, `pubnub_platform_provider_t*`, `pubnub_crypto_module_t*`, `pubnub_logger_provider_t*` **Prerequisite:** the exact compiled-in backend for each family (which library or RTOS binding is linked in) is a build-time choice. See [Environment Setup](https://www.pubnub.com/docs/sdks/c/environment-setup.md). **Ownership / lifetime:** all six fields are always borrowed, on both `pubnub_create` and `pubnub_init`. The caller must keep every supplied provider struct alive for the lifetime of the context. `crypto_module` in particular documents this explicitly: the caller manages the module's lifetime. **Blocking:** provider `init()`/`deinit()` callbacks run inline, synchronously, during `pubnub_init`/`pubnub_create` and `pubnub_deinit`/`pubnub_destroy` respectively. They never run from a background thread, and are never re-entrant with the configuration API. If one provider's `init()` fails, the SDK rolls back every provider whose `init()` already succeeded, calling `deinit()` on each in reverse order.

### Sample code

```c
#include <pubnub/client.h>

static void configure_providers(void)
{
    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.subscribe_key = "demo";
    cfg.user_id       = "my_unique_user_id";

    /* Leave every provider field NULL to use the compiled-in defaults */
    cfg.allocator     = NULL;
    cfg.transport     = NULL;
    cfg.serialization = NULL;
    cfg.platform      = NULL;
    cfg.crypto_module = NULL; /* payload encryption stays disabled */
    cfg.logger        = NULL; /* only the built-in default logger runs */
}
```

### Returns

`pubnub_init`/`pubnub_create` return `PUBNUB_ERR_PROVIDER_MISSING` if a non-`NULL` provider struct you supply is missing one of its mandatory vtable entries. `logger` isn't validated the same way. A malformed logger vtable is accepted rather than rejected.

## Runtime updates

Six `pubnub_config_t` fields have dedicated runtime setters: `user_id`, `auth_token`, `origin`, `log_level`, and `dns_primary`/`dns_secondary`, which you change together as a pair through one setter. Every other field is fixed once the context is created or initialized. Two more runtime controls aren't tied to a config-struct field at all: `pubnub_set_tls_ca_bundle()` and `pubnub_set_tls_verify()` reconfigure the transport's TLS behavior directly.

### Method(s)

```c
#include <pubnub/client.h>

pubnub_res_t pubnub_set_user_id(pubnub_context_t* ctx, const char* user_id);
const char*  pubnub_get_user_id(const pubnub_context_t* ctx);

pubnub_res_t pubnub_set_auth_token(pubnub_context_t* ctx, const char* token);
const char*  pubnub_get_auth_token(const pubnub_context_t* ctx);

pubnub_res_t pubnub_set_origin(pubnub_context_t* ctx, const char* origin);
const char*  pubnub_get_origin(const pubnub_context_t* ctx);

pubnub_res_t pubnub_set_log_level(pubnub_context_t* ctx, unsigned int level);

pubnub_res_t pubnub_set_dns_servers(pubnub_context_t* ctx,
                                    const char*        primary,
                                    const char*        secondary);

pubnub_res_t pubnub_set_tls_ca_bundle(pubnub_context_t* ctx, const char* ca_pem);
pubnub_res_t pubnub_set_tls_verify(pubnub_context_t* ctx, uint8_t skip_verify);
```

| Parameter | Description |
| --- | --- |
| `pubnub_set_user_id` | `ctx` borrowed. `user_id` required, non-empty, borrowed. Returns `PUBNUB_OK`, or `PUBNUB_ERR_INVALID_ARGUMENT` if `NULL`/`""`. |
| `pubnub_get_user_id` | `ctx` borrowed. Returns the context-owned current `user_id`, or `NULL` if `ctx` is uninitialized. Do not free the returned pointer. It is valid until the next `pubnub_set_user_id` call or context destruction. |
| `pubnub_set_auth_token` | `ctx` borrowed. `token` optional, borrowed, or `NULL` to clear. Returns `PUBNUB_OK`. |
| `pubnub_get_auth_token` | `ctx` borrowed. Returns the context-owned current `auth_token`, or `NULL` if unset or `ctx` is uninitialized. Same non-owning rule as `pubnub_get_user_id`. |
| `pubnub_set_origin` | `ctx` borrowed. `origin` optional, borrowed; a new hostname, or `NULL`/`""` to reset to the compile-time default. Takes effect on the next request dispatch; in-flight requests keep their own copy of the host. Returns `PUBNUB_OK`, `PUBNUB_ERR_NOT_INITIALIZED` if `ctx` is `NULL` or not initialized, or `PUBNUB_ERR_INVALID_ARGUMENT` if `origin` exceeds `PUBNUB_CFG_MAX_HOSTNAME_LEN`. |
| `pubnub_get_origin` | `ctx` borrowed. Returns the context-owned current origin, or `NULL` if `ctx` is `NULL` or uninitialized. Valid until the next `pubnub_set_origin` call or context destruction. |
| `pubnub_set_log_level` | `ctx` borrowed. `level` a `PUBNUB_LOG_LEVEL_*` constant. Returns `PUBNUB_OK`. |
| `pubnub_set_dns_servers` | `ctx` borrowed. `primary`/`secondary` each an IPv4 or IPv6 address string, or `NULL`. `NULL` for `primary` clears both and reverts to system discovery. Returns `PUBNUB_OK`, `PUBNUB_ERR_INVALID_ARGUMENT` if a non-`NULL` address string doesn't parse, or `PUBNUB_ERR_NOT_SUPPORTED` if the active transport can't honor custom DNS servers. |
| `pubnub_set_tls_ca_bundle` | `ctx` borrowed. `ca_pem` a PEM certificate chain, or `NULL` to revert to the platform's system trust store. Takes effect on connections opened after the call. In-flight connections are unaffected. Returns `PUBNUB_OK`. |
| `pubnub_set_tls_verify` | `ctx` borrowed. `skip_verify` non-zero to disable peer certificate verification, zero to re-enable it. Returns `PUBNUB_OK`. |

**Header:** `#include <pubnub/client.h>` **Ownership / lifetime:** on a `pubnub_create`-based context, `pubnub_set_user_id`/`pubnub_set_auth_token`/`pubnub_set_dns_servers` deep-copy the new value, mirroring the config-level rule. `pubnub_set_origin` always copies its argument into an internal fixed-size buffer, regardless of lifecycle. On a `pubnub_init`-based context, the other setters borrow their value instead. On an arena/bump-pointer allocator target, each call allocates new storage without reclaiming the prior allocation. Minimize how often you rotate these values on memory-constrained targets. **Thread / callback context:** these are plain synchronous mutators, not callbacks, so there is no re-entrancy contract to document. `pubnub_set_dns_servers()` is safe to call from any thread, including on multi-core RTOS targets. A rotation is visible to the middleware pipeline on the very next request, on both the borrowed and deep-copied paths. No pipeline rebuild is required. **Blocking:** synchronous, no I/O.

:::warning Disabling TLS verification exposes connections to man-in-the-middle attacks
Only call `pubnub_set_tls_verify(ctx, 1)` in development and testing. Never ship it enabled in production.
:::

### Sample code

```c
#include <pubnub/client.h>

static void rotate_credentials(pubnub_context_t* ctx)
{
    pubnub_res_t res = pubnub_set_user_id(ctx, "new_user_id");
    if (res != PUBNUB_OK) {
        /* user_id was NULL or empty */
    }

    const char* current_user_id = pubnub_get_user_id(ctx);
    (void)current_user_id;

    res = pubnub_set_auth_token(ctx, "new-auth-token");
    if (res != PUBNUB_OK) {
        /* handle error */
    }
    /* pubnub_set_auth_token(ctx, NULL) clears the token */

    res = pubnub_set_origin(ctx, "custom.pndsn.com");
    if (res != PUBNUB_OK) {
        /* origin exceeds PUBNUB_CFG_MAX_HOSTNAME_LEN, or ctx is not initialized */
    }
    /* pubnub_set_origin(ctx, NULL) resets to the compile-time default */

    pubnub_set_log_level(ctx, PUBNUB_LOG_LEVEL_NONE);

    /* Point at a custom DNS server pair instead of system discovery. */
    pubnub_set_dns_servers(ctx, "8.8.8.8", "1.1.1.1");
}
```

### Returns

`pubnub_set_user_id`, `pubnub_set_origin`, `pubnub_set_log_level`, `pubnub_set_dns_servers`, `pubnub_set_tls_ca_bundle`, and `pubnub_set_tls_verify` all return `pubnub_res_t`. `pubnub_get_user_id`/`pubnub_get_auth_token`/`pubnub_get_origin` return a context-owned `const char*`. For the full log-level catalog and custom logger interface, see [Logging](https://www.pubnub.com/docs/sdks/c/logging.md).

## Driving the event loop

### Method(s)

```c
#include <pubnub/client.h>

pubnub_res_t pubnub_process(pubnub_context_t* ctx);
```

**Header:** `#include <pubnub/client.h>` **Prerequisite:** `ctx` must already be initialized (`pubnub_init`/`pubnub_create` succeeded). **Blocking:** non-blocking. `pubnub_process` performs a single tick of work and returns immediately.

| Parameter | Direction | Description |
| --- | --- | --- |
| `ctx` | in, borrowed | `NULL`, never-initialized, or already-deinitialized contexts return `PUBNUB_ERR_NOT_INITIALIZED`. |

### Sample code

```c
#include <pubnub/client.h>

static void drive_event_loop(pubnub_context_t* ctx)
{
    pubnub_res_t res;
    do {
        res = pubnub_process(ctx);
    } while (res == PUBNUB_IN_PROGRESS);
}
```

### Returns

| Value | Meaning |
| --- | --- |
| `PUBNUB_OK` | The context is quiescent. No pending work remains. |
| `PUBNUB_IN_PROGRESS` | Work remains. Call `pubnub_process` again, or use `pubnub_await`/`pubnub_async` instead of polling. |
| `PUBNUB_ERR_NOT_INITIALIZED` | `ctx` is `NULL`, was never initialized, or was already deinitialized. |

`pubnub_process` is one of three ways to consume a `pubnub_future_t` returned by a feature call. The other two are blocking (`pubnub_await`) and callback-driven (`pubnub_async`). These are three consumption styles for the same asynchronous model, not three different builds.

## Accessing the serialization provider

### Method(s)

```c
#include <pubnub/client.h>

pubnub_serialization_provider_t* pubnub_serialization(pubnub_context_t* ctx);
```

**Header:** `#include <pubnub/client.h>` **Types:** `pubnub_serialization_provider_t` **Ownership / lifetime:** the returned pointer is borrowed and stays valid until the context is destroyed or deinitialized. Do not free it. Some vtable entries on the returned provider may themselves be `NULL`. Check before calling through them.

| Parameter | Direction | Description |
| --- | --- | --- |
| `ctx` | in, borrowed | Returns `NULL` if `ctx` is `NULL` or uninitialized. |

### Sample code

```c
#include <pubnub/client.h>

static void inspect_serialization(pubnub_context_t* ctx)
{
    pubnub_serialization_provider_t* serialization = pubnub_serialization(ctx);
    if (serialization != NULL) {
        /* inspect or use the active serialization vtable */
    }
}
```

### Returns

The active `pubnub_serialization_provider_t*` for `ctx`: either the one you supplied through `cfg.serialization`, or the compiled-in default. Returns `NULL` if `ctx` is `NULL` or uninitialized.

## Embedded and no-heap targets

Three symbols govern how `pubnub_config_t` and context lifecycle behave on memory-constrained targets:

* **PUBNUB_CFG_NO_HEAP**. A compile-time flag. When `1`, `pubnub_create()` and `pubnub_destroy()` are removed from the compiled API entirely, and only `pubnub_init()`/`pubnub_deinit()` on caller-provided memory remain. The `embedded` CMake profile forces this flag on. `full` and `minimal` force it off. See [Environment Setup](https://www.pubnub.com/docs/sdks/c/environment-setup.md) for the full profile comparison.
* **pubnub_context_size()**. A runtime function that returns the exact number of bytes `pubnub_init` needs at the memory you provide. Always check `sizeof(your_buffer) >= pubnub_context_size()` before calling `pubnub_init`, as shown in [Initialization](#initialization).
* **PUBNUB_CONTEXT_SIZE**. A compile-time upper-bound macro for cases where the buffer size must be known at compile time, such as `static` or global storage. It is generated as part of the build and may differ from the exact value `pubnub_context_size()` reports at runtime. Always guard with the runtime check even when sizing a buffer with this macro.
* **PUBNUB_ALIGNAS(type)**. A portability macro from `pubnub/pubnub_compat.h` that you must apply to any statically or globally declared context buffer (for example, `PUBNUB_ALIGNAS(max_align_t) uint8_t buf[PUBNUB_CONTEXT_SIZE]`). Without it, the buffer's natural alignment (`1` for a `uint8_t[]`) is incompatible with `pubnub_context_t`, and the cast in `(pubnub_context_t*)buf` is undefined behavior. This has been observed to fault on strictly-aligned targets such as Cortex-M0.

Compile-time tunables for buffer sizes, request concurrency limits, and the rest of the `PUBNUB_CFG_*` family are set through CMake, not through `pubnub_config_t`. See [Environment Setup](https://www.pubnub.com/docs/sdks/c/environment-setup.md) for the full tunable reference and build profiles.

## Terms in this document

* **PubNub** - PubNub is a real-time messaging platform that provides APIs and SDKs for building scalable applications. It handles the complex infrastructure of real-time communication, including: Message delivery and persistence, Presence detection, Access control, Push notifications, File sharing, Serverless processing with Functions and Events & Actions, Analytics and monitoring with BizOps Workspace, AI-powered insights with Illuminate.

Last updated at: 2026-09-24T12:25:41.000Z
