The Flow Control API starts an outbound call or performs actions on an existing active call.
Flow execution is asynchronous. A successful API response confirms that the flow was accepted, not that every action has completed.
Authentication
The endpoint requires an authenticated session or API token.
Authorization: Bearer <api-token>Endpoint
POST /api/flowControl
Content-Type: application/jsonRequest structure
interface FlowControlRequest {
callId?: number
organizationId?: number
flow: FlowAction[]
}There are two supported modes:
- Start a new outbound call by providing
organizationIdand aDialaction. - Control an existing active call by providing
callIdand omittingDial.
Response
{
"flowId": "e691070d-522f-4cce-9d45-6df4b8730804",
"callId": 12345
}| Field | Description |
|---|---|
flowId | Unique identifier for this flow request. |
callId | Identifier of the existing or newly created call. |
Retain both identifiers for logging and webhook correlation.
Starting an outbound call
Provide an organizationId and include one Dial action.
curl -X POST "$BASE_URL/api/flowControl" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"organizationId": 42,
"flow": [
{
"action": "Dial",
"payload": {
"number": "+27821234567",
"voicemailDetection": false,
"waitOnVoicemailDetection": false,
"killOnVoicemailDetection": true,
"additionalCallData": {
"customerReference": "customer-789"
}
}
},
{
"action": "MusicOnHoldStart",
"payload": {}
}
]
}'The phone number is interpreted using the organization’s configured country. International numbers beginning with + are recommended.
Only one Dial action should be supplied.
Dial payload
| Field | Required | Description |
|---|---|---|
number | Yes | Destination phone number. |
clid | No | Preferred outbound caller ID. |
clidGroup | No | Caller-ID selection group. |
additionalCallData | No | Additional JSON metadata stored against the call. |
url | No | Webhook invoked after the call is answered or voicemail processing completes. |
method | No | Webhook method: POST or GET. Defaults to POST. |
callStatusUrl | No | Webhook invoked when the call’s status changes. |
callStatusMethod | No | Status-webhook method. Defaults to POST. |
voicemailDetection | No | Enables voicemail detection. Defaults to false. |
waitOnVoicemailDetection | No | Waits for voicemail detection before invoking the answer webhook or continuing. Defaults to false. |
killOnVoicemailDetection | No | Ends the call if voicemail is detected. Defaults to true. |
earlyAnswerDetection | No | Enables early-answer detection where supported. |
When url is present, the webhook is invoked instead of automatically continuing the remaining actions in that flow. This mode is intended for dynamic flows where the webhook handler submits another request using the returned callId.
Controlling an existing call
Provide the callId of an active call.
curl -X POST "$BASE_URL/api/flowControl" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"callId": 12345,
"flow": [
{
"action": "MusicOnHoldStart",
"payload": {
"class": "default"
}
},
{
"action": "MusicOnHoldStop",
"payload": {}
},
{
"action": "Hangup",
"payload": {}
}
]
}'The call must still be active. A request may be accepted even if the call ends before its actions are executed.
Do not include Dial when controlling an existing call.
Supported actions
Actions are generally executed in their supplied order.
| Action | Payload | Description |
|---|---|---|
Dial | { number, ...options } | Creates a new outbound call. |
TransferToQueue | { queueId: number } | Transfers the call to a queue. |
TransferToNumber | { number: string } | Transfers the call to another telephone number. |
TransferToUser | { userId: number } | Transfers or assigns the call to a user. |
Hangup | {} | Ends the call. |
MusicOnHoldStart | { class?: string } | Starts music on hold. |
MusicOnHoldStop | {} | Stops music on hold. |
WaitForDTMF | See below | Waits for telephone keypad input. |
unidirectionalExternalMedia | See below | Streams call audio to a WebSocket consumer. |
TransferToQueue, TransferToNumber, TransferToUser, and Hangup should be treated as terminal actions. Do not place additional actions after them.
Action names are case-sensitive.
Transfer to queue
{
"action": "TransferToQueue",
"payload": {
"queueId": 25
}
}Transfer to number
{
"action": "TransferToNumber",
"payload": {
"number": "+27821234567"
}
}Transfer to user
{
"action": "TransferToUser",
"payload": {
"userId": 101
}
}Start music on hold
{
"action": "MusicOnHoldStart",
"payload": {
"class": "default"
}
}The class field is optional. The organization’s default music is used when it is omitted.
Stop music on hold
{
"action": "MusicOnHoldStop",
"payload": {}
}End a call
{
"action": "Hangup",
"payload": {}
}Waiting for keypad input
Use WaitForDTMF to collect telephone keypad input.
{
"action": "WaitForDTMF",
"payload": {
"timeout": 30,
"endCharacter": "#",
"url": "https://consumer.example.com/call/dtmf",
"method": "POST"
}
}WaitForDTMF payload
| Field | Required | Description |
|---|---|---|
url | Yes | Webhook invoked when the end character is entered. |
timeout | No | Maximum wait in seconds. Defaults to 60. |
endCharacter | No | Character used to finish input. Defaults to #. |
method | No | POST or GET. Defaults to POST. |
Webhook example:
{
"call": {
"id": 12345
},
"flow": {
"id": "e691070d-522f-4cce-9d45-6df4b8730804",
"action": "WaitForDTMF"
},
"voicemail": false
}Current behavioral limitations:
- The collected digits are not included in the webhook body.
- A timeout continues to the next action without invoking the webhook.
- Entering the end character invokes the webhook but does not continue to the next action.
If the collected digits are required, use external media streaming and consume its DTMF events.
External media streaming
The unidirectionalExternalMedia action streams call audio to a WebSocket endpoint controlled by the consumer.
{
"action": "unidirectionalExternalMedia",
"payload": {
"externalMediaUrl": "wss://media.example.com/calls",
"direction": "BOTH",
"codec": "ulaw"
}
}External-media payload
| Field | Required | Description |
|---|---|---|
externalMediaUrl | Yes | WebSocket URL to which the service connects. |
direction | No | USER, EXTERNAL, or BOTH. Defaults to BOTH. |
codec | No | ulaw, alaw, or slin16. Defaults to ulaw. |
Direction values:
| Value | Audio supplied |
|---|---|
USER | User-side audio. |
EXTERNAL | Other-party audio. |
BOTH | Both sides of the conversation. |
Start event
The first media event describes the stream:
{
"event": "start",
"sequenceNumber": 0,
"streamId": 55,
"callId": 12345,
"organizationId": 42,
"type": "SPY",
"direction": "BOTH",
"dialledNumber": "27821234567",
"clid": "27110000000",
"codec": "ulaw",
"packetTime": 20,
"optimalFrameSize": 160
}| Field | Description |
|---|---|
streamId | Unique media-stream identifier. |
sequenceNumber | Incrementing sequence number for stream events. |
callId | Associated call identifier. |
organizationId | Associated organization identifier. |
direction | Selected audio direction. |
dialledNumber | Destination number. |
clid | Outbound caller ID. |
codec | Audio codec used by media payloads. |
packetTime | Audio packet duration in milliseconds. |
optimalFrameSize | Recommended frame size in bytes. |
Consumers should ignore unknown fields to remain forward-compatible.
Audio event
Audio is delivered as base64-encoded frames:
{
"event": "media",
"sequenceNumber": 1,
"streamId": 55,
"media": {
"payload": "<base64-encoded-audio>"
}
}Decode media.payload from base64 and process it using the codec supplied in the start event.
DTMF event
Keypad input is delivered separately:
{
"event": "dtmf",
"sequenceNumber": 2,
"streamId": 55,
"dtmf": {
"digit": "5"
}
}Stop event
The service sends a stop event when the media session closes, provided that the WebSocket is still available:
{
"event": "stop",
"sequenceNumber": 3,
"streamId": 55
}Consumer control messages
Stop streaming without ending the call:
{
"action": "stopListening"
}End the call:
{
"action": "hangup"
}Webhooks
Flow webhook
Dial and keypad-input webhooks use this general structure:
{
"call": {
"id": 12345,
"...": "additional call fields"
},
"flow": {
"id": "e691070d-522f-4cce-9d45-6df4b8730804",
"action": "Dial"
},
"voicemail": false
}Call-status webhook
When callStatusUrl is configured, status changes produce:
{
"call": {
"id": 12345,
"callStatus": "ANSWERED",
"...": "additional call fields"
},
"flow": {
"id": "e691070d-522f-4cce-9d45-6df4b8730804",
"action": "Dial"
}
}Webhook endpoints should:
- Accept duplicate notifications safely.
- Respond quickly with a successful
2xxstatus. - Use
flow.idandcall.idfor correlation. - Ignore unknown fields for forward compatibility.
Webhook signing, custom authentication headers, and delivery retries are not currently part of the public contract.
Errors
Errors generally have this shape:
{
"message": "Description of the error"
}| Status | Meaning |
|---|---|
400 | Required call context is missing. |
401 | Authentication is missing or invalid. |
404 | The specified call or organization was not found. |
422 | The request, phone number, caller ID, or media configuration is invalid. |
503 | No calling capacity is currently available. |
Examples:
{
"message": "A callId is required or a Dial action must be present in the flow."
}{
"message": "OrganizationId is required when creating a new call"
}{
"message": "Invalid phone number"
}{
"message": "Unsupported external media codec. Expected ulaw, alaw, or slin16"
}{
"message": "Call Queue Full"
}Consumer recommendations
- Use one
Dialaction per new outbound call. - Do not combine an existing
callIdwithDial. - Treat successful responses as acceptance acknowledgements.
- Treat transfer and hang-up actions as terminal.
- Validate action payloads before sending them.
- Store
flowIdandcallIdfor correlation. - Make webhook processing idempotent.
- Use international phone-number formatting.
- Ignore unknown webhook and WebSocket fields.
- Do not use unsupported action names.
Was this article helpful?
That’s Great!
Thank you for your feedback
Sorry! We couldn't be helpful
Thank you for your feedback
Feedback sent
We appreciate your effort and will try to fix the article