RWI Events & Webhooks

RWI (Real-time WebSocket Interface) is RustPBX’s real-time event bus. In addition to WebSocket real-time push, it also supports HTTP Webhooks to forward events to external systems.

1. RWI Architecture

RustPBX internal events
    │
    ▼
RWI Gateway
    │
    ├── WebSocket connection (real-time control)
    │     ws://host:8080/rwi/ws
    │     Sub-protocol: rwi-v1
    │
    └── HTTP Webhook (event forwarding)
          POST https://your-server/webhook

2. Webhook Configuration

In config.toml:

[rwi_webhook]
url = "https://your-server.example.com/api/rwi/events"
timeout_ms = 5000

[rwi_webhook.headers]
Authorization = "Bearer my-webhook-secret"
X-Source = "rustpbx"

# Optional: event type filtering (leave empty or omit to forward all)
events = [
  "call_hangup",
  "call_answered",
  "queue_joined",
  "queue_agent_connected",
  "agent_state_changed",
  "dtmf",
  "ivr_step_trace",
  "record_stopped"
]

2.1 Configuration Parameters

ParameterDescriptionDefault
urlWebhook receiver endpoint (required)-
timeout_msHTTP request timeout5000
headersCustom HTTP HeadersNone
eventsEvent type allowlist (empty = all)All

3. Webhook Payload Format

Each POST request body:

{
  "rwi": "1.0",
  "sequence": 12345,
  "timestamp": "2026-05-26T10:30:00Z",
  "call_id": "abc123@10.0.0.1",
  "event_type": "call_hangup",
  "event": {
    "call_id": "abc123@10.0.0.1",
    "reason": "normal",
    "duration": 120,
    "sip_code": 200
  }
}
FieldDescription
rwiProtocol version
sequenceMonotonically increasing sequence number
timestampISO 8601 timestamp
call_idAssociated SIP Call-ID
event_typeEvent type (snake_case string)
eventEvent detail data (varies by type)

4. Deduplication

Webhooks have built-in deduplication:

  • Ring buffer of 4096 (call_id, sequence) records
  • Events with the same (call_id, sequence) are not sent twice
  • Handles scenarios where the same event is forwarded from multiple call owners

5. Complete Event Type List

5.1 Call Lifecycle

event_typeDescription
call_incomingIncoming call arrived
call_ringingRinging
call_early_mediaEarly media
call_answeredAnswered
call_bridgedBridge established
call_unbridgedBridge disconnected
call_hangupHung up
call_no_answerNo answer
call_busyBusy
call_transferredTransfer completed
call_transfer_acceptedTransfer accepted
call_transfer_failedTransfer failed
call_ownership_changedCall ownership changed
call_metadata_updatedCall metadata updated

5.2 Media

event_typeDescription
media_hold_startedHold started
media_hold_stoppedHold stopped
media_play_startedPlayback started
media_play_finishedPlayback finished
media_stream_startedMedia stream started
media_stream_stoppedMedia stream stopped
dtmfDTMF key press
dtmf_collectedDTMF collection complete
dtmf_collection_timeoutDTMF collection timeout

5.3 Recording

event_typeDescription
record_startedRecording started
record_pausedRecording paused
record_resumedRecording resumed
record_stoppedRecording stopped
record_failedRecording failed
recording_metadata_availableRecording metadata ready

5.4 Queue / ACD

event_typeDescription
queue_joinedJoined queue
queue_position_changedQueue position changed
queue_candidates_foundCandidate agents found
queue_agent_offeredOffered to agent
queue_agent_ringingAgent ringing
queue_agent_no_answerAgent did not answer
queue_agent_rejectedAgent rejected
queue_agent_connectedAgent connected
queue_leftLeft queue
queue_wait_timeoutWait timeout
queue_overflowedQueue overflowed
queue_voicemail_redirectedRedirected to voicemail
queue_fallback_executedFallback executed
queue_alertQueue alert

5.5 Agents

event_typeDescription
agent_state_changedAgent state changed

5.6 Extensions

event_typeDescription
dn_state_changedExtension state changed
dn_registeredExtension registered
dn_unregisteredExtension unregistered

5.7 IVR

event_typeDescription
ivr_node_enteredEntered IVR node
ivr_node_exitedExited IVR node
ivr_flow_transitionedIVR flow transitioned
ivr_flow_completedIVR flow completed
ivr_step_traceStep IVR trace

5.8 Conference

event_typeDescription
conference_createdConference created
conference_member_joinedMember joined
conference_member_leftMember left
conference_member_mutedMember muted
conference_member_unmutedMember unmuted
conference_destroyedConference destroyed
conference_errorConference error
conference_consult_dialingConsult dialing
conference_consult_connectedConsult connected
conference_merge_requestedMerge requested
conference_mergedMerge completed
conference_merge_failedMerge failed
conference_seat_replace_startedSeat replace started
conference_seat_replace_succeededSeat replace succeeded
conference_seat_replace_failedSeat replace failed

5.9 Supervisor

event_typeDescription
supervisor_listen_startedListen started
supervisor_whisper_startedWhisper started
supervisor_barge_startedBarge started
supervisor_takeover_startedTakeover started
supervisor_mode_stoppedSupervisor mode stopped

5.10 Parallel Originate

event_typeDescription
parallel_originate_startedParallel originate started
parallel_originate_leg_ringingOriginate leg ringing
parallel_originate_winnerWinning leg
parallel_originate_leg_cancelledLeg cancelled
parallel_originate_completedParallel originate completed
parallel_originate_failedParallel originate failed

5.11 SIP Signaling

event_typeDescription
sip_message_receivedSIP MESSAGE received
sip_notify_receivedSIP NOTIFY received

5.12 Session Recovery

event_typeDescription
session_resumedSession resumed after WebSocket reconnect

6. Webhook Receiver Examples

6.1 Python Flask

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/api/rwi/events", methods=["POST"])
def handle_rwi_event():
    payload = request.json
    event_type = payload.get("event_type")
    call_id = payload.get("call_id")
    event = payload.get("event", {})

    if event_type == "call_hangup":
        print(f"Call {call_id} ended, duration={event.get('duration')}s")
    elif event_type == "queue_agent_connected":
        print(f"Agent {event.get('agent_id')} connected to call {call_id}")
    elif event_type == "agent_state_changed":
        print(f"Agent {event.get('agent_id')} -> {event.get('state')}")

    return jsonify({"status": "ok"})

6.2 Node.js Express

app.post("/api/rwi/events", express.json(), (req, res) => {
  const { event_type, call_id, event, sequence } = req.body;
  console.log(`[${sequence}] ${event_type} on ${call_id}`);
  res.json({ status: "ok" });
});

7. WebSocket Real-time Connection (Supplement)

In addition to Webhooks, you can also subscribe to events in real-time via WebSocket:

const ws = new WebSocket("ws://rustpbx:8080/rwi/ws", "rwi-v1");
ws.onmessage = (e) => {
  const data = JSON.parse(e.data);
  console.log(data.event_type, data.call_id);
};

7.1 Session Recovery

After a WebSocket disconnect and reconnect, sessions can be recovered via the last_sequence parameter:

  • Gateway caches the latest 1000 events
  • 60-second retention period
  • Monotonically increasing sequence numbers

8. Typical Use Cases

ScenarioSubscribed EventsDescription
Real-time CDR pushcall_hangupGet call end events in real-time
Call center wallboardqueue_*, agent_state_changedReal-time queue and agent status updates
IVR tracingivr_step_traceLog each IVR decision step
Recording completionrecord_stopped + recording_metadata_availableProcess immediately after recording
Alert systemqueue_alert, queue_overflowedQueue anomaly alerts
CRM integrationcall_answeredScreen pop with customer info