Interaction model
Session Router sends call events to application hooks. When an HTTP hook
returns a positive response with a non-empty body, the body can contain
a JSON array of verb objects. Each object must include a verb
property. Verbs are parsed and executed in array order.
[
{
"verb": "say",
"text": "Welcome to Session Router."
},
{
"verb": "pause",
"length": 1
},
{
"verb": "hangup"
}
]
- An empty action array ends the call with a no-verbs condition.
- Unknown verbs are skipped during parsing. If no recognized verbs remain, the call is ended.
redirectstops parsing the current array and asks another hook for the next instructions.gather,dial,sip:request, andlistencan alter flow depending on runtime result.
Webhook behavior
| Hook type | Behavior |
|---|---|
callHook |
Receives call events and can return a JSON verb array in an HTTP response body. |
callStatusHook |
Receives call-status notifications. Responses are ignored for call control. |
bridgeHook |
Receives bridge-related events and can return a JSON verb array where the runtime processes it. |
| WebSocket hook URL | Session Router writes the event payload to the WebSocket. It does not currently read WebSocket messages back as verb responses. |
Current runtime note: hook schema fields such as method,
username, and password exist, but the sender
currently sends HTTP hooks as JSON POST requests and does
not apply Basic Auth.
Event payload fields
Hook payloads include call identity, event name, direction, call status, SIP header summary, caller identity, queue fields, and customer metadata. Exact fields depend on the event and verb result.
| Field | Meaning |
|---|---|
call_sid | Session Router CPaaS call SID. |
call_id | SIP Call-ID. |
direction | Call direction. |
eventName | High-level event name, such as Call State, DTMF Detection, or Queue State. |
eventDetails | Event detail, such as Initial, Answered, Cleared, or Bridged. |
digits | DTMF digits collected by gather. |
speech | Speech recognition result when speech input is collected. |
detectionReason | Reason a gather completed, such as DTMF completion, timeout, or speech detection. |
sip_status / sip_reason | SIP response status and reason for SIP control verbs. |
customerData | Metadata set by the tag verb. |
Supported verbs
These verbs are parsed and have meaningful runtime handlers in the current implementation.
| Verb | Purpose | Flow notes |
|---|---|---|
answer | Answer an inbound call. | Establishes the SIP dialog with a 200 OK when needed. |
say | Speak text using TTS. | Can use early media; plays generated audio into the call. |
play | Play audio URLs. | Signals play finished or play interrupted events. |
gather | Collect DTMF and/or speech. | Can contain nested say or play. |
dial | Create outbound leg. | Can break current verb flow when outbound call is launched. |
listen | Start WebSocket media streaming. | Streams binary PCM frames and JSON start/stop messages. |
dtmf | Send DTMF digits. | Requires negotiated DTMF format on the call. |
pause | Wait before next verb. | Blocks processing for the configured length. |
hangup | End, reject, or cancel the call. | Uses current call state to reject, cancel, or release. |
redirect | Ask another hook for more verbs. | Stops parsing the rest of the current verb array. |
tag | Add metadata to the call. | Values appear later under customerData. |
bridge | Bridge child and parent legs. | Only meaningful on child call legs. |
enqueue | Put call into a named queue. | Uses waitHook for queue treatment. |
leave | Leave the current queue. | Signals queue leave status. |
sip:request | Send in-dialog INFO, NOTIFY, or MESSAGE. | Reports SIP response to actionHook. |
sip:refer | Send in-dialog SIP REFER. | Can report provisional and final NOTIFY events. |
answer
Answers the current inbound call. If the call is already established, it continues immediately.
[
{
"verb": "answer"
}
]
| Property | Type | Required | Notes |
|---|---|---|---|
verb | string | yes | Must be answer. |
say
Speaks text into the call using TTS. The runtime supports Azure/Microsoft, ElevenLabs, and Deepgram TTS paths, depending on configured credentials.
[
{
"verb": "say",
"text": "Your call is now connected.",
"loop": 1,
"synthesizer": {
"vendor": "microsoft",
"language": "en-US",
"voice": "en-US-JennyNeural"
}
}
]
| Property | Type | Required | Notes |
|---|---|---|---|
verb | string | yes | Must be say. |
text | string | yes | Text or SSML-like text to speak. |
earlyMedia | boolean | no | If true, attempts playback before final answer when the call is not established. |
loop | number | no | Runtime defaults to one playback when absent or not positive. |
synthesizer | object | no | Nested vendor, language, voice, label, and vendor-specific settings. |
instructions | string | no | Prompt/instructions passed to supporting TTS vendors. |
stream | boolean | no | Modeled but not the main static text path. |
play
Plays one or more WAV/MP3 URLs into the call. Completion or interruption can generate a call event.
[
{
"verb": "play",
"url": ["https://example.com/audio/welcome.wav"],
"loop": 1,
"timeoutSecs": 30,
"dtmfInterrupt": true
}
]
| Property | Type | Required | Notes |
|---|---|---|---|
verb | string | yes | Must be play. |
url | array | yes | One or more audio URLs played in sequence. |
actionHook | string | no | Modeled for completion notification. |
earlyMedia | boolean | no | Passed to the dialog handler and can play before answer. |
loop | number | no | Defaults to 1 if absent or not positive. |
timeoutSecs | number | no | Limits playback duration. |
dtmfInterrupt | boolean | no | Allows DTMF to interrupt playback. |
seekOffset | number | no | Present in the model but not used by the current handler. |
gather
Collects DTMF digits, speech, or both. It can play a nested prompt using
say or play, then reports the result to
actionHook.
[
{
"verb": "gather",
"input": ["digits"],
"numDigits": 1,
"timeout": 10,
"interDigitTimeout": 5,
"finishOnKey": "#",
"includeFinishKey": false,
"dtmfBargein": true,
"actionHook": "https://example.com/menu-result",
"say": {
"text": "Press 1 for sales. Press 2 for support."
}
}
]
| Property | Type | Required | Notes |
|---|---|---|---|
verb | string | yes | Must be gather. |
input | array | yes | Use digits, speech, or both. Set explicitly. |
actionHook | string | yes for results | Called with DTMF/speech result when detection completes. |
say | object | no | Nested prompt using the same main say shape. |
play | object | no | Nested prompt. Current nested shape uses single url. |
timeout | number | no | Overall timeout in seconds. Defaults to 30, capped at 60. |
interDigitTimeout | number | no | Seconds to wait between DTMF digits. Defaults to 10, capped at 60. |
numDigits | number | no | Exact number of DTMF digits to collect. |
maxDigits | number | no | Maximum DTMF digits. Defaults to 32 when absent. |
finishOnKey | string | no | DTMF key that ends collection. |
includeFinishKey | boolean | no | Includes the finish key in returned digits when true. |
dtmfBargein | boolean | no | Allows DTMF to interrupt prompt playback. |
bargein | boolean | no | Allows speech barge-in where speech collection is active. |
listenDuringPrompt | boolean | no | Current runtime default is false. |
recognizer | object | for speech tuning | Vendor, language, model, confidence, and VAD options. |
Speech input example
[
{
"verb": "gather",
"input": ["speech"],
"timeout": 20,
"maxSpeechLength": 10,
"bargein": true,
"listenDuringPrompt": true,
"actionHook": "https://example.com/speech-result",
"recognizer": {
"vendor": "deepgram",
"language": "en-US",
"model": "nova-2",
"minConfidence": 0.55
},
"say": {
"text": "Tell me how I can help."
}
}
]
dial
Creates an outbound call leg and connects it to the current call. It
supports sequential and parallel target attempts and can include a nested
listen verb to stream caller audio while dialing.
[
{
"verb": "dial",
"callerId": "+15551234567",
"answerOnBridge": true,
"strategy": "sequential",
"timeout": 30,
"timeLimit": 3600,
"targets": [
{
"type": "phone",
"number": "+15557654321",
"trunk": "main-carrier"
}
],
"actionHook": "https://example.com/dial-result"
}
]
| Property | Type | Required | Notes |
|---|---|---|---|
verb | string | yes | Must be dial. |
targets | array | yes | Outbound destinations. Current implementation supports phone and user. |
strategy | string | no | sequential or parallel. Defaults to sequential. |
actionHook | string | no | Called with dial result. |
answerOnBridge | boolean | no | When true, keeps inbound leg in early media until outbound leg answers. |
makeB2b | boolean | no | Can move a still-establishing inbound session into B2BUA mode. |
callerId | string | no | Caller ID for outbound call attempts. |
dialMusic | string | no | Audio URL played while dialing. |
forwardPAI | boolean | no | Current runtime default is false unless explicitly true. |
headers | object | no | Custom SIP headers on outbound attempt. |
listen | object | no | Nested WebSocket media stream on the caller leg. |
timeLimit | number | no | Maximum call length in seconds. Runtime default is 7200. |
timeout | number | no | Ring-no-answer timeout in seconds. Runtime default is 60. |
Target object
| Property | Type | Notes |
|---|---|---|
type | string | phone and user are implemented. sip and teams are not implemented by the outbound call creator. |
number | string | Required for phone targets. Used by current user target path as well. |
trunk | string | Required for phone target routing. |
name | string | Modeled for user targets, but current outbound call path uses number. |
sipUri | string | Modeled, but SIP URI target dialing is not implemented. |
auth | object | Username/password object modeled for targets. |
listen
Streams call audio to and/or from a WebSocket media server. The media WebSocket sends an initial JSON start message and binary PCM audio frames.
[
{
"verb": "listen",
"url": "wss://media.example.com/session",
"direction": "both",
"audioFormat": "s16le",
"sampleRate": 16000,
"outputSampleRate": 16000,
"channels": 1,
"actionHook": "https://example.com/listen-ended"
}
]
| Property | Type | Required | Notes |
|---|---|---|---|
verb | string | yes top-level | Must be listen. Optional when nested under dial. |
url | string | yes | Must start with ws:// or wss://. |
direction | string | no | send, recv, or both. Defaults to both. |
audioFormat | string | no | Defaults to s16le. l16 big-endian is also supported. |
sampleRate | number | no | Defaults to 16000. |
outputSampleRate | number | no | Defaults to sampleRate. |
channels | number | no | Defaults to 1. |
actionHook | string | no | Called on listen end or failure. |
dtmf
Sends DTMF digits into the call. The current call must have a negotiated
DTMF format, and digits must be in the set 0-9, *,
or #.
[
{
"verb": "dtmf",
"dtmf": "123#",
"duration": 300
}
]
Duration is in milliseconds. The current runtime default is 300 ms, and values above 8000 ms are capped.
pause, redirect, tag, bridge, hangup
pause
Waits before processing the next verb. length is seconds and defaults to 1 when absent or not positive.
[{ "verb": "pause", "length": 2 }]
redirect
Signals the configured actionHook and processes returned verbs from that hook. Any remaining verbs in the current array are ignored after redirect is parsed.
[{ "verb": "redirect", "actionHook": "https://example.com/next" }]
tag
Merges custom data into the session tags. Later webhook payloads expose those values under customerData.
[{ "verb": "tag", "data": { "accountTier": "gold", "caseId": "A-1001" } }]
bridge
Bridges a child call leg back to its parent leg. releaseMedia can trigger media-path changes when possible.
[{ "verb": "bridge", "releaseMedia": false, "actionHook": "https://example.com/bridge" }]
hangup
Rejects, cancels, or releases the call depending on the current SIP state. statusCode, q850Code, and warning are the currently useful fields.
[
{
"verb": "hangup",
"statusCode": 486,
"q850Code": 17,
"warning": "Busy"
}
]
Queue verbs
enqueue
Places the call into a named queue. The runtime answers the call before
queueing and requires name, waitHook, and
actionHook.
[
{
"verb": "enqueue",
"name": "support",
"priority": 5,
"autoHunt": true,
"waitHook": "https://example.com/queue-wait",
"actionHook": "https://example.com/queue-result"
}
]
The wait hook receives Queue State events and may return only
say, play, pause, or leave
verbs. Session Router repeats the wait hook while the call remains queued
and no RTP prompt is active.
leave
Requests that the current queued call leave the queue.
[{ "verb": "leave" }]
SIP control verbs
sip:request
Sends an in-dialog SIP INFO, NOTIFY, or
MESSAGE. Session Router waits for the SIP response and
reports the result to actionHook.
[
{
"verb": "sip:request",
"method": "INFO",
"contentType": "application/json",
"body": "{\"event\":\"ping\"}",
"headers": {
"X-Trace-ID": "abc123"
},
"actionHook": "https://example.com/sip-info-result"
}
]
- Only
INFO,NOTIFY, andMESSAGEare accepted. - Use exactly one of
bodyorbodyBase64when sending content. - SDP, arbitrary XML, and unclassified content types are rejected by the current handler.
sip:refer
Sends an in-dialog SIP REFER. The runtime reports the REFER response to
actionHook, then can report REFER NOTIFY events. Provisional
NOTIFY statuses go to eventHook; final NOTIFY statuses go to
actionHook.
[
{
"verb": "sip:refer",
"referTo": "sip:agent@example.com",
"referredBy": "sip:caller@example.com",
"actionHook": "https://example.com/refer-result",
"eventHook": "https://example.com/refer-progress"
}
]
Unsupported or partial paths
| Feature | Current behavior |
|---|---|
dequeue | Parsed and passed to an executor case, but the handler is empty. |
transcribe | Constant and commented model exist, but there is no top-level parser/executor case. Use gather with speech input for STT. |
| WebSocket hook responses | Hook payloads can be written to WebSocket URLs, but responses are not read back and cannot drive verbs. |
application_sid on outbound REST calls | Accepted by validation, but not resolved into an application by the current SIP call creator. |
dial target types sip and teams | Modeled but not implemented by outbound call creation. |
hangup.statusReason and hangup.reason | Modeled, but current handler mainly uses status code, Q.850 code, and warning text. |
play.seekOffset | Modeled but not used by the current play handler. |
gather.minDigits, partialResultHook, actionHookDelayAction | Modeled but not materially enforced in the current gather path. |