CPaaS developer guide

CPaaS verbs

CPaaS verbs are JSON instructions returned by your application webhook to control a live SIP call. This page documents the current Session Router behavior validated from the verb parser, executor, and webhook sender.

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.
  • redirect stops parsing the current array and asks another hook for the next instructions.
  • gather, dial, sip:request, and listen can 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_sidSession Router CPaaS call SID.
call_idSIP Call-ID.
directionCall direction.
eventNameHigh-level event name, such as Call State, DTMF Detection, or Queue State.
eventDetailsEvent detail, such as Initial, Answered, Cleared, or Bridged.
digitsDTMF digits collected by gather.
speechSpeech recognition result when speech input is collected.
detectionReasonReason a gather completed, such as DTMF completion, timeout, or speech detection.
sip_status / sip_reasonSIP response status and reason for SIP control verbs.
customerDataMetadata set by the tag verb.

Supported verbs

These verbs are parsed and have meaningful runtime handlers in the current implementation.

Verb Purpose Flow notes
answerAnswer an inbound call.Establishes the SIP dialog with a 200 OK when needed.
saySpeak text using TTS.Can use early media; plays generated audio into the call.
playPlay audio URLs.Signals play finished or play interrupted events.
gatherCollect DTMF and/or speech.Can contain nested say or play.
dialCreate outbound leg.Can break current verb flow when outbound call is launched.
listenStart WebSocket media streaming.Streams binary PCM frames and JSON start/stop messages.
dtmfSend DTMF digits.Requires negotiated DTMF format on the call.
pauseWait before next verb.Blocks processing for the configured length.
hangupEnd, reject, or cancel the call.Uses current call state to reject, cancel, or release.
redirectAsk another hook for more verbs.Stops parsing the rest of the current verb array.
tagAdd metadata to the call.Values appear later under customerData.
bridgeBridge child and parent legs.Only meaningful on child call legs.
enqueuePut call into a named queue.Uses waitHook for queue treatment.
leaveLeave the current queue.Signals queue leave status.
sip:requestSend in-dialog INFO, NOTIFY, or MESSAGE.Reports SIP response to actionHook.
sip:referSend 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"
  }
]
PropertyTypeRequiredNotes
verbstringyesMust 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"
    }
  }
]
PropertyTypeRequiredNotes
verbstringyesMust be say.
textstringyesText or SSML-like text to speak.
earlyMediabooleannoIf true, attempts playback before final answer when the call is not established.
loopnumbernoRuntime defaults to one playback when absent or not positive.
synthesizerobjectnoNested vendor, language, voice, label, and vendor-specific settings.
instructionsstringnoPrompt/instructions passed to supporting TTS vendors.
streambooleannoModeled 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
  }
]
PropertyTypeRequiredNotes
verbstringyesMust be play.
urlarrayyesOne or more audio URLs played in sequence.
actionHookstringnoModeled for completion notification.
earlyMediabooleannoPassed to the dialog handler and can play before answer.
loopnumbernoDefaults to 1 if absent or not positive.
timeoutSecsnumbernoLimits playback duration.
dtmfInterruptbooleannoAllows DTMF to interrupt playback.
seekOffsetnumbernoPresent 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."
    }
  }
]
PropertyTypeRequiredNotes
verbstringyesMust be gather.
inputarrayyesUse digits, speech, or both. Set explicitly.
actionHookstringyes for resultsCalled with DTMF/speech result when detection completes.
sayobjectnoNested prompt using the same main say shape.
playobjectnoNested prompt. Current nested shape uses single url.
timeoutnumbernoOverall timeout in seconds. Defaults to 30, capped at 60.
interDigitTimeoutnumbernoSeconds to wait between DTMF digits. Defaults to 10, capped at 60.
numDigitsnumbernoExact number of DTMF digits to collect.
maxDigitsnumbernoMaximum DTMF digits. Defaults to 32 when absent.
finishOnKeystringnoDTMF key that ends collection.
includeFinishKeybooleannoIncludes the finish key in returned digits when true.
dtmfBargeinbooleannoAllows DTMF to interrupt prompt playback.
bargeinbooleannoAllows speech barge-in where speech collection is active.
listenDuringPromptbooleannoCurrent runtime default is false.
recognizerobjectfor speech tuningVendor, 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"
  }
]
PropertyTypeRequiredNotes
verbstringyesMust be dial.
targetsarrayyesOutbound destinations. Current implementation supports phone and user.
strategystringnosequential or parallel. Defaults to sequential.
actionHookstringnoCalled with dial result.
answerOnBridgebooleannoWhen true, keeps inbound leg in early media until outbound leg answers.
makeB2bbooleannoCan move a still-establishing inbound session into B2BUA mode.
callerIdstringnoCaller ID for outbound call attempts.
dialMusicstringnoAudio URL played while dialing.
forwardPAIbooleannoCurrent runtime default is false unless explicitly true.
headersobjectnoCustom SIP headers on outbound attempt.
listenobjectnoNested WebSocket media stream on the caller leg.
timeLimitnumbernoMaximum call length in seconds. Runtime default is 7200.
timeoutnumbernoRing-no-answer timeout in seconds. Runtime default is 60.

Target object

PropertyTypeNotes
typestringphone and user are implemented. sip and teams are not implemented by the outbound call creator.
numberstringRequired for phone targets. Used by current user target path as well.
trunkstringRequired for phone target routing.
namestringModeled for user targets, but current outbound call path uses number.
sipUristringModeled, but SIP URI target dialing is not implemented.
authobjectUsername/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"
  }
]
PropertyTypeRequiredNotes
verbstringyes top-levelMust be listen. Optional when nested under dial.
urlstringyesMust start with ws:// or wss://.
directionstringnosend, recv, or both. Defaults to both.
audioFormatstringnoDefaults to s16le. l16 big-endian is also supported.
sampleRatenumbernoDefaults to 16000.
outputSampleRatenumbernoDefaults to sampleRate.
channelsnumbernoDefaults to 1.
actionHookstringnoCalled 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, and MESSAGE are accepted.
  • Use exactly one of body or bodyBase64 when 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
dequeueParsed and passed to an executor case, but the handler is empty.
transcribeConstant and commented model exist, but there is no top-level parser/executor case. Use gather with speech input for STT.
WebSocket hook responsesHook payloads can be written to WebSocket URLs, but responses are not read back and cannot drive verbs.
application_sid on outbound REST callsAccepted by validation, but not resolved into an application by the current SIP call creator.
dial target types sip and teamsModeled but not implemented by outbound call creation.
hangup.statusReason and hangup.reasonModeled, but current handler mainly uses status code, Q.850 code, and warning text.
play.seekOffsetModeled but not used by the current play handler.
gather.minDigits, partialResultHook, actionHookDelayActionModeled but not materially enforced in the current gather path.