Flow Control API

Created by Pieter Venter, Modified on Tue, 21 Jul at 12:56 PM by Pieter Venter

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/json

Request structure

interface FlowControlRequest {
  callId?: number
  organizationId?: number
  flow: FlowAction[]
}

There are two supported modes:

  1. Start a new outbound call by providing organizationId and a Dial action.
  2. Control an existing active call by providing callId and omitting Dial.

Response

{
  "flowId": "e691070d-522f-4cce-9d45-6df4b8730804",
  "callId": 12345
}
FieldDescription
flowIdUnique identifier for this flow request.
callIdIdentifier 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

FieldRequiredDescription
numberYesDestination phone number.
clidNoPreferred outbound caller ID.
clidGroupNoCaller-ID selection group.
additionalCallDataNoAdditional JSON metadata stored against the call.
urlNoWebhook invoked after the call is answered or voicemail processing completes.
methodNoWebhook method: POST or GET. Defaults to POST.
callStatusUrlNoWebhook invoked when the call’s status changes.
callStatusMethodNoStatus-webhook method. Defaults to POST.
voicemailDetectionNoEnables voicemail detection. Defaults to false.
waitOnVoicemailDetectionNoWaits for voicemail detection before invoking the answer webhook or continuing. Defaults to false.
killOnVoicemailDetectionNoEnds the call if voicemail is detected. Defaults to true.
earlyAnswerDetectionNoEnables 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.

ActionPayloadDescription
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.
WaitForDTMFSee belowWaits for telephone keypad input.
unidirectionalExternalMediaSee belowStreams 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

FieldRequiredDescription
urlYesWebhook invoked when the end character is entered.
timeoutNoMaximum wait in seconds. Defaults to 60.
endCharacterNoCharacter used to finish input. Defaults to #.
methodNoPOST 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

FieldRequiredDescription
externalMediaUrlYesWebSocket URL to which the service connects.
directionNoUSER, EXTERNAL, or BOTH. Defaults to BOTH.
codecNoulaw, alaw, or slin16. Defaults to ulaw.

Direction values:

ValueAudio supplied
USERUser-side audio.
EXTERNALOther-party audio.
BOTHBoth 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
}
FieldDescription
streamIdUnique media-stream identifier.
sequenceNumberIncrementing sequence number for stream events.
callIdAssociated call identifier.
organizationIdAssociated organization identifier.
directionSelected audio direction.
dialledNumberDestination number.
clidOutbound caller ID.
codecAudio codec used by media payloads.
packetTimeAudio packet duration in milliseconds.
optimalFrameSizeRecommended 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 2xx status.
  • Use flow.id and call.id for 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"
}
StatusMeaning
400Required call context is missing.
401Authentication is missing or invalid.
404The specified call or organization was not found.
422The request, phone number, caller ID, or media configuration is invalid.
503No 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 Dial action per new outbound call.
  • Do not combine an existing callId with Dial.
  • Treat successful responses as acceptance acknowledgements.
  • Treat transfer and hang-up actions as terminal.
  • Validate action payloads before sending them.
  • Store flowId and callId for 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

Let us know how can we improve this article!

Select at least one of the reasons

Feedback sent

We appreciate your effort and will try to fix the article