RWI 事件与 Webhook

RWI(Real-time WebSocket Interface)是 RustPBX 的实时事件总线。除了 WebSocket 实时推送,还支持 HTTP Webhook 将事件转发到外部系统。

1. RWI 架构

RustPBX 内部事件
    │
    ▼
RWI Gateway
    │
    ├── WebSocket 连接(实时控制)
    │     ws://host:8080/rwi/ws
    │     Sub-protocol: rwi-v1
    │
    └── HTTP Webhook(事件转发)
          POST https://your-server/webhook

2. Webhook 配置

在 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"

# 可选:事件类型过滤(留空或不设则转发全部)
events = [
  "call_hangup",
  "call_answered",
  "queue_joined",
  "queue_agent_connected",
  "agent_state_changed",
  "dtmf",
  "ivr_step_trace",
  "record_stopped"
]

2.1 配置参数

参数说明默认值
urlWebhook 接收端点(必需)-
timeout_msHTTP 请求超时5000
headers自定义 HTTP Headers无
events事件类型白名单(空=全部)全部
retries推送失败后的重试次数(传输错误、5xx 或 429)。指数退避——200ms 起步、逐次翻倍。重试期间幂等键保持一致,接收端可安全去重0(单次尝试)
track_queue_latency在 rwi_event_queue_latency_seconds 直方图中记录网关→处理器的排队延迟false

2.2 投递运行时(0.5+)

Webhook 运行在与 SIP 热路径解耦的专用运行时上:

  • 可配置的 worker 线程(rwi_webhook_worker_threads)从有界队列取事件,慢速接收端不会拖垮呼叫处理。
  • 失败投递(5xx/429/传输错误)按退避策略重试至多 retries 次;每次尝试携带相同的幂等键和逐字节一致的请求体。
  • 每次请求都会记录 URL、状态码和延迟,用于结构化日志,并可选排队延迟指标。
  • [rwi_webhook] 段支持热更新——修改 URL、headers 或事件过滤无需重启即可生效。

3. Webhook Payload 格式

每次 POST 请求体:

{
  "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
  }
}
字段说明
rwi协议版本号
sequence单调递增序列号
timestampISO 8601 时间戳
call_id关联的 SIP Call-ID
event_type事件类型(snake_case 字符串)
event事件详细数据(因类型而异)

4. 去重机制

Webhook 内置去重:

  • 环形缓存 4096 条 (call_id, sequence) 记录
  • 相同 (call_id, sequence) 的事件不会重复发送
  • 适用于同一事件从多个 call owner 转发的场景

5. 完整事件类型列表

5.1 呼叫生命周期

event_type说明
call_incoming来电到达
call_ringing振铃
call_early_media早期媒体
call_answered应答
call_bridged桥接成功
call_unbridged桥接断开
call_hangup挂断
call_no_answer无应答
call_busy忙碌
call_transferred转接完成
call_transfer_accepted转接接受
call_transfer_failed转接失败
call_ownership_changed通话归属变更
call_metadata_updated通话元数据更新

5.2 媒体

event_type说明
media_hold_started保持开始
media_hold_stopped保持结束
media_play_started播放开始
media_play_finished播放完成
media_stream_started流媒体开始
media_stream_stopped流媒体结束
dtmfDTMF 按键
dtmf_collectedDTMF 收集完成
dtmf_collection_timeoutDTMF 收集超时

5.3 录音

event_type说明
record_started录音开始
record_paused录音暂停
record_resumed录音恢复
record_stopped录音停止
record_failed录音失败
recording_metadata_available录音元数据就绪

5.4 队列/ACD

event_type说明
queue_joined加入队列
queue_position_changed排队位置变化
queue_candidates_found找到候选坐席
queue_agent_offered向坐席分配
queue_agent_ringing坐席振铃
queue_agent_no_answer坐席未接
queue_agent_rejected坐席拒绝
queue_agent_connected坐席接通
queue_left离开队列
queue_wait_timeout等待超时
queue_overflowed队列溢出
queue_voicemail_redirected转语音信箱
queue_fallback_executed执行回退
queue_alert队列告警

5.5 坐席

event_type说明
agent_state_changed坐席状态变化

5.6 分机

event_type说明
dn_state_changed分机状态变化
dn_registered分机注册
dn_unregistered分机注销

5.7 IVR

event_type说明
ivr_node_entered进入 IVR 节点
ivr_node_exited离开 IVR 节点
ivr_flow_transitionedIVR 流程转换
ivr_flow_completedIVR 流程完成
ivr_step_traceStep IVR 跟踪

5.8 会议

event_type说明
conference_created会议创建
conference_member_joined成员加入
conference_member_left成员离开
conference_member_muted成员静音
conference_member_unmuted成员取消静音
conference_destroyed会议销毁
conference_error会议错误
conference_consult_dialing咨询拨号中
conference_consult_connected咨询接通
conference_merge_requested合并请求
conference_merged合并完成
conference_merge_failed合并失败
conference_seat_replace_started席位替换开始
conference_seat_replace_succeeded席位替换成功
conference_seat_replace_failed席位替换失败

5.9 监控(Supervisor)

event_type说明
supervisor_listen_started监听开始
supervisor_whisper_started密语开始
supervisor_barge_started强插开始
supervisor_takeover_started接管开始
supervisor_mode_stopped监控模式停止

5.10 并行外呼

event_type说明
parallel_originate_started并行外呼开始
parallel_originate_leg_ringing外呼线路振铃
parallel_originate_winner获胜线路
parallel_originate_leg_cancelled线路取消
parallel_originate_completed并行外呼完成
parallel_originate_failed并行外呼失败

5.11 SIP 信令

event_type说明
sip_message_received收到 SIP MESSAGE
sip_notify_received收到 SIP NOTIFY

5.12 会话恢复

event_type说明
session_resumedWebSocket 重连后恢复会话

6. Webhook 接收端示例

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 实时连接(补充)

除了 Webhook,也可通过 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 会话恢复

WebSocket 断连后重连,可通过 last_sequence 参数恢复:

  • Gateway 缓存最近 1000 条事件
  • 60 秒保留期
  • 单调递增 sequence 号

8. 典型应用场景

场景订阅事件说明
CDR 实时推送call_hangup实时获取通话结束事件
呼叫中心大屏queue_*, agent_state_changed实时更新排队和坐席状态
IVR 追踪ivr_step_trace记录每一步 IVR 决策
录音完成通知record_stopped + recording_metadata_available录音完成后立即处理
告警系统queue_alert, queue_overflowed队列异常告警
CRM 集成call_answered弹屏显示客户信息