The RWI in Practice: One Socket for Events and Control
The RWI (Real-time WebSocket Interface) is RustPBX’s control plane for external software: a command/event protocol over a single WebSocket that a CTI panel, CRM, or AI agent can hold open for the life of a shift. This is the practical guide — what the protocol looks like, what arrives on it, and the details that matter in production.

Why One Socket
The alternative architecture — REST polling + a separate webhook receiver + a database for state — has three failure modes: latency, duplication, and drift. A single duplex socket solves all three: commands go out, events come back on the same ordered channel, and there’s no polling loop to tune.
Events: What Arrives
The event bus mirrors the platform. Broad categories:
| Category | Examples |
|---|---|
| Call lifecycle | call_created, call_ringing, call_answered, call_hangup, call_error |
| Call detail | DTMF, hold/resume, transfer, recording state, ivr step trace |
| Queue / ACD | enqueue, agent_connected, overflow_joined, dequeue |
| Agent | state transitions, wrap-up start/end, break changes |
| Session data | user-data changes, session var updates |
Two protocol qualities make these usable:
- Root call identity on every call-scoped event. Every event carries the root session id, so a panel can group child legs (queue dispatch, transfer) into one logical call without guessing.
- Attribution on every event.
src_ip/client_ipare stamped, which is what lets a multi-tenant CTI panel decide what a given operator is allowed to see.
Commands: What You Can Do
Commands mirror the platform’s capabilities:
- Call control — originate, answer, hangup, bridge, transfer (blind/attended), hold/resume, mute
- Media — play prompts (including
side_only), send DTMF (rfc4733 or SIP INFO), stop an app - Queue operations — enqueue/dequeue, agent assignment, priority updates
- Conference control — create rooms, add/remove participants
- Session data — set/get user data and call variables
A call-control command’s REST twin exists for scripted use: POST /api/calls/active/{id}/commands.
Cluster Behavior: Owner-Routed
In a cluster, the socket you’re connected to may not own the call. That’s handled for you: commands are forwarded to the owning node (/cluster/session_op under the hood), and the terminal node applies them locally. From the client’s perspective, “transfer that call” works from any node, and the response/events come back on the same socket.
Session Data That Survives
Session user data set over RWI replicates to peers and is inherited by transfer sub-sessions — so the CRM id your panel attached at answer is still there after two transfers and a node failover.
Delivery Guarantees for Webhooks
Not every consumer wants a socket. The webhook runtime bridges the same events over HTTP with:
- Dedicated worker threads (a slow receiver never stalls calls)
- Retries with exponential backoff for 5xx/429/transport errors
- An idempotency key that stays identical across retry attempts
- Optional queue-latency metrics and hot-reload of the webhook config
Socket for interactive control, webhook for fire-and-forget consumers — same events, appropriate transport.
Practical Integration Pattern
- Connect the socket, subscribe to the event classes you need
- Keep a local map keyed by root session id
- Attach CRM ids via user-data on answer
- Drive control from the same socket; use REST for out-of-band scripting
- Route non-interactive consumers to webhooks
Guides: RWI Events & Webhooks and Extensibility.