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

AspectDetail
ProtocolWebSocket (WSS in production)
DirectionFull duplex — commands out, events back on one ordered channel
AuthConsole session, agent token, or deployment-configured API token
ScopeA 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:

FieldMeaning
call_idThe leg this event concerns
session_idRoot session of the logical call (equals call_id for the root leg)
directioninbound / outbound
src_ip / client_ipAttribution for multi-tenant and security filtering

Event categories

CategoryExamples
Call lifecyclecall_created, call_ringing, call_answered, call_hangup
Call errorsunified call_error with catalog reference
Mediacall_early_media, recording start/stop, play completion
DTMF / IVRdtmf, ivr_step_trace, flow resume
Queue / ACDqueue_joined, queue_agent_connected, overflow_joined, dequeued
Agentstate transitions, wrap-up start/end, break changes
Session datauser_data updates, call variable changes
Clustersession 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_seconds histogram (opt-in) tracks gateway→handler queueing latency.

3. Command Model

Commands are JSON messages with a command field. Capabilities mirror the platform:

GroupCommands
Call controloriginate, answer, hangup, bridge, transfer (blind/attended), hold, resume
Mediaplay (with side_only), send_dtmf (rfc4733 / SIP INFO), app.stop
Session dataset_userdata / get_userdata, set_var / get_var
Queueenqueue / dequeue, agent assignment, priority updates
Conferencecreate room, add/remove participant, end room

REST equivalents

Interactive commands have REST twins under /api for scripting:

OperationEndpoint
List active callsGET /api/calls/active
Inspect a sessionGET /api/calls/active/{session_id}
Send a commandPOST /api/calls/active/{session_id}/commands
Read/write user dataGET / PUT /api/calls/active/{session_id}/userdata
Force hangupGET /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 aggregated X-User-Data form is deprecated)

5. Cluster Behavior

In a cluster, the connected node may not own the call. The protocol hides this:

BehaviorDetail
Owner routingCommands are forwarded to the owning node transparently
Envelope/cluster/session_op (terminal node applies locally)
Session opsset_userdata / set_var are owner-routed similarly
Ownership queriesGET /cluster/session_owner/{call_id}
Cluster listingGET /cluster/list_calls
FailureA 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

  1. Connect the socket; authenticate; subscribe implicitly to your scope
  2. Maintain a local map keyed by session_id
  3. Attach business context via set_userdata at answer
  4. Drive control from the same socket; use REST for out-of-band jobs
  5. Route passive consumers to webhooks; dedupe on the idempotency key
  6. In clusters, rely on owner routing — never assume the local node owns a call

See also: RWI Events & Webhooks and the Extensibility guide.