Realtime

Live updates for every XPAC plugin on your site, over Pusher, PubNub or a shared timer that needs no account at all.

Realtime is the plumbing the rest of the pack uses to keep a screen current. It has no screens of its own beyond a settings page, owns no content, and stores nothing about your site's data. Plugins ask it for a channel; it decides how that channel is delivered.

It ships with three providers, and the default is not a socket.

ProviderWhat it isAccount needed
PollingOne shared timer per page, asking one small question per tickNone
PusherA hosted WebSocket servicePusher
PubNubA hosted WebSocket servicePubNub

Where to find it

XPAC → Realtime. One page: the provider, the interval, and the credentials for whichever provider you chose.

What it does for the plugins that use it

Every consumer — Support, Comments, Community Platform — publishes a tiny signal when something changes and subscribes to a channel to hear about it. Realtime's job is to make "something changed" arrive, and to do it the same way whether the transport is a socket or a timer.

A channel has a name, a visibility (private or presence), an authorizer that runs on your server, and a counter. A subscriber is never told what changed; it is told that something did, and re-reads it over the REST API where permissions are checked again. That is a deliberate constraint rather than an optimisation: when a socket is on, everything published crosses a third party's infrastructure.

Polling is a provider, not a failure

With Polling selected there is no third party in the picture. Each open page runs one timer and asks one batched question per tick — "which of these counters moved?" — capped at fifty channels per request. Six changes inside one interval produce one refresh, not six, because the client reacts to a number going up rather than to individual events.

The interval is a setting with a hard floor of five seconds, enforced on write and again in the browser. Raising it is the lever for a busy site: doubling the interval halves the request rate exactly.

Sizing polling before you rely on it

Aggregate load is the number nobody states. Twenty agents plus a hundred customers with a page open, all at five seconds, is roughly twenty-four authenticated WordPress requests a second. Each consumer's settings screen prints its own figure; read it before you promise an interval to anyone.

A backgrounded tab makes no requests at all. A consumer may opt one channel into a slow background rate — Support does this for the agent inbox — which polls at a quarter of the interval in a hidden window.

Sockets

Choosing Pusher or PubNub adds credentials and changes three things: latency drops from the interval to about a tenth of a second, hidden tabs receive updates, and the timer stops. A socket also unlocks the two features a timer cannot express at all — typing indicators, and "who else is looking at this" — because both describe a moment rather than a state.

Both providers bill by concurrent connection. A channel can be registered as engagement only, which means it stays on the timer until the person actually does something, then upgrades. That is what stops a signed-in visitor who never opens anything from costing a connection all day.

A refused channel is not a lost connection

A provider can refuse one channel on a perfectly healthy socket — an expired page nonce, a permission that changed, a security plugin blocking the authorisation request. Realtime reports channel liveness separately from connection liveness for exactly that case, and a refused channel falls back to the timer rather than sitting silently behind a green badge.

Encryption

Pusher supports end-to-end encrypted channels when a master key is set. Encryption is baked into a channel's name at registration, so it cannot be switched on later without every subscriber moving, and it is unavailable on PubNub unless a global cipher key is configured. Treat it as a hardening option for a specific deployment, not as the reason it is safe to put content on a socket. The consumers in this pack put signals on the wire and nothing else.

Client events

A channel may allow the browser to originate messages, which the provider fans out without your server seeing them. That is how typing indicators work. It is off unless a consumer asks for it, and it needs Client Events enabled in the Pusher dashboard as well; without that, Pusher silently refuses them and those features stay quiet while everything else keeps working.

Settings

SettingWhat it does
ProviderPolling, Pusher or PubNub
Polling intervalSeconds between checks. Floored at five. Applies to every provider — a socket is an upgrade on top of it, not a replacement for it
Pusher app id / key / secret / clusterCredentials. The secret is write-only and never returned to the browser
Pusher encryption master keyOptional. Enables encrypted channels for consumers that ask for them
PubNub publish / subscribe / secret keyCredentials, same write-only handling

For developers

Registering a channel

add_action('xpac_realtime_register_channels', function ($registry) {
    $registry->private('xp-example-thread', '{thread_id}', [
        'authorize' => function (array $params, int $userId): bool {
            return $userId > 0 && my_plugin_can_read((int) $params['thread_id'], $userId);
        },
    ]);
});

A channel parameter is transmitted to the provider and appears in its dashboards and logs, so it must be an id rather than a credential. Authorisation is a server-side callback keyed on the current user, which is why a logged-out visitor holding a signed cookie can still be granted a private channel.

Publishing

apply_filters('xpac_realtime_publish', false, 'xp-example-thread', ['thread_id' => 17], [
    'ev' => 'reply',
    'at' => time(),
]);

Everything Realtime exposes is a filter, attached at plugin load. If Realtime is not installed the filter goes unanswered and returns the default the caller passed, so a consumer needs no class_exists guard anywhere and simply falls back to its own timer.

Payload size

Pusher drops a publish over ten kilobytes and only logs it under WP_DEBUG. Keeping payloads to a signal — an event name, an integer id and a timestamp — puts you three orders of magnitude clear of that cap, which is the difference between "this feature is occasionally flaky for large groups" and "this feature works".

On this page