How to Warm Transfer a Call with Java and Twilio Voice

August 19, 2015
Written by
Chris Hranj
Contributor
Opinions expressed by Twilio contributors are their own

warm-transferes-java2

Calling into customer support or similar support lines can lead to some not so great experiences. Often times I find myself being tossed back and forth between different agents with no context of who I am talking to and when I am being transferred. These kinds of calls feel a lot like this:

It's always sunny slap
A better way for agents to handle incoming callers is to use a warm transfer. The term “warm transfer” can mean different things to different people. For this blog post we are going to use this scenario to explain a warm transfer: Agent A (Artemis) is speaking to the caller. Artemis needs to transfer the caller to Agent B (Barnabas). Artemis conferences in Barnabas, introduces the caller to Barnabas and then releases the call.

warm transfer callflow

In this blog post we will setup a very simple call center that supports warm transfers using Twilio’s Java helper library and an Apache Tomcat server.

If you are only interested in the finished product, all of the code used in this post can be found on Github here.

“Charlie Work”

The first step to building our call center is to set up our environment. Since we are using Tomcat to implement our web server you can follow the Twilio Quickstart to get started with Tomcat and the Twilio SDK. Take your time and make sure your environment is setup properly before continuing on with this post. If you’ve never used Maven, I suggest following Option 2 in the quickstart and downloading the .jar file directly.

Your file structure should look something like this if you followed along with the quickstart:

Directory Structure

Once we have Tomcat running properly, we can test that our servlets are running locally. Open a terminal, navigate to the top of your project directory and run the following:

$ sh bin/startup.sh

You might run into an issue saying Cannot find ./bin/catalina.sh. We can easily fix this by running the following in our terminal (source):

$ chmod +x bin/*.sh
$ chmod +x bin/*.jar

Once our server has started, navigate to localhost:8080 in a web browser and we should see the Apache Tomcat landing page:

Tomcat default page

Our server is working locally, but we will need to be able to access it publicly so that Twilio can reach it. We can use ngrok to do this. Ngrok is a command line tool that can create a public tunnel to access your localhost. If you’ve never used ngrok before consider reading this blog post by Kevin Whinnery to get familiar with it. Once you have ngrok installed, run  $ ngrok http 8080 in a terminal and navigate to the URL it gives you (see image below) in a browser.

ngrok running example

We should see the same landing page as we did at localhost:8080, meaning our server is now publicly accessible. Before moving on, take a moment to appreciate this cat wearing mittens:

Kitten Mittens ™

“The Gang Hits the Road”

In order to handle incoming requests we need a map to tell our Tomcat server which Java files handle which requests. We can map servlets to endpoints in our server’s web.xml file, which will be located in /webapps/twilio/WEB-INF/. This servlet is going to handle all incoming/outgoing calls to our two agents. Configure this file as such:

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">

    <display-name>Twilio Warm Transfer App</display-name>

    <servlet>
        <servlet-name>TwilioCallerServlet</servlet-name>
        <servlet-class>com.twilio.TwilioCallerServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>TwilioCallerServlet</servlet-name>
        <url-pattern>/handle-caller</url-pattern>
    </servlet-mapping>
</web-app>

This mapping tells our web server that when a request to the /handle-caller URL is made it should forward to the TwilioCallerServlet class. Let’s build this class in a new file now.

Building Our Servlet

Navigate to /webapps/twilio/WEB-INF/classes/com/twilio and create a new file called TwilioCallerServlet.java. This servlet will return TwiML instructions according to the incoming caller.

We first need to import a number of packages in order to use servlets and the Twilio library. Place the following code at the top of your file:

package com.twilio;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import org.apache.http.message.BasicNameValuePair;
import org.apache.http.NameValuePair;

import com.twilio.sdk.TwilioRestClient;
import com.twilio.sdk.resource.factory.CallFactory;
import com.twilio.sdk.resource.instance.Call;
 
import java.util.ArrayList;
import java.util.List;
 
import com.twilio.sdk.verbs.TwiMLResponse;
import com.twilio.sdk.TwilioRestException;
import com.twilio.sdk.verbs.TwiMLException;
import com.twilio.sdk.verbs.Dial;
import com.twilio.sdk.verbs.Conference;
import com.twilio.sdk.verbs.Gather;

We can now create our TwilioCallerServlet class. The class will contain a method called service that will handle incoming HTTP requests and create HTTP responses. Place the following code after your imports:

public class TwilioCallerServlet extends HttpServlet {

    public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {

        TwiMLResponse twiml = new TwiMLResponse();
        // Dial verb allows callers to enter conference.
        Dial dial = new Dial();
        dial.setHangupOnStar(true);
        // Conference will be named SupportRoom.
        Conference conf = new Conference("SupportRoom");
        conf.setBeep(Conference.BEEP_TRUE);

        try {
            dial.append(conf);  
            twiml.append(dial);
        } catch (TwiMLException e) {
            e.printStackTrace();
        }
        // Respond with TwiML
        response.setContentType("application/xml");
        response.getWriter().print(twiml.toXML());    
    }
}

This code creates a new TwiMLResponse() object and appends a Dial verb and a Conference noun to it. This means that when this TwiML is prepared by Twilio, the caller will be placed in a conference named “SupportRoom”. If you wish to learn more about TwiML verbs see the Twilio documentation here.

Testing Our Servlet

Before we move on let’s make sure that our TwilioCallerServlet is functional and returning the correct TwiML. We need to compile our Java files before our Tomcat server can use them. During compilation you must specify the classpath of the jar file containing the Twilio SDK. Navigate to the top of your project directory and run this command to compile all of your java files:

$ javac -cp lib/servlet-api.jar:webapps/twilio/WEB-INF/lib/twilio-java-sdk-4.4.5-jar-with-dependencies.jar webapps/twilio/WEB-INF/classes/com/twilio/*.java

If you are receiving compilation errors then the classpaths specified in your command are not correct or you are running the command from the wrong directory.

Now we need to restart the Tomcat server for our changes to take effect. Do this by running $ sh bin/shutdown.sh and then running $ sh bin/startup.sh again. Remember you’ll need to restart your server any time you recompile your Java files for the changes to take effect.

Navigate to http://localhost:8080/twilio/handle-caller in your browser and you should see the following:

<Response>
    <Dial hangupOnStar="true">
        <Conference beep="true">SupportRoom</Conference>
    </Dial>
</Response>

Expanding Our Servlet

Now let’s define a couple of constants that our servlet is going to need later on. Place the following lines directly below your class definition and above the service function:

final String ACCOUNT_SID = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; // Found on your Twilio Dashboard
final String AUTH_TOKEN = "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"; // Found on your Twilio Dashboard
final String CALL_ENDPOINT = "http://XXXXXXXX.ngrok.io/twilio/handle-caller"; // Paste URL generated by ngrok
final String ARTEMIS_NUMBER = "+15555555555"; // The first agent's number
final String BARNABAS_NUMBER = "+15555555555"; // The second agent's number
final String SUPPORT_NUMBER = "+15555555555"; // Twilio number

Replace each variable with the appropriate data: Your ACCOUNT_SID and AUTH_TOKEN can be found on your Twilio account dashboard. CALL_ENDPOINT will be the URL generated by your ngrok server with your servlet mapping appended to it (For example: http://229cd6dd.ngrok.io/twilio/handle-caller). ARTEMIS_NUMBER is the phone number of your first agent. This is the number that will answer all incoming calls to your Twilio number. BARNABAS_NUMBER is the phone number that will receive the warm transfer from Artemis. Lastly, SUPPORT_NUMBER is your Twilio number that callers will call into.

Now let’s add some additional logic to handle agent transfers and outgoing calls. Add the following code above your response.setContentType("application/xml"); line:

// Check if any key presses were made.
String digits = request.getParameter("Digits");
// If digits were pressed, Artemis needs to conference Barnabas.
if (digits != null && digits.equals("1")) {
    // Call Barnabas to bring into conference.
    makeCall(CALL_ENDPOINT, BARNABAS_NUMBER, SUPPORT_NUMBER);
}
else {
    // Check if incoming call is coming directly to our Support Number.
    if(request.getParameter("To").equals(SUPPORT_NUMBER)) {
        // Call Artemis to connect to incoming caller.
        makeCall(CALL_ENDPOINT, ARTEMIS_NUMBER, request.getParameter("From"));
    }
    // Check if the incoming call is coming to Artemis.
    else if(request.getParameter("To").equals(ARTEMIS_NUMBER)) {
        // Append Gather to Artemis's TwiML
        Gather gather = new Gather();
        gather.setAction(CALL_ENDPOINT);
        gather.setNumDigits(1);
        try {
            twiml.append(gather);
        } catch (TwiMLException e) {
            e.printStackTrace();
        }
    }
    // Calls coming to Barnabas will receive the same TwiML as the initial caller.
}

Our servlet is checking if a “1” was entered into the keypad by our first agent (Artemis) after disconnecting from the conference. If it was, our second agent (Barnabas) will be dialed and Artemis will be reconnected to the conference.

If no input was sent, we must check where the incoming call is coming from. If a regular caller is dialing into your Twilio number, they must be connected with Artemis. Artemis will be sent different TwiML that contains a Gather verb to collect input from the keypad. Combined with the setHangupOnStar attribute, this allows Artemis to press “*1” to dial Barnabas.

You’ll notice we haven’t defined the makeCall function. Let’s add this function right before the class’s closing bracket. The following code will make calls for us using a  TwilioRestClient:

public void makeCall(String url, String to, String from) {
    try {
        TwilioRestClient client = new TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN);

        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("Url", url));
        params.add(new BasicNameValuePair("To", to));
        params.add(new BasicNameValuePair("From", from));
         
        CallFactory callFactory = client.getAccount().getCallFactory();
        Call call = callFactory.create(params);
    }
    catch (TwilioRestException e) {
            System.out.println(e.getErrorMessage());
    }
}

“Frank’s Back in Business”

The last step is to buy a Twilio phone number and configure it to reach out to our TwilioCallerServlet servlet. From the Twilio account dashboard, follow the animation below to purchase a new number. This number will serve as our support line.

Buying a Twilio Number

In the Setup Number screen, under Voice, set your ‘Request URL’ to the CALL_ENDPOINT URL we used in our code and click save:

Configuring a Twilio Number

Remember to restart your Tomcat server and make sure your ngrok tunnel is online. You can test out your application by having someone call your Twilio number. Your first agent (Artemis) will receive a phone call and be placed into a conference with the caller. When Artemis presses “*1”, your second agent (Barnabas) will then receive a call and be placed into the same conference. This allows Artemis to introduce the caller before handing them off to Barnabas.

It’s Always Servlet in Philadelphia

Congratulations! You’ve now built a call center that supports warm transferring incoming callers. With warm transfers, your callers will now feel more like this:

It's Always Sunny dancing

Note: This is a very simple implementation of a call center and it can be improved in many ways. Keep your eyes peeled for future Java posts that expand on what we’ve built here.

If you ran into any issues or have feedback on this tutorial, please don’t hesitate to leave a comment below or reach out to me via Twitter @brodan_ or email me at chranj@twilio.com.