This document describes the Verto protocol, a JSON-RPC 2.0 protocol used by FreeSWITCH's [mod_verto] to provide WebRTC signaling and event handling over Secure WebSockets (WSS).
- Protocol: JSON-RPC 2.0
- Transport: Secure WebSockets (WSS)
- Endpoint: mod_verto acts as the server endpoint.
Request:
{
"jsonrpc": "2.0",
"method": "login",
"params": {
"login": "1000@example.com",
"passwd": "password123",
"sessid": "optional-uuid-for-session-recovery"
},
"id": 1
}Response (Success):
{
"jsonrpc": "2.0",
"result": {
"message": "logged in",
"sessid": "abc12345-uuid"
},
"id": 1
}verto.attach(Server → Client): Re-establishes call state for active callsverto.clientReady(Server → Client): Session re-attachment completeverto.punt(Server → old Client): Notifies the old connection that a new one claimed the session
Request:
{
"jsonrpc": "2.0",
"method": "verto.invite",
"params": {
"dialogParams": {
"callID": "uuid-generated-by-client",
"destination_number": "9999",
"caller_id_name": "John Doe",
"caller_id_number": "1000",
"remote_caller_id_name": "Conference",
"remote_caller_id_number": "9999"
},
"sdp": "v=0\r\no=- 12345 IN IP4 ..."
},
"id": 2
}Request:
{
"jsonrpc": "2.0",
"method": "verto.answer",
"params": {
"dialogParams": {
"callID": "call-uuid-from-invite"
},
"sdp": "v=0\r\no=- 12345 IN IP4 ..."
},
"id": 3
}Request:
{
"jsonrpc": "2.0",
"method": "verto.bye",
"params": {
"dialogParams": {
"callID": "call-uuid"
},
"cause": "NORMAL_CLEARING"
},
"id": 4
}All verto.modify requests require dialogParams.callID and action in params.
Response Structure (all actions):
{
"jsonrpc": "2.0",
"result": {
"callID": "call-uuid",
"action": "hold",
"holdState": "held"
},
"id": 5
}| Response Field | Description |
|---|---|
callID |
Echo of the call UUID |
action |
Echo of the action performed |
holdState |
Current hold state: "held" or "active" |
sdp |
(updateMedia only) The new local SDP |
message |
Error message if action failed |
Hold:
{
"jsonrpc": "2.0",
"method": "verto.modify",
"params": {
"dialogParams": { "callID": "call-uuid" },
"action": "hold"
},
"id": 5
}Unhold:
{
"jsonrpc": "2.0",
"method": "verto.modify",
"params": {
"dialogParams": { "callID": "call-uuid" },
"action": "unhold"
},
"id": 6
}Toggle Hold:
{
"jsonrpc": "2.0",
"method": "verto.modify",
"params": {
"dialogParams": { "callID": "call-uuid" },
"action": "toggleHold"
},
"id": 7
}Transfer:
{
"jsonrpc": "2.0",
"method": "verto.modify",
"params": {
"dialogParams": { "callID": "call-uuid" },
"action": "transfer",
"destination": "1001"
},
"id": 8
}Replace/Bridge (Attended Transfer):
{
"jsonrpc": "2.0",
"method": "verto.modify",
"params": {
"dialogParams": { "callID": "call-uuid" },
"action": "replace",
"replaceCallID": "other-call-uuid"
},
"id": 9
}Video Refresh (keyframe request):
{
"jsonrpc": "2.0",
"method": "verto.modify",
"params": {
"dialogParams": { "callID": "call-uuid" },
"action": "videoRefresh"
},
"id": 10
}Update Media (re-INVITE with new SDP):
{
"jsonrpc": "2.0",
"method": "verto.modify",
"params": {
"dialogParams": { "callID": "call-uuid" },
"action": "updateMedia",
"sdp": "v=0\r\no=- 12345 IN IP4 ..."
},
"id": 11
}Response includes new local SDP:
{
"jsonrpc": "2.0",
"result": {
"callID": "call-uuid",
"action": "updateMedia",
"sdp": "v=0\r\no=- 67890 IN IP4 ...",
"holdState": "active"
},
"id": 11
}Note
updateMedia can only be called on an answered call. Calling it before the call is answered will result in the call being hung up.
DTMF:
{
"jsonrpc": "2.0",
"method": "verto.info",
"params": {
"dialogParams": { "callID": "call-uuid" },
"dtmf": "1234#"
},
"id": 11
}Chat Message:
{
"jsonrpc": "2.0",
"method": "verto.info",
"params": {
"msg": {
"to": "1001@example.com",
"from": "1000@example.com",
"body": "Hello!"
}
},
"id": 12
}Real-Time Text (RTT):
{
"jsonrpc": "2.0",
"method": "verto.info",
"params": {
"dialogParams": { "callID": "call-uuid" },
"txt": {
"code": 65,
"chars": "A"
},
"noDialogParams": true
},
"id": 13
}Request:
{
"jsonrpc": "2.0",
"method": "verto.subscribe",
"params": {
"eventChannel": ["conference.9999@example.com", "presence"]
},
"id": 14
}Response:
{
"jsonrpc": "2.0",
"result": {
"subscribedChannels": ["conference.9999@example.com"],
"unauthorizedChannels": ["presence"]
},
"id": 14
}Request:
{
"jsonrpc": "2.0",
"method": "verto.unsubscribe",
"params": {
"eventChannel": "conference.9999@example.com"
},
"id": 15
}Request:
{
"jsonrpc": "2.0",
"method": "verto.broadcast",
"params": {
"eventChannel": "conference.9999@example.com",
"data": {
"action": "chat",
"message": "Hello everyone!"
}
},
"id": 16
}Server pushes events to subscribed clients:
{
"jsonrpc": "2.0",
"method": "verto.event",
"params": {
"eventChannel": "conference.9999@example.com",
"pvtData": { "action": "conference-liveArray-join" },
"data": { ... }
}
}Conference participant management uses a "liveArray" mechanism. When joining a conference, the server sends private data containing channel information, then pushes real-time participant updates.
When a call joins a conference, the server sends verto.event with pvtData:
{
"jsonrpc": "2.0",
"method": "verto.event",
"params": {
"eventType": "channelPvtData",
"pvtData": {
"laChannel": "conference-liveArray.3500@domain.com",
"laName": "3500",
"role": "moderator",
"chatChannel": "conference-chat.3500@domain.com",
"infoChannel": "conference-info.3500@domain.com"
}
}
}| Field | Description |
|---|---|
laChannel |
LiveArray channel to subscribe to for participant updates |
laName |
Conference name/number |
role |
User role: "moderator" or "participant" |
chatChannel |
Channel for chat messages |
infoChannel |
Channel for conference info updates |
After receiving pvtData, subscribe to the liveArray channel:
{
"jsonrpc": "2.0",
"method": "verto.subscribe",
"params": {
"eventChannel": "conference-liveArray.3500@domain.com",
"subParams": {}
},
"id": 20
}Sent immediately after subscription with all current participants:
{
"jsonrpc": "2.0",
"method": "verto.event",
"params": {
"eventChannel": "conference-liveArray.3500@domain.com",
"data": {
"action": "bootObj",
"data": [
["1001", 0, [{"participantId": "1001", "participantName": "Alice", "audio": {"muted": false, "talking": false}, "video": {"muted": false}}]],
["1002", 1, [{"participantId": "1002", "participantName": "Bob", "audio": {"muted": true, "talking": false}, "video": {"muted": true}}]]
]
}
}
}{
"jsonrpc": "2.0",
"method": "verto.event",
"params": {
"eventChannel": "conference-liveArray.3500@domain.com",
"data": {
"action": "add",
"hashKey": "1003",
"arrIndex": 2,
"data": [{
"participantId": "1003",
"participantName": "Charlie",
"audio": { "muted": false, "talking": false },
"video": { "muted": false }
}]
}
}
}{
"jsonrpc": "2.0",
"method": "verto.event",
"params": {
"eventChannel": "conference-liveArray.3500@domain.com",
"data": {
"action": "modify",
"hashKey": "1001",
"arrIndex": 0,
"data": [{
"participantId": "1001",
"participantName": "Alice",
"audio": { "muted": false, "talking": true },
"video": { "muted": false }
}]
}
}
}{
"jsonrpc": "2.0",
"method": "verto.event",
"params": {
"eventChannel": "conference-liveArray.3500@domain.com",
"data": {
"action": "del",
"hashKey": "1002",
"arrIndex": 1
}
}
}| Field | Type | Description |
|---|---|---|
participantId |
string | Unique member ID |
participantName |
string | Display name |
audio.muted |
boolean | Microphone muted |
audio.talking |
boolean | Currently speaking |
video.muted |
boolean | Camera disabled |
Moderators can control the conference via verto.broadcast:
Mute a Member:
{
"jsonrpc": "2.0",
"method": "verto.broadcast",
"params": {
"eventChannel": "conference-liveArray.3500@domain.com",
"data": {
"action": "mute",
"memberId": "1002"
}
},
"id": 21
}Kick a Member:
{
"jsonrpc": "2.0",
"method": "verto.broadcast",
"params": {
"eventChannel": "conference-liveArray.3500@domain.com",
"data": {
"action": "kick",
"memberId": "1002"
}
},
"id": 22
}Send Chat Message:
{
"jsonrpc": "2.0",
"method": "verto.broadcast",
"params": {
"eventChannel": "conference-chat.3500@domain.com",
"data": {
"action": "chat",
"from": "Alice",
"message": "Hello everyone!"
}
},
"id": 23
}Request:
{
"jsonrpc": "2.0",
"method": "verto.ping",
"id": 99
}Response:
{
"jsonrpc": "2.0",
"result": { "message": "PONG" },
"id": 99
}All call-related methods include dialogParams:
| Field | Description |
|---|---|
callID |
Unique UUID for the call (client-generated for outbound) |
destination_number |
Number to dial |
caller_id_name |
Caller's display name |
caller_id_number |
Caller's number |
callee_id_name |
Callee's display name (for answered calls) |
callee_id_number |
Callee's number |
remote_caller_id_name |
Remote party name |
remote_caller_id_number |
Remote party number |
login |
User login string |
| Code | Description |
|---|---|
-32600 |
Invalid Request |
-32601 |
Invalid Method |
-32602 |
Permission Denied |
-32000 |
Auth Required - must login first |
-32001 |
Auth Failed - invalid credentials |
-32002 |
Session Error |
Verto supports session recovery to handle network disconnections or page reloads without dropping active calls.
- Disconnection: WebSocket closes. Server marks session as "detached" and starts a timeout timer.
- Reconnection: Client reconnects and sends
loginwith the originalsessid. - Server Initiation: Server detects the reattached session has detached calls and sends
verto.attachREQUEST to the client. - Client Response: Client handles the request, creates a new PeerConnection, and sends a
verto.attachMETHOD back to server with the Answer SDP. - Restoration: Server processes the answer, clears ICE, and call media resumes.
The server invites the client to reattach to a specific call.
{
"jsonrpc": "2.0",
"method": "verto.attach",
"id": 1234,
"params": {
"callID": "e1f19988-4fbd-4b81-842d-24239a854587",
"sdp": "v=0\r\no=FreeSWITCH ... (Server's Offer)",
"display_name": "Alice",
"display_number": "1001",
"callee_id_name": "Bob",
"callee_id_number": "1002"
}
}The client accepts the offer, generates an answer, and sends it back.
{
"jsonrpc": "2.0",
"method": "verto.attach",
"id": 5678,
"params": {
"dialogParams": {
"callID": "e1f19988-4fbd-4b81-842d-24239a854587",
"destination_number": "1002",
"caller_id_name": "Alice",
"caller_id_number": "1001"
},
"sdp": "v=0\r\no=- ... (Client's New Answer)"
}
}