Skip to main content

Click to Dial

Single Request Click-to-dial

Calls can also be initiated from the PBX using five different types of web links.

  1. Vodia apps: If the user has the Vodia app, you can just use the vodia scheme to initiate the call:

This requires a recent version of the Vodia app (e.g. 1.22 on iOS). The dial parameter contains the number to dial and you may include a name, if available. The advantage of this scheme is that is is clear that the Vodia app should be used and there is no need to include any authentication information.

  1. Links without session and password: When the PBX sends out emails, it includes links that contain the number to dial and the account to use, but not the authentication information. These links have the following form:

The parameter "user" identifies the extension that should initiate the call. The parameter "dest" indicates which number should be dialed. The PBX will challenge the user, and the user must answer the challenge with the username and the password. The username must contain the tenant (e.g. "") if there are several tenants on the system.

  1. Links without session but with an md5 hash of the concatenation of username, password, destination number, current time in seconds (since 01/01/1970) and duration in seconds for which it is valid:

The parameter "user" identifies the extension that should initiate the call. The parameter "extension=true" makes sure the pbx uses the kind of authentication used here. The parameter "dest" indicates which number should be dialed. The parameter "auth" is used for authentication purposes. It is an md5 hash and calculated as MD5(user + pass + dest + time + duration), without any seperators between the strings. Every parameter must be URL-encoded, including the resulting hash.

  1. Links without session but with password: In some situations, you cannot answer the challenge from the PBX. For example, if you have a script that makes the PBX start a call, you want to include the credentials in the link. These links have the following form:

The parameter "user" identifies the extension ( above) that should initiate the call. The parameter "dest" indicates which number should be dialed (like above). The parameter "auth" is used for authentication purposes. It must have the form "" and it must be base64-encoded. So, the base64 encoding of "" is MTIzQGRvbWFpbi5jb206cGFzc3dvcmQ as shown. The whole url should be url_encoded as shown above.

  1. Links without 'press 1' prompt: In some situations, you want to be connected to the destination without having to listen the message 'press 1 to continue' and pressing the digit '1'. You can use 'connect=true' parameter as shown below to achieve this.

Using the REST API

If you want to use the REST API instead, you can use POST a request to the calls API:

fetch(`${server}/rest/user/${undn}/calls`, {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify({ "remote-dial": number, name })

undn is the username and the tenant (domain) name, put together with the @, for example The server variable contain the address for the user. The variables name and number contain the name and the number of the destination. The API request needs to be authenticated, e.g. with Basic authentication from an admin account that has permission to accept API commands or by a session variable. If you want to use Basic authentication, you will need to add another header to the fetch.

Click-to-dial Embedded into HTML

If you have control over the content in the web page and you want to use the browser instead of the app through teh Vodia scheme, you can embed an more elaborate click to dial where you don't have to expose the password of the user and also offer a hangup button. It would also be possible to add more controls, for example a hold button. It is important to note that this way of doing click to dial will assume that there is a user of the PBX sitting in front of the web browser. It is not suitable for generating callbacks to the public.

In order to make this happen, we embed an element that is easy to find after the HTML document has loaded:

<pbxcall number="6173998147">6173998147 <button name="call">Call</button><button name="cancel" style="display:none">Cancel</button></pbxcall>

The tag name "pbxcall" could be anything, however we will use that tag name later in the JavaScript code that needs to be loaded in order to set everything up. The attribute "number" will tell the script what number to use. There must be a button with the name attribute "call" and there may be a button with the name attribute "cancel" if you want to offer the cancellation of a call. There may be more than one pbxcall elements on a page, however the script will cancel only the last call. The styling of the element if completely up to the web designer.

The JavaScript that looks for the pbxcall elements needs to be included in the page for example with a <script type="text/javascript" src="/js/callback.js"></script> element. The script does not do anything unless the user clicks on a dial button. After that it fetches credentials from the REST API of the web server and then uses the token provided by it to log in to the PBX, set up a cookie for that connection and then starts a WebSocket connection to the PBX that is used for the remote control of the users phone. It dials the number provided in the number attribute, and if the cancel button is available offers to cancel the call after it was started. It looks like this:

window.addEventListener('load', () => {
let socket, pbxcall, number
let rid // Refresh ID
const pending = []

// The numbers to be called and their call-ID
let callid = ''
const open = (server, domain, user) => {
socket = new WebSocket(`wss://${server}/websocket?domain=${encodeURIComponent(domain)}&user=${encodeURIComponent(user)}`)
socket.onopen = () => {
while (pending.length) send(pending.shift());
socket.onclose = () => {
console.log("Connection closed")
socket = false
socket.onerror = evt => {
console.log('Error ' +
// The central message dispatch function for incoming websocket frames:
socket.onmessage = evt => {
const msg = JSON.parse(
console.log("received msg " + JSON.stringify(msg))
if (msg.action == 'call-state') {
for (let i = 0; i < msg.calls.length; i++) {
const call = msg.calls[i]
if (call['to-number'] == number) {
callid = call['id']
// Can we offer the cancel button?
const cancelbutton = pbxcall.querySelector('[name="cancel"]')
if (cancelbutton && callid) = '';

// Send something, can be a string or otherwise will be converted into string:
const send = message => {
if (typeof message == 'string') socket.send(message)
else socket.send(JSON.stringify(message))

const refresh = () => {
rid && clearTimeout(rid)
rid = setTimeout(refresh, reconnectTimer / 2)
socket.doSend({ action: 'wskeepalive' })

const call = (server, domain, user) => {
!socket && open(server, domain, user)
pending.push({ action: 'get-calls' })
pending.push({ action: "make-call", to: number })

const dial = async element => { = 'none'
pbxcall = element.parentNode
while (pbxcall && pbxcall.tagName != 'PBXCALL') pbxcall = pbxcall.parentNode;
const info = await (await fetch('/rest/pbxlogin')).json()
const session = await (await fetch(`https://${info.server}/rest/system/session`, {
method: 'POST',
body: JSON.stringify({name: 'session', value: info.session})
number = pbxcall.attributes['number'].value
call(info.server, info.domain, info.user)

const cancel = element => {
if (callid) { = 'none'
send({ action: "clear-call", id: String(callid) })
callid = ''

// Find all the elements that should be clickable:
const elements = document.getElementsByTagName('pbxcall')
for (let i = 0; i < elements.length; i++) {
const element = elements[i]
if (element.attributes.number) {
const callbutton = element.querySelector('[name="call"]')
callbutton && callbutton.addEventListener('click', even => {
const cancelbutton = element.querySelector('[name="cancel"]')
cancelbutton && cancelbutton.addEventListener('click', event => {
else {
console.log("Element has no number attribute")

In order to get the session token needed from the PBX, there needs to be a back-end code that fetches that token from the PBX. This is done using the third-party login REST API of the PBX. The credentials for that step are stored server-side and are not exposed to the user. In our example we just use the credentials for a system administrator of the PBX. In PHP, it could look like this:

// Get a login token for the PBX
$pbxuser = 'admin';
$pbxpass = 'bigsecret';
$pbxadr = '';
$username = '123';
$domain = '';
$data = array(
'name' => '3rd',
'domain' => $domain,
'username' => $username
$options = array(
'http' => array(
'method' => 'POST',
'content' => json_encode($data),
'header' => "Authorization: Basic " . base64_encode($pbxuser . ':' . $pbxpass) . "\r\n" .
"Content-Type: application/json\r\n" .
"Accept: application/json\r\n"
$url = 'https://' . $pbxadr . '/rest/system/session';
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
$sessionid = json_decode($result);
header('Content-Type: application/json');
$info = array(
'session' => $sessionid,
'server' => $pbxadr,
'user' => $username,
'domain' => $domain

It is important to check if the user has really logged in. In a CRM system this should be easy as there is already a user logged into the page. This is important because the front end does not have any other authentication and the fron end user could in theory use the token to make calls to any destination. It might also be necessary to include CORS headers; however in our example it worked right away without those headers.