RWI Protocol Reference
The RWI (Real-time WebSocket Interface) is RustPBX’s control plane for external software: a command/event protocol over a single WebSocket, plus an HTTP webhook bridge for non-interactive consumers. This page is the reference; the webhook page covers delivery configuration.
1. Transport & Authentication
| Aspect | Detail |
|---|---|
| Protocol | WebSocket (WSS in production) |
| Direction | Full duplex — commands out, events back on one ordered channel |
| Auth | Console session, agent token, or deployment-configured API token |
| Scope | A socket connection represents one authenticated principal |
One socket per client is the intended pattern. Events for a principal’s scope arrive automatically; no polling and no per-call subscription is required.
2. Event Model
Every call-scoped event carries a root call identity block so child legs collapse into a logical call:
| Field | Meaning |
|---|---|
call_id | The leg this event concerns |
session_id | Root session of the logical call (equals call_id for the root leg) |
direction | inbound / outbound |
src_ip / client_ip | Attribution for multi-tenant and security filtering |
Event categories
| Category | Examples |
|---|---|
| Call lifecycle | call_created, call_ringing, call_answered, call_hangup |
| Call errors | unified call_error with catalog reference |
| Media | call_early_media, recording start/stop, play completion |
| DTMF / IVR | dtmf, ivr_step_trace, flow resume |
| Queue / ACD | queue_joined, queue_agent_connected, overflow_joined, dequeued |
| Agent | state transitions, wrap-up start/end, break changes |
| Session data | user_data updates, call variable changes |
| Cluster | session owner changes, peer liveness |
Example event
{
"event": "call_answered",
"call_id": "d3f0-…@10.0.0.1",
"session_id": "d3f0-…@10.0.0.1",
"direction": "inbound",
"caller": "8613800001001",
"callee": "1001",
"agent_id": "demo-agent-alice",
"queue_id": "demo-support-tier1",
"src_ip": "203.0.113.20",
"client_ip": "198.51.100.7",
"timestamp": "2026-10-04T09:12:03.482Z"
}
session_id equals call_id on the root leg; child legs (queue dispatch, transfer) carry the same session_id, which is what lets a consumer group them.
Ordering & delivery
- Events for a session are ordered as observed by the owning node.
- Webhook delivery is at-least-once with an idempotency key stable across retries; consumers dedupe on it.
- The
rwi_event_queue_latency_secondshistogram (opt-in) tracks gateway→handler queueing latency.
3. Command Model
Commands are JSON messages with a command field. Capabilities mirror the platform:
| Group | Commands |
|---|---|
| Call control | originate, answer, hangup, bridge, transfer (blind/attended), hold, resume |
| Media | play (with side_only), send_dtmf (rfc4733 / SIP INFO), app.stop |
| Session data | set_userdata / get_userdata, set_var / get_var |
| Queue | enqueue / dequeue, agent assignment, priority updates |
| Conference | create room, add/remove participant, end room |
REST equivalents
Interactive commands have REST twins under /api for scripting:
| Operation | Endpoint |
|---|---|
| List active calls | GET /api/calls/active |
| Inspect a session | GET /api/calls/active/{session_id} |
| Send a command | POST /api/calls/active/{session_id}/commands |
| Read/write user data | GET / PUT /api/calls/active/{session_id}/userdata |
| Force hangup | GET /ami/v1/hangup/{id} |
Example command
{
"command": "transfer",
"session_id": "d3f0-…@10.0.0.1",
"type": "attended",
"target": "1002",
"request_id": "req-7f31"
}
Responses echo request_id; results also arrive as events on the same socket, so a client can correlate command → outcome without polling.
4. Session User Data
User data is application state attached to a session — CRM ids, campaign tags, agent context. Rules:
- Set at any time; readable by any node
- Replicated to peers
- Inherited by transfer sub-sessions and queue dispatches
- Reachable from the RWI socket and the REST endpoints above
- On the wire, per-key
X-<key>SIP headers carry a flattened view to the calling/called legs (the aggregatedX-User-Dataform is deprecated)
5. Cluster Behavior
In a cluster, the connected node may not own the call. The protocol hides this:
| Behavior | Detail |
|---|---|
| Owner routing | Commands are forwarded to the owning node transparently |
| Envelope | /cluster/session_op (terminal node applies locally) |
| Session ops | set_userdata / set_var are owner-routed similarly |
| Ownership queries | GET /cluster/session_owner/{call_id} |
| Cluster listing | GET /cluster/list_calls |
| Failure | A failed owner marks its sessions offline instead of ghosting them |
6. Webhook Bridge
For consumers that don’t hold a socket, the same events flow over HTTP:
[rwi_webhook]
url = "https://your-server.example.com/api/rwi/events"
events = ["call_hangup", "call_error", "queue_agent_connected"]
retries = 3
- Dedicated worker threads; a slow receiver never stalls calls
- Retry with exponential backoff for transport errors, 5xx, 429
- Idempotency key identical across attempts; body byte-identical
- Hot-reloadable configuration
7. Integration Pattern
- Connect the socket; authenticate; subscribe implicitly to your scope
- Maintain a local map keyed by
session_id - Attach business context via
set_userdataat answer - Drive control from the same socket; use REST for out-of-band jobs
- Route passive consumers to webhooks; dedupe on the idempotency key
- In clusters, rely on owner routing — never assume the local node owns a call
See also: RWI Events & Webhooks and the Extensibility guide.