RustPBX Basics: Three Ways to Extend — HTTP Routing, Webhooks, and the RWI
No PBX stays stock. Sooner or later a call needs a CRM lookup, an event needs to reach another system, or a bot needs to hang up on command. RustPBX’s extension surface has three layers — choose by direction: decisions in, events out, and control both ways.
1. Decisions In: HTTP Dynamic Routing
When call routing depends on an external system — CRM data, real-time pricing, fraud scoring — the HTTP router asks your service on every INVITE:
[proxy.http_router]
url = "https://api.example.com/route"
headers = { Authorization = "Bearer token123" }
fallback_to_static = true
timeout_ms = 300
The payload carries caller, callee, source IP, and SIP headers. Your response picks the action: forward to a trunk (with caller override), reject with a status code, or busy. fallback_to_static = true falls back to config/routes/*.toml when your endpoint is down — a routing outage never becomes a call outage. Invalid URIs in the response fail the call loudly rather than routing somewhere unintended.
For deeper logic, Step IVR hands each call step to your API — same idea, per-action granularity.
2. Events Out: RWI Webhooks
The RWI (Real-time WebSocket Interface) event bus mirrors everything — call lifecycle, queue events, DTMF, IVR traces, agent state — and the webhook runtime forwards selected events over HTTP:
[rwi_webhook]
url = "https://your-server.example.com/api/rwi/events"
events = ["call_hangup", "queue_agent_connected", "call_error"]
retries = 3
Delivery runs on dedicated workers (never the SIP hot path), retries 5xx/429 with exponential backoff, keeps the idempotency key identical across attempts, tracks queue latency in metrics, and hot-reloads configuration. Your receiver gets at-least-once delivery it can dedupe safely.
3. Control Both Ways: The RWI WebSocket
For command-and-control — originate, answer, bridge, transfer, hold, queue operations, conference control — connect to the RWI WebSocket. It’s the same protocol the CC desk and voice agents speak, with root call identity on every event and owner-routed operations in clusters (a command issued on node A reaches the call on node B).

A webhook delivery arrives looking like this (truncated):
{
"event": "call_ended",
"call_id": "d3f0-…",
"session_id": "d3f0-…",
"direction": "outbound",
"status": "answered",
"duration_secs": 187,
"hangup_reason": "caller",
"caller": "1001",
"callee": "+8613800001001"
}
Typical stack: your CTI panel listens for events and issues commands over one socket; your CRM opens popup URLs from the headers (see the screen-pop post); recording and CDR flow back through the call records API.
And the Fourth Way: Addons
When the surface itself must change — new reports, new tables, new console pages — the Addon trait hosts everything from ACME to Contact Center. Commercial addons (wholesale, CC, IVR editor, voicemail) are the proof of concept.
Guides: RWI Events & Webhooks, Step IVR, and JSON-RPC routing.