Getting started
Getting StartedAPI BasicsAPIs
AuthenticationBlocklistsCall ChannelsCall CenterCall RecordingsDevicesPivotQuickcallWebhooksBusiness SMS
VirtualTextAutomation
ZapierUse cases
Create Trello Card for Voicemails Received Send Call Data to Google SheetsSlack Notifications from Call EventsSMS Airtable TemplateTrigger SMS Messages from Your CRMZapier Webinar RecordingAbout Pivot
The Pivot gives developers control over Inbound Call Handling. Pivot send call details via HTTP to the developer's web server. Pivot expects a response with appropriate JSON, and will execute the callflow returned on behalf of the developer.
A Pivot can be used in callflows in anywhere you want the external application to determine call routing and manage the call experience.
A Pivot can build complex call handling using a wide range of callflow elements. Complex call control may requiere muliple pivots.
To configure Pivots you need to have Advance Call Flows feature enabled on your account.
Use Cases
- Collecting input from callers to determine routing
- Automatically assigning inbound calls to the last agent they spoke with
- Dynamically routing calls based on phone number
- Automatically routing calls when a location is closed
Example Callflow
The most basic callflow for Pivot:
JSON
{
"flow":{
"module":"pivot"
,"data":{
"voice_url":"http://your.pivot.server/path/to/script.php"
,"req_format":"kazoo"
,"method":"get"
,"debug":false
}
}
}
Debugging
You can set the debug
flag to "true" to log the requests and responses Pivot receives from your Pivot Callflows.
Summary
Get a list of recent Pivot attempts:
Shell
$ curl -H "X-Auth-Token: {AUTH_TOKEN} \
-H "Content-Type: application/json" \
'http://api.virtualpbx.net/v2/accounts/{ACCOUNT_ID}/pivot/debug'
Response:
JSON
{
"data":{
"name":"Missed Call",
"hook":"notifications",
"include_subaccounts":false,
"http_verb":"get",
"retries":4,
"uri":"https://hooks.zapier.com/hooks/catch/591668/xlwslm/",
"type":"account",
"action":"doc_created",
"custom_data":{
"type":"missed_call"
},
"enabled":true,
"include_internal_legs":true,
"id":"51840ffc8bde832a1f5477def32cd665"
}
}
Specific Call
Get details of a specific Pivot attempt:
Shell
$curl -H "X-Auth-Token: {AUTH_TOKEN} \
-H "Content-Type: application/json" \
'http://api.virtualpbx.net/v2/accounts/{ACCOUNT_ID}/pivot/debug/{CALL_ID}'
Response:
JSON
{
"auth_token": "{AUTH_TOKEN}",
"data": [
{
"call_id": "{CALL_ID}",
"id": "{DEBUG_ID}",
"method": "get",
"req_body": "",
"req_headers": {},
"uri": "http://your.pivot.server/script.php?Caller-ID-Number=XXXXXXXXXX&Caller-ID-Name=JoeFromIT&Direction=inbound&ApiVersion=2013-05-01&ToRealm=your.pivot.server&To=3004&FromRealm=your.pivot.server&From=user_sov2kt&Account-ID={ACCOUNT_ID}&Call-ID={CALL_ID}"
},
{
"call_id": "{CALL_ID}",
"id": "{DEBUG_ID}",
"resp_body": "\n {\"module\":\"say\"\n ,\"data\":{\"text\":\"Please leave your message after the beep\"}\n ,\"children\":{\n \"_\":{\n \"module\":\"record_caller\"\n ,\"data\":{\n \"format\":\"mp3\"\n ,\"url\":\"http://your.pivot.server/recordings\"\n ,\"time_limit\":360\n }\n }\n }\n }\n"
},
{
"call_id": "{CALL_ID}",
"id": "{DEBUG_ID}",
"resp_headers": {
"content-length": "342",
"content-type": "application/json",
"date": "wed, 01 oct 2014 23:30:24 gmt",
"server": "apache/2.4.7 (ubuntu)",
"x-powered-by": "php/5.5.9-1ubuntu4.4"
},
"resp_status_code": "200"
}
],
"request_id": "{REQUEST_ID}",
"revision": "{REVISION}",
"status": "success"
}
Remember to URL-encode the {CALL_ID}
before sending the request.
Failback
You can add a children to your pivot callflow in case your server is unreachable or send back an error.
JSON
"flow": {
"data": {
"method": "GET",
"req_timeout": "5",
"req_format": "kazoo",
"voice_url": "{SERVER_URL}"
},
"module": "pivot",
"children": {
"_": {
"module": "play",
"data": {
"id": "{MEDIA_ID}"
},
"children": {}
}
}
}
The Request Payload
You can configure whether you receive the data as a query string in a GET
or as a URL-encoded body in a POST
.
Payload
Some of the fields may not be included in every request.
Field | Description |
Call-ID | The unique call identifier |
Account-ID | The account id receiving the call |
From | The SIP From username |
From-Realm | The SIP From realm |
To | The SIP To username |
To-Realm | The SIP To realm |
Request | The SIP Request username |
Request-Realm | The SIP Request realm |
Call-Status | Status of the call |
Api-Version | The version of the API |
Direction | The direction of the call, relative to VirtualPBX |
Caller-ID-Name | Caller ID Name |
Caller-ID-Number | Caller ID Number |
User-ID | The VirtualPBX User identifier(s) of the caller |
Language | The caller's language preference |
Recording-Url | Where the recording will be stored |
Recording-Duration | How long the recording is |
Recording-ID | The recording id |
Digits | Any DTMF (or collection of DTMFs) pressed |
JSON Overview
Sometimes VirtualPBX's callflow builder doesn't match your needs, integrate with your applications, or provide the experience for your caller that you desire. Fortunately, the building blocks are available for you to play with as you see fit!
By routing a call to a Pivot callflow action, Vi will make an HTTP request to your web server asking for the callflow to process for that specific call (versus the one-size-fits-all approach in the normal callflow builder).
This could be best illustrated with an example, no?
Building Time-based routing
We want to add time-based rules when deciding how to route our office's main number (this exists with the temporal_route
callflow action already, but we're trying to make a point!). Following a "typical" office's hours, we are open 9am-5pm (9:00 to 17:00). During that time, we'd like to ring the front desk's phone. Outside of those hours, we'd like calls to go directly to the company's voicemail box.
Example of Building on Pivot
Based on the above goals, we need to build:
- Front Desk's device
- Company Voicemail box
- Main number callflow
Once you've made those, note the IDs for the device and voicemail box (using the developer tool is a good way to find those).
The main number callflow should be:
Plain Text
[NUMBER] -> Pivot
Url: http://your.webserver.com/path/to/main_number_tod.php
Now, whenever this number is called, VirtualPBX will query your URL for callflow to execute.
Build main_number_tod.php
The first thing needed is to set the content-type to application/json.
PHP
<?php
header('content-type:application/json');
Now we need to know what time it is and determine what to do:
PHP
$now = time();
$hour = date("G", $now);
if ( $hour >= 9 && $hour < 17 ) {
business_hours();
} else {
after_hours();
}
Now we have two functions to build the JSON for the callflow to execute:
business_hours()
:after_hours()
:
PHP
function business_hours() {
?>
{"module":"device"
,"data":{"id":"{FRONT_DESK_DEVICE_ID}"}
,"children":{
"_":{
"module":"voicemail"
,"data":{"id":"{COMPANY_VM_BOX_ID}"}
}
}
<?php
}
PHP
function after_hours() {
?>
{"module":"voicemail"
,"data":{"id":"{COMPANY_VM_BOX_ID}"}
}
<?php
}
Bring it all together:
PHP
<?php
header('content-type:application/json');
$now = time();
$hour = date("G", $now);
if ( $hour > 8 && $hour < 17 ) {
business_hours();
} else {
after_hours();
}
function business_hours() {
?>
{"module":"device"
,"data":{"id":"{FRONT_DESK_DEVICE_ID}"}
,"children":{
"_":{
"module":"voicemail"
,"data":{"id":"{COMPANY_VM_BOX_ID}"}
}
}
<?php
}
function after_hours() {
?>
{"module":"voicemail"
,"data":{"id":"{COMPANY_VM_BOX_ID}"}
}
<?php
}
?>
TwiML Format
Pivot supports a subset of TwiML to help ease you into Pivot with an existing TwiML-based application.
Core Supported
Default Request Data included
Request Parameter | Name | Description |
CallerName | Caller-ID-Name | Name of the caller, if any |
Direction | Direction | Direction of the call (outbound if VirtualPBX originated the call, inbound otherwise) |
ApiVerson | N/A | Version string related to API changes |
CallStatus | N/A | What state the call is currently in |
To | To-User | Dialed number |
From | From-User | Caller's number, if available |
AccountSid | Account-ID | Account ID processing the call |
CallSid | Call-ID | Unique identifier of the call leg |
Optional/Conditional Request Data
Request Parameter | Name | Description |
RecordingUrl | Recording-URL | Where a recording will be sent (via HTTP PUT request) |
RecordingDuration | Recording-Duration | Length of the recording, if available |
RecordingSid | Media-Name | Name of the recording file |
Digits | DTMF-Pressed | The DTMF(s) (touch tone) pressed by the caller |
DialCallStatus | N/A | Call status of the b-leg |
DialCallSid | Other-Leg-Unique-ID | Call-ID of the b-leg |
DialCallDuration | Billing-Seconds | How many billable seconds the call lasted |
Other optional data includes user-defined key/value pairs stored using the verb below.
TwiML Verbs
Verb | Description | Nestable Verbs and Nouns |
Connect the caller to other endpoints | plain text DID, , , , , | |
Record the caller | ||
Collect DTMFs from the caller | ||
Play a media file (mp3, wav) to the caller | ||
Say | Use a TTS engine to say the supplied text | |
Redirect | Like an HTTP Redirect, make another HTTP request | |
Pause | Pause callflow execution for supplied number of seconds | |
Hangup | Hangup the caller | |
Reject | Reject (and don't answer - won't start billing) the call |
Custom Verbs
Verb | Description | Nestable Nouns |
Key value pair(s) to store along-side the call |
Core TwiML Nouns
Noun | Description |
Conference room endpoint for | |
Call queue to line callers up in | |
DID with extended attributes | |
ID of an existing User (works like the User callflow element | |
ID of an existing Device | |
SIP URI to dial |
Custom Nouns
Noun | Description |
Includes 'key' and 'value' attributes; values will be put subsequent requests |
Bridging
VirtualPBX JSON offers a plethora of ways to call out to various endpoints! Below are some minimalist examples to whet your appetite. Most take more options, are nestable as children of other callflow actions, and are generally quite useful in accomplishing most peoples' needs.
Devices
Dial a single device
JSON
{
"module":"device",
"data":{"id":"device_id"}
}
Users
Dial a User (any devices owned by the user)
JSON
{
"module": "user",
"data": {
"id": "user_id"
}
}
Ring Group
Ring groups are ultra-flexible in what types of endpoints you can combine: devices, users, or groups! You need only include the IDs you want to ring and VirtualPBX will build the appropriate list of endpoints.
JSON
{
"module":"ring_group",
"data":{
"endpoints": ["device_1_id",
"device_2_id",
"user_1_id",
"user_2_id",
"group_1_id",
"group_2_id"
]
}
}
You are free to mix/match devices, users, and groups based on the needs of this particular call.
Dialing outside the account
It is all well and good that dialing to known VirtualPBX endpoints is so easy, but what about contacting the outside world?
VirtualPBX supports two types of resources, global and per-account (or local, as VirtualPBX refers to them). You can optionally route to either, depending on how you've configured your account and whether you utilize the VirtualPBX cluster's global resources.
The only real difference is the use_local_resources
flag.
Using Global resources
JSON
{
"module": "resources",
"data": {
"to_did": "+14155550000",
"use_local_resources": false
}
}
Using Local resources
JSON
{
"module": "resources",
"data": {
"to_did": "+14155550000",
"use_local_resources": true
}
}
Collect
It is possible to collect DTMFs via VirtualPBX JSON using the cf_collect_dtmf
callflow action. You can also store the collected DTMF in the custom-named keys to differentiate different collections.
Collecting DTMF
The initial VirtualPBX JSON to collect DTMF could look something like:
PHP
<?php
header('content-type:application/json');
?>
{
"module":"tts",
"data":{
"text": "Please enter up to four digits."
},
"children": {
"_": {
"module": "collect_dtmf",
"data": {
"max_digits": 4,
"collection_name": "custom_name"
},
"children": {
"_": {
"module": "pivot",
"data": {
"voice_url": "http://pivot.your.company.com/collected.php"
},
"children": {}
}
}
}
}
}
First, VirtualPBX will use the TTS engine to say the text
field. Next, it will wait for the user to press up to 4 DTMF (with #
being a terminating DTMF that is not included in the collection). Finally, a second pivot request will be made the the collected.php
script on your server.
This is a basic menu! Congrats, you can build custom IVRs!
Processing collected DTMF
Here is demonstrated speaking back the digits pressed to the caller; you could obviously key off the DTMF to do whatever further call processing.
PHP
<?php
header('content-type:application/json');
$dtmf = $_REQUEST['Digits'];
if ( empty($dtmf) ) {
?>
{
"module": "tts",
"data": {
"text": "We didn't get that"
},
"children": {}
}
<?php } else if ( is_string($dtmf) ) { ?>
{
"module": "tts",
"data": {
"text": "You typed <?= $dtmf ?>"
},
"children": {}
}
<?php } else { ?>
{
"module": "tts",
"data": {
"text": "You typed <?= $dtmf['custom_name'] ?>"
},
"children": {}
}
<?php } ?>
The is_string($dtmf)
check is to support the old way of returning DTMF in the Pivot request. Otherwise, you should receive an array of DTMF collections, indexed by the key name supplied ("default" if you didn't specify one).
Conferencing
There are two main ways to add the caller to a conference:
- Use pre-built VirtualPBX conference rooms, configured via Crossbar
- Create ad-hoc conference rooms via Pivot
Pre-built example
JSON
{
"module": "conference",
"data": {
"id": "conference_id"
}
}
Ad-Hoc example
JSON
{
"module": "conference",
"data": {
"config": {
"name": "My Ad-hoc Conference"
}
}
}
This will create a minimalist conference bridge, named "My Ad-hoc Conference". Use the same name to ensure callers end up in the same conference together.
Conference Per Area Code
PHP
<?php
header('content-type: application/json');
$caller_id = $_REQUEST['Caller-ID-Number'];
$areacode = ($caller_id, 0, 3);
?>
{
"module": "tts",
"data": {
"text": "Welcome to the <?= $areacode ?> conference!"
},
"children": {
"_": {
"module": "conference",
"data": {
"config": {
"name": "Areacode <?= $areacode ?>"
}
},
"children": {}
}
}
}
Obviously this doesn't handle hidden or missing caller ID.
DTMF
Sending DTMF to the caller is sometimes necessary (automating IVR navigation, perhaps). Use the send_dtmf
callflow to do so.
Example
JSON
{
"module": "send_dtmf",
"data": {
"digits": "123ABC#",
"duration_ms": 2000
},
"children": {}
}
digits
is a string of DTMF to send.duration_ms
is how long of a tone to send each DTMF (and optional)
The above example would send "1", "2", "3", "A", "B", "C", and finally "#", each as 2 second tones.
Hang Ups
Sometimes it is necessary to respond to a call with a hangup cause and code. For extra fun, some phones will display the hangup cause on the display, which can be a source of amusement. For instance, for a while VirtualPBX would return a "403 Insert Coin" if the account was out of money.
Example
JSON
{
"module": "response",
"data": {
"code": "486",
"message": "User Busy",
"media":"id_or_url"
}
}
If you define media
, the media will be played to the user before hanging up the call.
Play
It is pretty easy to play files, either hosted in your account or accessible via a URI.
Play sample
The initial VirtualPBX JSON to play a file could look something like:
PHP
<?php
header('content-type:application/json');
?>
{
"module": "play",
"data": {
"id": "media_id"
},
"children": {
"_": {
"module": "play",
"data":{
"id": "http://some.file.server/some/file.mp3"
},
"children": {}
}
}
}
Here we see two play actions, one that uses a media file hosted by VirtualPBX and one that fetches the file from an HTTP server.
Presence
Pivot allows you to set custom presence updates (known as manual presence).
Example
JSON
{
"module": "manual_presence",
"data": {
"presence_id": "username",
"status": "ringing"
}
}
Do note, presence_id
without an @realm.com
will be suffixed with the account's realm.
status
can be one of idle
, ringing
, or busy
.
Recording
Recording the caller (or caller and callee in a bridged-call scenario) is straightforward to start. What's trickier is how to store the recording.
Recording Caller/Callee situations
Starting recording
JSON
{
"module": "record_call",
"data": {
"action": "start",
"time_limit": 1200,
"format": "mp3",
"url": "http://your.server.com/recordings"
}
}
This will start the call recording, limiting it to 1200 seconds, and will encode the audio into an MP3 file (alternatively, you can use "wav"). The url
is where the resulting file will be sent via an HTTP PUT request. It is then up to the receiving server to properly handle the request and store the file for later use.
Note: time_limit
is constrained by the system_config/media
doc's max_recording_time_limit
entry (default is 10800 seconds). If your recordings are not long enough, that is the setting that needs increasing.
Note: url
will be used as the base URL for the resulting PUT. The final URL will be URL/call_recording_CALL_ID.EXT
where URL
is the supplied URL, CALL_ID
is the call ID of the A-leg being recorded, and EXT
is the format
parameter.
Note: If url
is not provided, VirtualPBX will determine if call recoriding is configured on the account. If not configured, the recording will not be stored. If configured, VirtualPBX will store the recording based on the configuration.
Stop recording
If you need to programmatically stop the current recording (vs implicitly when the call ends):
JSON
{
"module": "record_call",
"data": {
"action": "stop"
}
}
Sample Recording Per User
JSON
{
"module": "record_call",
"data": {
"action": "start",
"format": "mp3",
"url": "http://my.recording.server/{ACCOUNT_ID}/{USER_ID}",
"time_limit":360
},
"children": {
"_": {
"module": "user",
"data": {
"id": "{USER_ID}"
},
"children": {
"_": {
"module": "record_call",
"data": {
"action": "stop"
},
"children": {
"module": "voicemail",
"data": {
"id": "{VMBOX_ID}"
},
"children": {}
}
}
}
}
}
}
Note: Call recording and Voicemail do not play well together. You will need to stop the recording before voicemail to avoid conflict.
Recording just the caller
This action is more appropriate for recording just the caller (think voicemail or recording menu prompts).
JSON
{
"module": "say",
"data": {
"text": "Please leave your message after the beep"
},
"children": {
"_": {
"module": "record_caller",
"data": {
"format": "mp3",
"url": "http://my.recording.server/voicemail/{ACCOUNT_ID}/{BOX_ID}",
"time_limit":360
}
}
}
}
Receiving a recording
Here is a simple PHP/.htaccess
combo for receiving a recording.
- Assume
url
in ourdata
object is "http://your.server.com/kzr" - Create a
.htaccess
file in the DocumentRoot. This will direct the request to/kzr/index.php
with therecording
query string parameter set toCALL_ID.EXT
.
Plain Text
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^kzr/call_recording_(.+)\.(.+)$ kzr/index.php?recording=$1.$2 [QSA,L]
</IfModule>
- Create
/kzr/index.php
to receive and store the recording.
PHP
<?php
/* PUT data comes in on the stdin stream */
$putdata = fopen("php://input", "r");
$r = $_REQUEST["recording"];
/* Open a file for writing */
$fp = fopen("/tmp/$r", "w");
/* Read the data 1 KB at a time and write to the file */
while ($data = fread($putdata, 1024))
fwrite($fp, $data);
/* Close the streams */
fclose($fp);
fclose($putdata);
?>
A file should be created in /tmp/
named CALL_ID.EXT
. You could, of course, store this in MySQL, Postgres, S3, feed it to a transcription service, etc.
Say
Text-to-speech is an easy way to read text to the caller
Play sample
The initial VirtualPBX JSON to
PHP
<?php
header('content-type:application/json');
?>
{
"module": "tts",
"data": {
"text": "Pivot is pretty awesome. Have a great day."
},
"children": {
"_": {
"module": "response",
"data": {}
}
}
}
Here the TTS engine will read the text to the caller and then hang up.
← Previous
Add link here
Next →
Add link here
On this page
- About Pivot
- Use Cases
- Example Callflow
- Debugging
- Summary
- Specific Call
- Failback
- The Request Payload
- Payload
- JSON Overview
- Building Time-based routing
- Example of Building on Pivot
- Build main_number_tod.php
- TwiML Format
- Core Supported
- Default Request Data included
- Optional/Conditional Request Data
- TwiML Verbs
- Custom Verbs
- Core TwiML Nouns
- Custom Nouns
- Bridging
- Devices
- Users
- Ring Group
- Dialing outside the account
- Using Global resources
- Using Local resources
- Collect
- Collecting DTMF
- Processing collected DTMF
- Conferencing
- Pre-built example
- Ad-Hoc example
- Conference Per Area Code
- DTMF
- Example
- Hang Ups
- Example
- Play
- Play sample
- Presence
- Example
- Recording
- Recording Caller/Callee situations
- Starting recording
- Stop recording
- Sample Recording Per User
- Recording just the caller
- Receiving a recording
- Say
- Play sample