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.
| Provider | What it is | Account needed |
|---|---|---|
| Polling | One shared timer per page, asking one small question per tick | None |
| Pusher | A hosted WebSocket service | Pusher |
| PubNub | A hosted WebSocket service | PubNub |
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
| Setting | What it does |
|---|---|
| Provider | Polling, Pusher or PubNub |
| Polling interval | Seconds 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 / cluster | Credentials. The secret is write-only and never returned to the browser |
| Pusher encryption master key | Optional. Enables encrypted channels for consumers that ask for them |
| PubNub publish / subscribe / secret key | Credentials, 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".