Skip to content

Call Control

This page documents the Call Control endpoint.

For shared REST API environment and authentication requirements, see REST API Overview.

Note

  • Click-to-dial can only be initiated by the user that owns the specified user extension. If the authenticated user does not own the extension, a 403 Forbidden response will be returned.
  • Hangup can only be performed by an active call participant. If the authenticated user is not a participant in the specified call leg, a 403 Forbidden response will be returned.

Endpoints

Call Control

Base path: /v3/call_control/

Rate limit scope: "call-control"

Click to Dial

Initiates a call from the specified user extension to a destination number or extension.

How Click to Dial works

Click to Dial is a two-step process. When you trigger a call, the system does not immediately dial the destination. Instead:

  1. You click "Call" next to a contact or phone number.
  2. The system first rings your own phone — you will see a message like "(pick up to call)".
  3. You pick up your phone.
  4. Only after you answer, the system automatically dials the destination number and connects you.
POST /v3/call_control/
curl --include --request POST \
  --header "Content-Type: application/json" \
  --header "Authorization: Bearer {token}" \
  -d '{"command": "click-to-dial", "user": {"tenant_id": "b11d749b-8eb7-4236-a068-3d94ba3860d6", "user_extension": "210"}, "payload": {"destination": "200"}}' \
https://api-eu-central-1.phoneserver.dev/v3/call_control/
import requests
import json

# Prepare headers
headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer {token}"
}

# Prepare data
myDict = {
    "command": "click-to-dial",
    "user": {
        "tenant_id": "b11d749b-8eb7-4236-a068-3d94ba3860d6",
        "user_extension": "210"
    },
    "payload": {
        "destination": "200"
    }
}

# Send the request
response = requests.post('https://api-eu-central-1.phoneserver.dev/v3/call_control/', headers=headers, json=json.dumps(myDict))
<?php
// Create a new cURL resource
$ch = curl_init();

// Prepare headers
$headers = array();
$headers[] = 'Content-Type: application/json';
$headers[] = 'Authorization: Bearer {token}';

// Set the header
curl_setopt($ch, CURL_HTTPHEADER, $headers);

// Indicate URL
curl_setopt($ch, CURLOPT_URL, 'https://api-eu-central-1.phoneserver.dev/v3/call_control/');

// Return the transfer as a string 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

// Indicate the method
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

// Prepare data
$data = array(
    "command" => "click-to-dial",
    "user" => "{'tenant_id': 'b11d749b-8eb7-4236-a068-3d94ba3860d6', 'user_extension': '210'}",
    "payload" => "{'destination': '200'}",
};
$fields = json_encode($data);

// Set the body
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);

// Send the request
$result = curl_exec($ch);
curl_close($ch);
using (var httpClient = new HttpClient())
{
    // Prepare request
    using (var request = new HttpRequestMessage(new HttpMethod("POST"), "https://api-eu-central-1.phoneserver.dev/v3/call_control/"))
    {
        // Prepare headers
        request.Headers.Add("Content-Type", "application/json");
        request.Headers.Add("Authorization", "Bearer {token}");

        // Prepare body
        request.content = JsonContent.Create(new { 
            command = "click-to-dial"
            user = "{'tenant_id': 'b11d749b-8eb7-4236-a068-3d94ba3860d6', 'user_extension': '210'}"
            payload = "{'destination': '200'}"
        });

        // Send response
        var response = await httpClient.SendAsync(request);
    }
}
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.MediaType;

// Prepare body
String jsonString = "{\"command\": \"click-to-dial\", \"user\": {\"tenant_id\": \"b11d749b-8eb7-4236-a068-3d94ba3860d6\", \"user_extension\": \"210\"}, \"payload\": {\"destination\": \"200\"}}";

Client client = ClientBuilder.newClient();
Response response = client.target("https://api-eu-central-1.phoneserver.dev/v3/call_control/")
  .request(MediaType.APPLICATION_JSON_TYPE)
  .header("Content-Type", "application/json")
  .header("Authorization", "Bearer {token}")
  .post(Entity.json(jsonString));
const Http = new XMLHttpRequest();
Http.open("POST", "https://api-eu-central-1.phoneserver.dev/v3/call_control/");

// Prepare headers
Http.setRequestHeader("Content-Type", "application/json");
Http.setRequestHeader("Authorization", "Bearer {token}");

// Prepare data
const json = {
    "command": "click-to-dial",
    "user": {
        "tenant_id": "b11d749b-8eb7-4236-a068-3d94ba3860d6",
        "user_extension": "210"
    },
    "payload": {
        "destination": "200"
    }
}

// Send the request
Http.send(JSON.stringify(json));

// Handle the response
Http.onreadystatechange = (e) => {
    console.log(Http.responseText)
}
Request body
Field Type Required Description
command string Yes Must be "click-to-dial".
user.tenant_id string Yes The tenant ID of the user initiating the call.
user.user_extension string Yes The extension of the user initiating the call.
payload.destination string Yes The destination to call. Can be an extension (e.g. "200") or a phone number (e.g. "+32470123456").
Example

Using values from Example 4 — Answered, Internal Call in the Webhooks documentation, extension 210 (Jane Doe) initiates a click-to-dial to extension 200 (John Doe):

{
  "command": "click-to-dial",
  "user": {
    "tenant_id": "b11d749b-8eb7-4236-a068-3d94ba3860d6",
    "user_extension": "210"
  },
  "payload": {
    "destination": "200"
  }
}

This call will produce the call events documented in Example 4, where each event contains an id field that serves as the call_leg_id for the Hangup command below.

Error responses

403 Forbidden when the authenticated user does not own the specified user extension.

Hangup

Terminates an active call leg for the specified user.

POST /v3/call_control/
curl --include --request POST \
  --header "Content-Type: application/json" \
  --header "Authorization: Bearer {token}" \
  -d '{"command": "hangup", "user": {"tenant_id": "b11d749b-8eb7-4236-a068-3d94ba3860d6", "user_extension": "210"}, "payload": {"call_leg_id": "b6b87eb5-04e4-4838-81b6-fe6bfa6936bf"}}' \
https://api-eu-central-1.phoneserver.dev/v3/call_control/
import requests
import json

# Prepare headers
headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer {token}"
}

# Prepare data
myDict = {
    "command": "hangup",
    "user": {
        "tenant_id": "b11d749b-8eb7-4236-a068-3d94ba3860d6",
        "user_extension": "210"
    },
    "payload": {
        "call_leg_id": "b6b87eb5-04e4-4838-81b6-fe6bfa6936bf"
    }
}

# Send the request
response = requests.post('https://api-eu-central-1.phoneserver.dev/v3/call_control/', headers=headers, json=json.dumps(myDict))
<?php
// Create a new cURL resource
$ch = curl_init();

// Prepare headers
$headers = array();
$headers[] = 'Content-Type: application/json';
$headers[] = 'Authorization: Bearer {token}';

// Set the header
curl_setopt($ch, CURL_HTTPHEADER, $headers);

// Indicate URL
curl_setopt($ch, CURLOPT_URL, 'https://api-eu-central-1.phoneserver.dev/v3/call_control/');

// Return the transfer as a string 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

// Indicate the method
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

// Prepare data
$data = array(
    "command" => "hangup",
    "user" => "{'tenant_id': 'b11d749b-8eb7-4236-a068-3d94ba3860d6', 'user_extension': '210'}",
    "payload" => "{'call_leg_id': 'b6b87eb5-04e4-4838-81b6-fe6bfa6936bf'}",
};
$fields = json_encode($data);

// Set the body
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);

// Send the request
$result = curl_exec($ch);
curl_close($ch);
using (var httpClient = new HttpClient())
{
    // Prepare request
    using (var request = new HttpRequestMessage(new HttpMethod("POST"), "https://api-eu-central-1.phoneserver.dev/v3/call_control/"))
    {
        // Prepare headers
        request.Headers.Add("Content-Type", "application/json");
        request.Headers.Add("Authorization", "Bearer {token}");

        // Prepare body
        request.content = JsonContent.Create(new { 
            command = "hangup"
            user = "{'tenant_id': 'b11d749b-8eb7-4236-a068-3d94ba3860d6', 'user_extension': '210'}"
            payload = "{'call_leg_id': 'b6b87eb5-04e4-4838-81b6-fe6bfa6936bf'}"
        });

        // Send response
        var response = await httpClient.SendAsync(request);
    }
}
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.MediaType;

// Prepare body
String jsonString = "{\"command\": \"hangup\", \"user\": {\"tenant_id\": \"b11d749b-8eb7-4236-a068-3d94ba3860d6\", \"user_extension\": \"210\"}, \"payload\": {\"call_leg_id\": \"b6b87eb5-04e4-4838-81b6-fe6bfa6936bf\"}}";

Client client = ClientBuilder.newClient();
Response response = client.target("https://api-eu-central-1.phoneserver.dev/v3/call_control/")
  .request(MediaType.APPLICATION_JSON_TYPE)
  .header("Content-Type", "application/json")
  .header("Authorization", "Bearer {token}")
  .post(Entity.json(jsonString));
const Http = new XMLHttpRequest();
Http.open("POST", "https://api-eu-central-1.phoneserver.dev/v3/call_control/");

// Prepare headers
Http.setRequestHeader("Content-Type", "application/json");
Http.setRequestHeader("Authorization", "Bearer {token}");

// Prepare data
const json = {
    "command": "hangup",
    "user": {
        "tenant_id": "b11d749b-8eb7-4236-a068-3d94ba3860d6",
        "user_extension": "210"
    },
    "payload": {
        "call_leg_id": "b6b87eb5-04e4-4838-81b6-fe6bfa6936bf"
    }
}

// Send the request
Http.send(JSON.stringify(json));

// Handle the response
Http.onreadystatechange = (e) => {
    console.log(Http.responseText)
}
Request body
Field Type Required Description
command string Yes Must be "hangup".
user.tenant_id string Yes The tenant ID of the user performing the hangup.
user.user_extension string Yes The extension of the active call participant.
payload.call_leg_id string Yes The call leg ID to hang up. This is the same as the id field in call events.
Example

Using values from Example 4 — Answered, Internal Call, extension 210 (Jane Doe) hangs up the leg (id: b6b87eb5-04e4-4838-81b6-fe6bfa6936bf):

{
  "command": "hangup",
  "user": {
    "tenant_id": "b11d749b-8eb7-4236-a068-3d94ba3860d6",
    "user_extension": "210"
  },
  "payload": {
    "call_leg_id": "b6b87eb5-04e4-4838-81b6-fe6bfa6936bf"
  }
}

Tip

The call_leg_id corresponds to the id field in call events. For example, in Example 4, extension 210 has call leg ID b6b87eb5-04e4-4838-81b6-fe6bfa6936bf and extension 200 has call leg ID e8461f72-e479-44bd-a312-c51447925a96. Either participant can hang up by using their own call leg.

Error responses

403 Forbidden when the authenticated user is not an active participant in the call.