Skip to main content
Version: 4.x

Custom Integrations

Scope

This page helps you to start using the Integration API functionality in order to add custom code to b+s Connects, by providing examples and ideas.

info

For more information please refer to the Integration API documentation.

Limitations

In order to send requests/receive events from b+s Connects, the Custom Integration code must be hosted within ServiceNow.

Requirements

In order to start implementing custom code the following requirements must be met:

Interface overview

The methods to invoke and subscribe to the Integration API are exposed by the class ConnectsIntegrationLibrary.

Requests

All requests on the ConnectsIntegrationLibrary return a promise. The data returned by the promise has the following structure:

FieldDescription
successBoolean indicating whether the request was successful or not
responseObject containing the data as outlined in the overview

Events

Multiple event handlers can be registered for the same event. The event data is described in the overview.

Setup the example integration

Step 1: Create a UI Page and insert the following code (adjust [reason code] with respective values).

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">

<head>
<meta charset="utf-8"></meta>
<title>Example of a Custom Integration</title>

<script src="https://connects.bucher-suter.com/snow/v4/stable/cnx.connectsIntegrationLibrary.min.js"></script>

<script>

const cil = ConnectsIntegrationLibrary;

console.warn(`message: waiting for initIntegration`);

cil.initIntegration(() => {
console.warn('message: initIntegration ready');
cil.onError((error) => console.warn('message: onError data: ' + JSON.stringify(error)));
cil.onAgentStateChange((data) => console.warn('message: onAgentStateChange, data: ' + JSON.stringify(data)));
cil.onWorkitemCreate((data) => console.warn('message: onWorkitemCreate, data: ' + JSON.stringify(data)));
cil.onWorkitemConnect((data) => console.warn('message: onWorkitemConnect, data: ' + JSON.stringify(data)));
cil.onWorkitemPause((data) => console.warn('message: onWorkitemPause, data: ' + JSON.stringify(data)));
cil.onWorkitemResume((data) => console.warn('message: onWorkitemResume, data: ' + JSON.stringify(data)));
cil.onWorkitemEnd((data) => console.warn('message: onWorkitemEnd, data: ' + JSON.stringify(data)));
cil.onQueueDataUpdate((data) => console.warn('message: onQueueDataUpdate, data: ' + JSON.stringify(data)));
cil.onWorkitemWrapup((data) => console.warn('message: onWorkitemWrapup, data: ' + JSON.stringify(data)));
cil.onCallVariableChanged((data) => console.warn('message: onCallVariableChanged, data: ' + JSON.stringify(data)));
cil.onWrapupDataChanged((data) => console.warn('message: onWrapupDataChanged, data: ' + JSON.stringify(data)));
});

async function getChannel() {

const result = await cil.getChannel('voice');

console.warn(result);

console.warn('message: getChannel result '+ JSON.stringify(result));

return result;

}

async function getReasonCodeList(reasonType) {

const channel = await cil.getChannel('voice');

cil.getReasonCodeList(
channel.response.channel.id,
reasonType
).then((data) => {
console.warn('message: getReasonCodeList ' + JSON.stringify(data));
});
}

async function setWrapupReason() {

const channel = await cil.getChannel('voice');

if (!channel.response.channel.workitems.length) {
console.error('The function setWrapupReason needs a workitem id as parameter. So a call is needed to use this button.');
return;
}

cil.setWrapupReason(
channel.response.channel.id,
channel.response.channel.workitems[0].id,
'Generic'
).then((data) => {
console.warn('message: setWrapupReason ' + JSON.stringify(data));
});
}

async function isUpdateWorkitemDataEnabled() {

const channel = await cil.getChannel('voice');

if (!channel.response.channel.workitems.length) {
console.error('The function isUpdateWorkitemDataEnabled needs a workitem id as parameter. So a call is needed to use this button.');
return;
}

cil.isUpdateWorkitemDataEnabled(
channel.response.channel.id,
channel.response.channel.workitems[0].id
).then((data) => {
console.warn('message: isUpdateWorkitemDataEnabled ' + JSON.stringify(data));
});
}

async function updateWorkitemData(requestId) {

const channel = await cil.getChannel('voice');

if (!channel.response.channel.workitems.length) {
console.error('The function updateWorkitemData needs a workitem id as parameter. So a call is needed to use this button.');
return;
}

cil.updateWorkitemData(
channel.response.channel.id,
channel.response.channel.workitems[0].id,
{ callVariable1: 'Updated value' },
requestId
).then((data) => {
console.warn('message: update workitem ' + JSON.stringify(data));
});
}

async function setAgentState(newState, reasonCode, requestId) {

const channel = await cil.getChannel('voice');

cil.setAgentState(
channel.response.channel.id,
newState,
reasonCode,
requestId
).then((data) => {
console.warn('message: setAgentState ' + JSON.stringify(data));
});
}

async function dialOrConsult(requestId) {

cil.dialOrConsult(
'voice',
document.getElementById('phoneNum').value,
requestId
).then((data) => {
console.warn('message: dial ' + JSON.stringify(data));
});
}

async function directTransfer(requestId) {

cil.directTransfer(
'voice',
document.getElementById('phoneNum').value,
undefined,
requestId
).then((data) => {
console.warn('message: directTransfer ' + JSON.stringify(data));
});
}

async function singleStepConference(requestId) {

cil.singleStepConference(
'voice',
document.getElementById('phoneNum').value,
undefined,
requestId
).then((data) => {
console.warn('message: singleStepConference ' + JSON.stringify(data));
});
}

async function isDtmfEnabled() {

cil.isSendDtmfEnabled().then((data) => {
console.warn('message: isSendDtmfEnabled ' + JSON.stringify(data));
});
}

async function sendDtmf(requestId) {

cil.sendDtmf(
document.getElementById('phoneNum').value,
requestId
).then((data) => {
console.warn('message: sendDtmf ' + JSON.stringify(data));
});
}

</script>
<style>
body {
margin: 0;
}

.wrapper {
padding: 2px;
display: flex;
flex-wrap: wrap;
}

button {
width: calc(100% - 4px);
margin: 2px;
background-color: #fff;
color: #333;
border: 1px solid #777;
cursor: pointer;
}

input {
width: calc(100% - 8px);
margin: 10px 2px 2px 2px;
}

</style>
</head>

<body>
<div class="wrapper">
<button onclick="setAgentState('ready', undefined, 'some rid3')">Ready</button>
<button onclick="setAgentState('notready', '[reason code]', 'some rid1')">Not Ready (Lunch)</button>
<button onclick="setAgentState('logout', '[reason code]', 'some rid2')">Logout (1) </button>
<button onclick="getChannel()">GetChannel</button>
<button onclick="getReasonCodeList('[reason type]')">Get Reason Code List</button>
<button onclick="setWrapupReason()">Set WrapUp Reason</button>
<button onclick="isUpdateWorkitemDataEnabled('some rid10')">Update Workitem Data enabled?</button>
<button onclick="updateWorkitemData('some rid9')">Update Workitem Data</button>
<input id="phoneNum" placeholder="Enter phone number or DTMF here"></input>
<button onclick="dialOrConsult('some rid4')">Call/Consult</button>
<button onclick="directTransfer('some rid5')">SST</button>
<button onclick="singleStepConference('some rid6')">SSC</button>
<button onclick="isDtmfEnabled('some rid7')">DTMF enabled?</button>
<button onclick="sendDtmf('some rid8')">Send DTMF</button>
</div>
</body>


</j:jelly>

Step 2: Save the customization.

Step 3: Add the corresponding HTTP Request Header for the saved UI page. For detailed instructions please read this chapter.

Step 4: Configure the Custom Integration in the Service Layout. For detailed instructions please read this chapter.

HTTP Response Header

info

ServiceNow role required: security_admin

In order to load the custom integration, an additional HTTP response header is needed. It can be created within the sys_response_header table.

The following values need to be set specifically:

FieldValue
ActiveTrue
Applies toSpecific Type
TypeUI Page [sys_ui_page]
RecordSelect the UI page that you want to integrate within the gadget.
NameContent-Security-Policy
Valueframe-ancestors 'self' https://connects.bucher-suter.com
Add byOverwrite *
OrderHigher value than existing entry for teams.microsoft.com *

* These values are edited in the table view, not in the editing form.

info

If multiple UI pages are created for custom integrations, one response header needs to be created per UI page.

caution

In the ServiceNow Utah release, the built-in HTTP response header for teams.microsoft.com comes preconfigured in the sys_response_header table. To avoid conflicts with b+s Connects custom integrations, the configuration must comply with the following:

  • The custom header for b+s Connects has a higher Order than the built-in header.
  • The custom header for b+s Connects is configured with the value Overwrite in the Add by column.

Service Layout Configuration

Up to four custom integrations can be configured following the structure below.

[
{
"location": "",
"url": "",
"title": "",
"height": ""
}
]

Location

The place where the custom integration should be loaded. Possible values are:

ViewWorkitemHomeHidden
Custom Integration ViewCustom Integration WorkitemCustom Integration Home
The custom integration is displayed in its own view.The custom integration is only visible on workitems.The custom integration is only visible on the Home screen.The custom integration is hidden and always active in the background.
info

Only one integration can be used per location. Additional integrations will be ignored.

info

The page timing information is added automatically in ServiceNow when using a UI page. If you choose the location View or Home, there may be an alarm clock symbol displayed at the bottom which cannot be removed.

URL

Where to load the custom integration from. The url can be absolute as well as relative (in case the name of the ui page is used here).

Title

The name of your custom integration. It will be used as a title when displaying the integration.

Height

The height of the iframe the custom integration is loaded into. If this property is not provided, a default value is used.

info

Make sure to set a value that is equal to or greater than the total height of your custom integration UI Page to avoid additional scrollbars. This value will be ignored if the custom integration is hidden or displayed in a view.