SIGN UP

Step-by-step call center tutorial

Step-by-step call center tutorial

This tutorial will walk you through the entire process of setting up a call center based on the VoxImplant platform from the very basics up to the advanced features. While many topics covered in this tutorial are also useful for other types of applications, we will focus on a call center as a popular environment that can be challenging to implement. You are welcome to check our other tutorials devoted to various use cases.

Handling an incoming call

To begin with, let’s review the simplest possible setup: answering an incoming call and playing a message to the caller automatically. Create an application as explained in the "Apps, Scenarios, Rules and Users" tutorial and handle an incoming call as explained in the "Receiving and handling calls in the cloud" tutorial.

Redirecting an incoming call to a web browser

Answering an incoming call automatically can be useful in some situations, but in the real world, you are most likely want to communicate with the caller and/or connect the caller with other people. So let’s improve the application created at the previous step by forwarding the user call to a web browser or mobile app. Such a solution is popular among the distributed call centers where operators typically work from home and accept and initiate calls via a web browser. To add the call handling features to a browser, we need two things. First, the browser should be connected to the Voximplant cloud to be able to accept an incoming call. Second, the browser should have a unique identifier to distinguish it among all the browsers connected to our cloud. Both tasks are solved via our web SDK. It allows any webpage to establish a real-time connection to the Voximplant cloud. Each connection is identified by the user name.

In the production version, you will definitely use the Web SDK to add voice and video capabilities to your web project. In this example, we will use a pre-built phone interface. Note that this interface is for demonstration purposes only and we cannot guarantee its uptime. Visit the above webpage and log in using the user name and password you have created previously. Use ‘callcenter’ as the name of the application you have already created at the previous step. Use the account name you have specified at the previous step when you registered at the VoxImplant control panel:

webphone_login

If you receive a login error, it most probably means that there are no applications associated with the user. To form an association, select User on the top menu, click Edit to the right of the user name, select the application from the Applications drop-down list and click Save to confirm the changes.  Note that checkbox "is active" have to be activated.

Logging in from a phone web app will establish a connection from the web browser to the VoxImplant cloud. After that, the webpage can initiate and receive calls. In this example, we will forward all incoming calls to the webpage. To solve this task, we will need to make a few changes in the existing scenario. First, we need to initiate an outgoing call. There are lots of ways how VoxImplant can initiate calls; in this tutorial, we will use the VoxEngine.callUser method that allows calling a user connected via the web or mobile sdk:

VoxEngine.addEventListener(AppEvents.CallAlerting, inc => {
  const out = VoxEngine.callUser("user", "callerid", "callername");
  out.addEventListener(CallEvents.Connected, () => {
    inc.call.startEarlyMedia();
    inc.call.say("outgoing call connected");
  });
});

Second, we need to answer the incoming call. In telephony, an incoming call can remain in the unanswered state for some time (we limit this time to 40 seconds). This is called the early media state. While the call remains in this state, it is not billed but audio can be sent only in one direction — to the caller. Normally, such state is used to play a progress tone while the call is being forwarded to another person. The process of adding a progress tone will be covered later in other tutorials. At this step, we will answer the incoming call and make sure that both parties can hear each other. The call is answered via the answer method; audio is sent in both ways via the VoxEngine.sendMediaBetween method. There are more fine-grained methods such as sendMediaTo, which we will examine later:

VoxEngine.addEventListener(AppEvents.CallAlerting, e => {
  const inc = e.call;
  const out = VoxEngine.callUser("user", "callerid", "callername");
  out.addEventListener(CallEvents.Connected, () => {
    inc.answer();
    VoxEngine.sendMediaBetween(inc, out);
  });
});

Call the rented phone number and the Incoming call popup window will appear on the webpage. Click Answer, and you will be able to hear the voice and answer back. This illustrates how calls can be redirected from phones to webpages.

Creating a call queue

The application we created at the previous step has a flaw: it’s unclear what to do if the second user calls while the first one is still talking to the operator. Our web and mobile SDK can accept multiple incoming calls (and the phone app supports two lines to demonstrate it), but a human operator can speak only with one person at a time. This can be solved by a call queue: while the operator speaks with a user, all other users are waiting in a queue, with optional waiting music being played and/or the synthesized message informing the users about the remaining waiting time.

VoxImplant provides the Call Queue abstraction to automatically handle a queue of incoming calls. To use a queue, you need to create a queue object. Open the VoxImplant control panel, select Settings on the top menu and click Queues. The queue control interface will be displayed. Click Add queue and enter the queue name. At this step, name the queue ‘main’ and click Add to confirm the operation.

queues

  joxi_screenshot_1491400636347

After the queue is created, it can be used instead of callUser to enqueue the calls to the call center. But if there’s more than one operator available, how to define which one will receive the call? For this purpose, we have a skill group concept that adds operators into a queue and assigns target operators to handle specific questions. For a call queue to work, you need to add a skill group and users to it. Open the VoxImplant control panel, select Settings on the top menu and click Skills. Click Create skill and enter the skill name. At this step, name the skill ‘general’, select the queue named ‘main’ and click Assign users to select the user you added at the previous step. Click Add to confirm the new skill creation. The call queue itself works like a filter: it allows to enqueue requests only to operators who have all the skills that are set for the specific queue. Queues also provide additional features like priority or maximum wait duration.

skills-menu

  joxi_screenshot_1491400826492

  Modify the code created at the previous step by using enqueueACDRequest instead of callUser and subscribing to the queue events:

require(Modules.ACD);

VoxEngine.addEventListener(AppEvents.CallAlerting, e => {
  const inc = e.call;
  const incId = e.callerid;
  inc.answer();
  inc.addEventListener(CallEvents.Connected, () => {
    const request = VoxEngine.enqueueACDRequest("main", incId);
    request.addEventListener(ACDEvents.OperatorReached, e => {
      const out = e.operatorCall;
      VoxEngine.sendMediaBetween(inc, out);
    });
  });
});

With the updated scenario, the second caller will not be connected to the operator until the first finishes his or her conversation. This simple example also has a few pitfalls that you need to keep in mind:

  • Voximplant web phone have special engines to work with the call queues. The operator needs to change his or her status to ‘ready’ to be able to receive calls. If the operator fails to answer the call, he or she becomes non-responsive and the status is changed to ‘banned’ for some time. Then the operator needs to change the status to ‘online’ and then to ‘ready’.
  • Up to this step, the scenario doesn’t have any code to handle user disconnections. So, if a user terminates a phone call, the operator will not be disconnected automatically. To accept the second user’s call, the operator needs to manually click Disconnect. At the next step, we will show how you can handle disconnections properly.

To troubleshoot the call queue, use the queue control panel interface. Click Refresh to the right of the queue row to see the number of users talking to operators, users waiting in the queue and number of operators. All Voximplant control panel features are also available via HTTP API

Handling a call disconnect

Since our cloud has separate "incoming" and "outgoing" call logs, we have full control over call disconnects. If a calling user or an operator disconnects, you can make decisions what to do next: play a notification message, call back, disconnect the remaining participants and so on. If the incoming or outgoing call is disconnected, the corresponding call object receives Disconnected event. Please note that an outgoing call can fail to connect (ex user not answered), in such case Failed event will be received instead of Disconnected.

A call can be disconnected by a remote user, by the JavaScript via the call.hangup method or via the VoxEngine.terminate method which will disconnect all calls and will terminate the JavaScript scenario. The scenario will also be automatically terminated after having no active calls for 60 seconds. Upon scenario termination, AppEvents.Terminating and AppEvents.Terminated events are fired. The Terminating event handler is normally used for scenario results reporting. This event is limited to single HTTP request (and every HTTP request is limited to 90 seconds). Terminated is the last event received by the scenario JavaScript code, it is normally used for log and debug purpose. It’s not possible to issue HTTP request from this event handler. Given all this information about different ways to disconnect calls and different events that are fired during such disconnects and scenario termination, let’s add "gracefull disconnect" to our call center. We will disconnect an operator if a user hangs up, and disconnect a user if an operator hangs up. Modify scenario code by adding Disconnected event handler:

require(Modules.ACD);

VoxEngine.addEventListener(AppEvents.CallAlerting, e => {
  const inc = e.call;
  const incId = e.callerid;
  let request = null;
  let out = null;
  inc.answer();
  inc.addEventListener(CallEvents.Connected, () => {
    request = VoxEngine.enqueueACDRequest("main", incId);
    request.addEventListener(ACDEvents.OperatorReached, e => {
      out = e.operatorCall;
      VoxEngine.sendMediaBetween(inc, out);
      out.addEventListener(CallEvents.Disconnected, () => {
        inc.hangup();
        // Some logic can be performed instead of session termination
        VoxEngine.terminate();
      });
    });
  });
  inc.addEventListener(CallEvents.Disconnected, () => {
    // If operator already connected, this also hangs up operator's call.
    request.cancel();
    // Some logic can be performed instead of session termination
    VoxEngine.terminate();
  });
});

Now if a user or an operator hangs up the call, other party will be gracefully disconnected. Please note, that you are not obliged to disconnect a call and can do any logic: call back, report some statistics, call other person etc. Flexibility of the Voximplant platform allows to implement any call handling scenario and is not limited to "A calls B, they speak and then the call terminates".

Waiting in a queue

Having a full control over an incoming and outgoing calls allows to implement any business needs. By default, a user will hear nothing while waiting in the queue, since we have not instructed VoxEngine to produce any audio. Good practice is to inform the user about his or her queue position and play some music to indicate the call in progress.

Let’s start by adding the background music by using the call object’s startPlayback method. In order to start background music, add the following source code:

inc.addEventListener(CallEvents.Connected, () => {
  const isLoop = true;
  inc.startPlayback("https://cdn.voximplant.com/bb_remix.mp3", isLoop);
  request = VoxEngine.enqueueACDRequest('main', incId);
  ...
});

Important note: outside of conference rooms, any call object can have only one audio source. So if you are using startPlayback and then say, the say method call will detach startPlayback audio and attach it’s own audio. After say finishes, audio from startPlayback will not be automatically attached back. Music automatically stops after operator answers and new audio source is connected to call via sendMediaBetween. One more thing to do is queue position notification. This is done by periodically calling a getStatus method of the queue request object and subscribing to the the ACDEvents.Waiting event. The event receives queue position info as position and ewt (estimated wait time) properties of an event handler parameter:

// This event only fired if request was queued without errors.
request.addEventListener(ACDEvents.Queued, () => request.getStatus());
request.addEventListener(ACDEvents.Waiting, e => {
  inc.say(`Queue position is ${e.position}`);
  inc.addEventListener(CallEvents.PlaybackFinished, () => {
    // Note that say() changes audio source, so we need to change it back,
    // but only if operator is not yet connected.
    if (!out) {
      const isLoop = false;
      inc.startPlayback("https://cdn.voximplant.com/bb_remix.mp3", isLoop);
    }
  });
  setTimeout(function() { request.getStatus(); }, 30000);
});

While that setup will work in most situations, where are so-called “edge cases” what can be gracefully handled. For example, if no operators are connected, it’s definitely not possible to queue a request. For such “error cases” ACD module fires a number of events:

It will be a good idea to handle such events and provide the caller with a meaningful way to reach an operator, like asking a permission for callback and passing phone number to your backend, so an operator can call back manually or automatically later. Complete source code for this step:

require(Modules.ACD);

VoxEngine.addEventListener(AppEvents.CallAlerting, e => {
  const inc = e.call;
  const incId = e.callerid;
  let request = null;
  let out = null;
  inc.answer();
  inc.addEventListener(CallEvents.Connected, () => {
    const isLoop = true;
    inc.startPlayback("https://cdn.voximplant.com/bb_remix.mp3", isLoop);
    request = VoxEngine.enqueueACDRequest('main', incId);
    // This event only fired if request was queued without errors.
    request.addEventListener(ACDEvents.Queued, () => request.getStatus());
    request.addEventListener(ACDEvents.Waiting, e => {
      inc.say(`Queue position is ${e.position}`);
      inc.addEventListener(CallEvents.PlaybackFinished, () => {
        // Note that say() changes audio source, so we need to change it back,
        // but only if operator is not yet connected.
        if (!out) {
          inc.startPlayback("https://cdn.voximplant.com/bb_remix.mp3", isLoop);
        }
      });
      setTimeout(() => request.getStatus(), 30000);
    });
    request.addEventListener(ACDEvents.OperatorReached, e => {
      out = e.operatorCall;
      VoxEngine.sendMediaBetween(inc, out);
      out.addEventListener(CallEvents.Disconnected, () => {
        inc.hangup();
        // Some logic can be performed instead of session termination
        VoxEngine.terminate();
      });
    });
  });
  inc.addEventListener(CallEvents.Disconnected, () => {
    // If operator already connected, this also hangs up operator's call.
    // Also cancels all events like ACDEvents.Waiting
    request.cancel();
    // Some logic can be performed instead of session termination
    VoxEngine.terminate();
  });
});

IVR and operator skills

In a simple scenario all incoming user calls are evenly distributed among all available operators. But as a number of users grows, it became more convenient to direct specific requests to specific operators who can handle them better. This raises a number of questions: how to figure out user question and how to define which operator can answer that questions.

First question is addressed by an IVR, “Interactive voice response” system, which asks a caller questions and interpret answers either as keypresses or via voice recognition. Such system can be written via JavaScript by using say and handleTones methods. Or you can use the IVR framework that is built into VoxEngine and allows to construct IVR menus from ready-made blocks. Second question is addressed by an “operator skill” concept. You can define a number of skills. Each skill is assigned to one or more operators and to one or more queues. After request is enqueued via enqueueACDRequest, it will reach only operators that have all the required skills. For the operator to be reached, following conditions must be met:

  1. Set of skills associated with the queue must be a subset of skills associated with the operator.
  2. Both queue and operator must be associated with same application.

The call queue itself works like a filter: it allows to enqueue requests only to operators who have all the skills that are set for the specific queue. Entire setup works in two steps: ask user questions, interpret answers and queue request for operators with appropriate skills. For demonstration purpose, we will implement a simple IVR that will ask “If you have a warranty question, press 1, otherwise press 2”. Selecting “1” from a phone keyboard will forward user to the operator with “warranty” skill. Selecting “2” will forward to any available operator. In order to use IVR module include it into your scenario CommonJS-style:

require(Modules.IVR);

IVR module works by creating a number of linked states and calling enter method on root state, passing it call object. At this point you should have a skill named "general" associated with queue "main" and one operator. Create a second queue "warranty", a second skill "warranty" and a second operator. Associate "general" skill with both "main" and "warranty" queues, while "warranty" skill only with "warranty" queue. That way, requests via "main" queue will reach any operators with "general" skill ("operator who can answer non-specific questions"), and requests via "warranty" queue will reach only operators that have both "general" and "warranty" skills ("operator can answer non-specific questions and questions about warranty). After queues are set, associate "general" skill with both operators and "warranty" skill only with second operator.  Complete call center code that will reach different operators based on user choice is listed here:

require(Modules.ACD);
require(Modules.IVR);

VoxEngine.addEventListener(AppEvents.CallAlerting, e => {
  const inc = e.call;
  const incId = e.callerid;
  let request = null;
  let out = null;
  inc.answer();
  inc.addEventListener(CallEvents.Connected, () => ivr());
  inc.addEventListener(CallEvents.Disconnected, () => {
    // If operator already connected, this also hangs up operator's call.
    // Also cancels all events like ACDEvents.Waiting
    request.cancel();
    // Some logic can be performed instead of session termination
    VoxEngine.terminate();
  });
  
  function ivr() {
    const stateWarranty = new IVRState("warranty", {
      type: 'noinput',
      prompt: {say: "Your warranty issue will be handled by first available expert"},
    }, function() {
      enqueue('warranty');
    });
    
    const stateGeneral = new IVRState("general", {
      type: 'noinput',
      prompt: {say: "Your issue will be handled by first available expert"},
    }, function() {
      enqueue('main');
    });
    
    const mainState = new IVRState("main", {
      type: 'select',
      prompt: {say: "If you have a warranty question, press 1, otherwise press 2"},
      nextStates: {
        '1': stateWarranty,
        '2': stateGeneral,
      },
    });

    mainState.enter(inc);
  }
  
  function enqueue(queue) {
    const isLoop = true;
    inc.startPlayback("https://cdn.voximplant.com/bb_remix.mp3", isLoop);
    request = VoxEngine.enqueueACDRequest(queue, incId);
    // This event only fired if request was queued without errors.
    request.addEventListener(ACDEvents.Queued, () => request.getStatus());
    request.addEventListener(ACDEvents.Waiting, e => {
      inc.say(`Queue position is ${e.position}`);
      inc.addEventListener(CallEvents.PlaybackFinished, () => {
        // Note that say() changes audio source, so we need to change it back,
        // but only if operator is not yet connected.
        if (!out) {
          inc.startPlayback("https://cdn.voximplant.com/bb_remix.mp3", isLoop);
        }
      });
      setTimeout(() => request.getStatus(), 30000);
    });
    request.addEventListener(ACDEvents.OperatorReached, e => {
      out = e.operatorCall;
      VoxEngine.sendMediaBetween(inc, out);
      out.addEventListener(CallEvents.Disconnected, () => {
        inc.hangup();
        // Some logic can be performed instead of session termination
        VoxEngine.terminate();
      });
    });
  }
});

Try to login both operators, call phone number and select "2". Request will be queued into "main" queue and will reach any operator since "general" skill is associated with "main" queue and both operators. Now call phone number and select "1". Request will be queued into "warranty" queue and will reach only second operator since only "warranty" skill is associated with "warranty" queue and that skill is also associated only with second operator. Tip: If you check "Settings" --> "Skills" you'll see 1 operator in "warranty" and 2 operators in "main" queue. joxi_screenshot_1491986618996

Operator status and temporary ban

While Voximplant ACD framework provides only essential call center backbone, it also has some out-of-the-box features for most popular use cases. One of such features is "Operator Status" that can be set to "Ready" if operator is available to handle a call, or any one of non-"Ready" if calls should not be forwarded to that operator.

All statuses except "Ready" and "Offline" works the same, different names can be used to track statistics. Web SDK is used to manage operator status. For example, status can be changed via setOperatorACDStatus method. In our demo web phone, operator status can be changed via a buttons row:

operator-status-buttons

Operator that is not logged in has an "Offline" status. After login, the status is automatically changed to "Online". If all operators has "Offline" status, queuing new request will fail with the ACDEvents.Offline event. Important: Since "Online" is not "Ready", all new logged-in operators will not receive any calls until they status is set to "Ready". This mechanic is implemented to protect new developers from accident calls toward operators who don't expect them. Status need to be explicitly set to "Ready" either by developer or operator. Another out-of-the-box feature is "automatic operator ban". If operator does not answer the call, such operator is marked as "banned" and will not receive calls for 12 hours. For non-answered call the ACDEvents.OperatorFailed event will be fired and call will be dispatched to the next available operator. This is the second "safety net" so calls are not constantly directed to operators who don't answer them. In order to "unban" operator, status should be changed to "Ready" from "non-Ready" via Web SDK API. With our phone.voximplant.com example app this is done by clicking on any non-"Ready" status button and then on the "Ready" status button. Queue info and operator status can be received via HTTP API:

curl "https://api.voximplant.com/platform_api/GetACDState?account_id=123456&api_key=12345678-1234-1234-1234-123456789012"

For example, with one "banned" operator and one call waiting, following response will be received:

{
   "result":{
      "acd_queues":[
         {
            "outgoing":{
               "waiting_call_count":0,
               "max_waiting_time":0,
               "servicing_calls":[],
               "servicing_call_count":0,
               "waiting_calls":[]
            },
            "waiting_call_count":1,
            "max_waiting_time":112,
            "acd_queue_name":"main",
            "ready_operators":[],
            "ready_operator_count":0,
            "locked_operators":[
               {
                  "unreached":"2016-11-21 11:47:34",
                  "user_id":170857,
                  "acd_calls":[],
                  "user_name":"user",
                  "locks":[],
                  "user_display_name":"user"
               }
            ],
            "servicing_calls":[],
            "servicing_call_count":0,
            "acd_queue_id":207,
            "waiting_calls":[
               {
                  "acd_session_history_id":87746,
                  "is_incoming":true,
                  "minutes_to_submit":719,
                  "waiting_time":112,
                  "acd_request_id":"1V4TTW8dREyxTc0DFvcQBwJdNWXvyEAqnmh4AvHEfQw",
                  "begin_time":"2016-11-21 11:46:32",
                  "callerid":"79262251608",
                  "priority":100
               }
            ]
         }
      ]
   }
}

Note the locked_operators list that contains info about unavailable operators. Operators in that list with unreached field are banned. Other HTTP API methods can be used to manage queue, collect statistics, etc.

Call lists, PDS and SIP

There are two ways how Voximplant can handle an outgoing call. First, the outgoing call can be initiated from JavaScript session created from the incoming call. That approach is used if you want to connect two or more people, forward call into a call center or implement any other "connect two persons" logic where and outgoing call is initiated as a response to the incoming call. Second, you can initiate outgoing call via the HTTP API. That approach is used if you want to initiate a call from your service or call multiple users. You can implement any call logic via HTTP API, but you also need to setup your own backend that will initiate calls, maintain the suitable number of simultaneous calls etc. For fast "out-of-the-box" experience we have a built-in "Call list" functionality that allows calling thousands of users with a few commands and without any backend of your own.

Call list can be created via CreateCallList HTTP API. Using this API you upload phone numbers and start the calling process with specified options. Each call will trigger specified VoxEngine scenario that can access number to call (alongside with any custom data) via VoxEngine.customData method. Outgoing call is performed via one of VoxEngine "call" methods, for example a VoxEngine.callPSTN method or any other from call... methods family. You can read more about call list at this article

Predictive dialing

Call list relies on a developer to manually use some call... method if connected call should be forwarded to an operator. With "predictive dialing" feature call list can be connected with automated call distribution system so they work in unison: Voximplant analyses speed at which calls are answered and initiate outgoing calls so call center load is maximized. This feature is automatically enabled if:

  1. Call list is created with queue name specified via queue_id argument.

  2. After operator is reached, reportProgress method should be called. This call is used to calculate operator reach speed and place outgoing calls in a timely manner.

Please note that predictive dialing works well only if call center has a suitable number of operators. Our internal tests show that reasonable number is 30 operators.

3rd party call control

Up to now, operators was connected to a platform via web SDK (web or mobile). In real life scenarios, operators can be connected via software or hardware SIP phones. In such case they can be still reached via username, but how to change statuses? Statuses are changed only via the web or mobile SDK. If an operator is connected via SIP, you need to establish a second connection via the web or mobile SDK that will use same username and password. If multiple connections share the same username, they all will ring on an incoming call, which is inconvenient since SDK connection used only to change statuses. For SDK not to ring on incoming calls, it should init with special receiveCalls flag set to false.

Tags:call center
B6A24216-9891-45D1-9D1D-E7359CEB8282 Created with sketchtool.

Answers(0)

Add your comment

Please complete this field.

Recommended

Get your free developer account or talk with our sales team to learn more about Voximplant solutions
SIGN UP