Skip to main content

Integrations Framework

Scope

Integrating the Vodia PBX with other software can significantly increase the value for businesses. Integrations are available from version 69 on.

Examples include

  • Logging calls with customers
  • Opening pop-up screens for faster user interaction
  • Tracking user activity on timesheet software

The framework for this contains the following parts:

  • Backend code. This code handles the interaction with other software.
  • User frontend code. This part collects the neccessary settings from the user front end and stores them in the user settings. This might include a username for a CRM system and a password.
  • Tenant frontend code. This part does the same for tenant-level information. This might contain a default CRM account, or credentials to push information into a CRM system.

A single tenant may use multiple integrations at the same time. For example while one set of extensions may use a CRM system, another set might be using a trouble ticket system.

Integration Definition

Integrations are managed through the system level web page. An integration must have a name for display purposes, for example "Our CRM System". An integration must also have a identifier that can be used to uniquely identify the integration in the front end and backend in JavaScript and in the HTML code, for example "ourcrmsys". A safe way to choose an identifier is to stick to lower case alphanumeric characters.

Tenant code

The tenant code will be used in the tenant administration web interface to collect the information needed for the integration. It consists of three text fields:

  • HTML. This code will be inserted in the accordion when the tenant administrator selects the integration. It is not a independent web page; it is just a part of the page and typically starts with a div tag.
  • CSS. This piece contains the stylesheet information required for properly displaying the HTML code. In many cases this part can remain empty.
  • JavaScript. This code will take care about presenting and collecting the information for the integration. Because it runs in the browser, all modern browser features can be used.

HTML example

The following example collects a token and a password from the tenant administrator:

<div class="row">
<div class="form-group has-feedback">
<label class="col-sm-6 control-label">[[dom_actionurl#user]]</label>
<div class="col-sm-6">
<input class="form-control" type="text" name="token" pattern="[a-z]+">
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
</div>
</div>
</div>
<div class="row">
<div class="form-group has-feedback">
<label class="col-sm-6 control-label">[[dom_actionurl#pass1]]</label>
<div class="col-sm-6">
<input class="form-control" type="password" name="password">
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
</div>
</div>
</div>

The HTML code is using two rows in div tags according to Bootstrap, the framework that the tenant code is using. As with the other web pages it uses language items embedded in [[ and ]] tags. For many integrations there will sufficient to just use plain text without the need to translate them into multiple languages.

If there would be any need to style elements, that styling can be either done inline or in the CSS part of the integration. It is suggested to use classnames that start with the integration identifiert to avoid naming conflicts in the web page, for example ourcrmsys-token.

JavaScript example

In order to load the part correctly, there is a piece of JavaScript that will parse the settings and put them into the right place. For saving there is another piece of code that will encode the settings. The following example illustrates how this works:

// Load the public information:
export const load = (form, data) => {
form.querySelector('[name="token"]').value = data.token || ''
form.querySelector('[name="password"]').value = ''
}

// Save returns an array with the public and private information:
export const save = (form) => {
return [
{
token: form.querySelector('[name="token"]').value
},
form.querySelector('[name="password"]').value
]
}

The script must have two entries:

  • load. The load entry must be a function that takes the form element and the settings for the integration. The form element can be used to easily find the right fields in the HTML code. In order to avoid conflicts with other integrations, the elements should not use id. For example name tags are better to avoid problems. The data argument contains the data that was stored for the integration with that tenant. It is guaranteede to be an object even if it is not available. Because the object may have no data, it is important to provide default values when loading the form. Sensitive information like passwords are not loaded and must be cleared when this functions is being called.
  • save. The save entry is used to store input from the tenant administrator. It takes like the load entry the form as argument. It returns an Array, where the first entry is the public information and the second entry the sensitive information for the integration. If there is no sensitive information, the function can return just the public information (unless it is an Array).

User frontend code

The user front end consists like the tenant code of three parts: HTML code, CSS code and JavaScript. The main difference is that in the front end, Bootstrap and jQuery are not available.

HTML example

The following example shows how to present a username prompt in the part for the integration:

<pbx-setting-wrapper>
<pbx-label>[[usr_crm#user]]</pbx-label>
<pbx-input class="form-control" type="text" name="username" />
</pbx-setting-wrapper>
<pbx-setting-wrapper>
<pbx-label>[[usr_crm#pass1]]</pbx-label>
<pbx-input class="form-control" type="password" name="password" />
</pbx-setting-wrapper>
<pbx-setting-wrapper>
<pbx-label>[[usr_crm#addr]]</pbx-label>
<pbx-input class="form-control" type="text" name="address" />
</pbx-setting-wrapper>
<pbx-button-wrapper>
<pbx-button type="submit" appearance="contained" icon="fa-regular fa-floppy-disk" class="save">[[save]]</pbx-button>
</pbx-button-wrapper>

The styling is done by the general user front end style. The easiest way to have styling consistent with other user input elements is to look at those templates.

JavaScript example

The following example shows how to present a username prompt in the part for the integration:

//
// Example integration code for user level
//

// Load the content of the form
export const load = (form, data) => {
form.querySelector('[name="username"]').value = data.username || '';
form.querySelector('[name="address"]').value = data.address || '';
}

// Generate the content that should be saved when the user hits the save button:
export const save = (form) => {
// Public information
const pub = {
username: form.querySelector('[name="username"]').value,
address: form.querySelector('[name="address"]').value
}

// Private information
const pri = {
password: form.querySelector('[name="password"]').value
}

// Write the password only if the user has entered something:
if (pri.password) return [ pub, pri ];
return pub;
}

The JavaScript code structure is similar to the structure in the tenant part. The integration object is in pbx.integrations.

Backend code

The backend code is executed on the server. Because this is not a browser environment, there is only a smaller functionality available, for more details see Backend JavaScript. The following lines show an example of backend code:

function ongroup(data) {
console.trace('SCRIPT', 5, data.domain, system.format("Call hung up with data %s", JSON.stringify(data)));
}

function onhttp(data, callback) {
console.trace('SCRIPT', 5, data.domain, system.format("Received http request with data %s", JSON.stringify(data)));
callback(200, "Ok", "application/json", "true");
}

function ongroup(data)

The ongroup function is called when there is a change to a group (queue or ring group). The data object contains the following entries:

  • domain: An object that references the tenant. It contains the address (e.g. "tenant1.pbx.com"), the name (e.g. "ACME trash removal") and the id for the tenant (e.g. 3).
  • state: The state of the call, e.g. "ringing".
  • timestamps: Timestamps for the main events of the call, e.g. "start", "connect" or "end" in seconds since 1970.
  • inbound: A boolean value that indicates if this was an inbound call.
  • ringing (optional): A list of the agents that are currently ringing. For each agent, the system uses an object that includes the number (e.g. "41"), a name (e.g. "Joe Doe") and the idfor the agent (e.g. 3).
  • missed (optional): A list of agents that missed the call, in the same format like ringing.
  • agent (optional): The agent that is connected.
  • caller: The information about the caller as an object with the number and the name.
  • callee: The information about the callee as an object with the number and the name.
  • group: Information about the group as an object, includnig the type of the call, the number and the name.
  • reason (optional): If the call was disconnected, additional information as object including the disconnect code (e.g. 487) and the human readable text for the code (e.g. "Disconnected").
  • callid: The call-ID for the call which should be unique across all calls.

function onagent(data)

The onagent function is called when there is a change to an agent. The entries domain, state, timestamps, inbound, agent, caller, callee, reason, callid are the same as for the ongroup call. Only the group entry is different, it contains the number of the group.

function oncdr(data)

This function is called when the call has ended. It contains the following information about the call:

  • domain: The address of the tenant.
  • callid: The unique call-ID for the call.
  • from: The From header for the call.
  • to: The To header for the call.
  • cmc: The client matter code for the call (entered by the agent).
  • comment: A comment for the call (entered by the agent).
  • category: The category for the call (entered by the agent).
  • rating: The rating for teh call (entered by the caller).
  • start: The start timestamp.
  • connect: The time when the call connected (if it connected)
  • end: The end time stamp
  • recordings: An array containing objects about the recording for the call:
    • time: The time when the recording started.
    • file: The file location for the call
    • log: The log entry for accessing the recording (e.g. a manager started playback)
    • account: The group account for the recording (e.g. the queue)
    • extension: The extension that was connected
  • states: An array containing objects about the states the call went through:
    • from: The From header at that state of the call
    • to: The To header at that state of the call
    • language: The language code at that state of the call
    • code: The state code for the state (e.g. 200)
    • start: The start time stamp for the state
    • durationivr: The duration (in ms) how long IVR was played
    • durationring: The duration (in ms) how long ringback was played
    • durationtalk: The duration (in ms) how long the call was connected (including hold)
    • durationhold: The duration (in ms) how long the call was held
    • durationidle: The duration (in ms) how long the agent was idle before accepting the call
    • missed: An array if the extension ID that missed the call
    • reason: The reason code for redirecting the call
    • type: The state type (e.g. acd)
    • account: The account number for the group of the state
    • accountuser: The account ID for the group of the state
    • extension: The extension number for the group of the state
    • extensionuser: The extension ID for the group of the state
  • trunklegs: An array containing information about the trunks involved in the call:
    • direction: The direction of the trunk call
    • from: The From header at that state of the call
    • to: The To header at that state of the call
    • remoteparty: The remote party for the call (depending on the direction)
    • localparty: The local party for the call
    • trunk: The trunk that was involved
    • cost: The cost for the call
    • start: The start time stamp for the call
    • connect: The connect time stamp for the call (if connected)
    • end: The end time stamp for the call
    • code: The SIP response code for the call
    • ipadr: The IP address for the SIP packets from the trunk
    • quality: The quality report for the call
    • extension: The extension that triggered in the call
    • codec: The codec that was used for the call
    • mos: The MOS estimate for the call
  • extensionlegs: An array containing information about the extensions involved in the call:
    • callid: The call-ID for the call
    • from: The From header at that state of the call
    • to: The To header at that state of the call
    • direction: The direction of the trunk call
    • extension: The extension that was involved in the call
    • redirect: The redirect information for the call
    • idle: The idle duration for the agent
    • start: The start time stamp for the call
    • connect: The connect time stamp for the call (if connected)
    • end: The end time stamp for the call
    • ipadr: The IP address for the extension
    • quality:The quality report for the call
    • type: An indication is the call was missed and if the user has marked the call as read
    • deleted: A flag that indicates if the user marked this leg as deleted
    • ua: The User-Agent for the call
    • codec: The codec

function onhttp(data, callback)

This function is called when the system receives a request on HTTP/HTTPS that matches the prefix for the integration. The function must take one argument that contains an object with information about the method, the scheme, the body, the identifier for the integration, the url (which is the filename of the request), the domain (if available) and the headers (an array with the headers). This function must call the callback to deliver the result of the request. The callback can be asynchronous, e.g. after performing a query.

All of the above callbacks (except onhttp) take as argument an object with the following entries:

  • call: An object representing the current call.
  • leg: An object representing the current call leg.
  • user: The user ID. This is a integer number, not the name of the account.

Loading and saving state

The backend can load and save variables that are set from the frontend with the two following functions. These functions are only available in the integrations framework and will automatically store the domain or extension variable that is associated with the integration ID.

function loadIntr(domain, user, name, secret): This function can be used to load the value of the entry with the specific name. The username can be empty "" to access the domain data. If secret is true the private information will be retrieved, otherwise the public information. function saveIntr(domain, user, name, value, secret): This function can be used to save the value of the entry with the specific name.

Starting and ending calls

If there is a need to start or end a call, there are two functions available.

function startCall(args): This function starts a new call and returns the ID for the call. The args are an object with the following entries:

  • domain: This entry contains the DNS name or number of the tenant in which the call will be started.
  • user: This entry contains the alias name or number of the account that is starting the call. It is optional if the from entry is available.
  • from: This field contains number that starts the call. It is only required if there is no user entry.
  • ani: This optional entry explicitly set the ANI to be used for the call.
  • to: This entry defines the number to be called.
  • timeout: The timeout for the user to connect the call, the default is 60 seconds.
  • connect: When this entry is true, the system will attempt to automatically connect the user with the call.

function endCall(id, code, expl, forced): This function can be used to disconnect a call. The id argument is the ID returned from the startCall function. The code is the optional SIP code for ending the call, and the expl the optional disconnect text. If forced is true, the call will be disconnected forcefully. The default values are 200, "Request Terminated" and false.