Have you ever had your call disconnect while you were on hold with customer service and waiting for a transfer from one department to another? You probably felt like this when the call dropped:
A warm phone call transfer, where a caller is on the phone with an agent then the agent brings on another agent and introduces them to the customer, shouldn’t be so difficult. There are only a few steps that need to be done properly for a warm phone call transfer to work, as we can visualize in the following diagram.
As the diagram shows, a customer, who in this post we’ll refer to as Trinity, dials into a typical phone number for service. An agent, known as Agent Smith, answers the calls. However, Agent Smith discovers he can’t solve the problem and needs to connect Trinity to Agent Johnson, who’s better equipped to handle the issue. Agent Smith dials in Agent Johnson, introduces Agent Johnson to the customer, then hangs up so Trinity and Agent Johnson can solve the problem and finish the support call.
In this blog post, we’ll use Twilio’s Voice API and a bit of Python code to implement the above scenario and help rid the world of warm transfer failures so that this problem never happens to your callers.
What We’ll Need
Let’s walk through how to perform a warm transfer with Python and a simple web application built with the Flask framework. The following dependencies will be used to create and run the application:
- Either Python 2.x or 3.x as both will work fine with our code
- Twilio Python helper library to create our TwiML
- A Twilio phone number with calling capability
- Ngrok for testing the code locally
- Flask to run our small web application
- Optionally, it never hurts to have the Twilio Docs, specifically the Twilio REST API documentation, open for reference as we build the application
We’ll also need three phones with phone numbers to represent Trinity, Agent Smith and Agent Johnson. If you don’t have multiple phones lying around for testing, you can use Twilio Client to turn three browser tabs into different phones. Use this Twilio Client quickstart project with the deploy to Heroku button to create phones as you need them.
There is also a GitHub repository with the code we’ll be creating in this post. It’s a handy reference in case you make a typo or get stuck somewhere along the way.
Also, if Python isn’t your jam and Java is more your speed, check out my colleague’s post that handles a warm transfer in that language instead.
Now that we know our dependencies, let’s get our coding environment established.
Python Environment Setup
It’s time to get our Python development environment ready for this application. In this section we’ll handle the following steps to make sure you’re ready to run the code:
- Check the Python installation
- Set up a new virtualenv then activate it
- Install a couple of dependencies via pip
- Fire up Ngrok to handle localhost tunneling
- Obtain a phone number for our customer service line
- Export a few critical environment variables for our application to use
Setup Step 1: Check the Python installation
If you’re on Mac OS X or Linux, you’ve already got Python installed on your system. Check the version by going into the terminal and typing
python --version. You’ll see the precise version number, something like
Python 2.7.6. If you’re on Python 2.6 or greater, you’re good to go.
For Windows, make sure you download and install the Python .exe installation package. Either Python 2 or 3 is fine. My recommendation is to use Python 3.5 unless you have a pressing reason to use an earlier version, because the Python community is now migrating en masse to the Python 3.
Once Python is installed on your system and you can run it with the
pythoncommand, we’re ready to set up a virtualenv.
Setup Step 2: Set up a new virtualenv and activate it
Virtualenv is a dependency isolation library that makes it much easier to switch between Python versions and various code packages you’re using on different projects. Create a virtualenv in a location you’ll remember using the following command. In my case, I have a folder called
Envsunder my home directory that keeps my virtualenv organized.
virtualenv ~/Envs/warmtransfer source ~/Envs/warmtransfer/bin/activate
Now we have our virtualenv activated and should see our command prompt show the name of our virutalenv, such as
(warmtransfer)$. With the virtualenv in place and activated, we can use
pipto install our dependencies.
Setup Step 3: Install dependencies with pip
piphandles Python library installations. With your virtualenv still activated, run the following command to install our Flask and Twilio helper library dependencies.
(warmtransfer)$ pip install flask==0.10.1 twilio==4.4.0
You can likely install the newest versions of the Flask and Twilio packages but it’s preferable to peg dependencies to a specific version number just in case there are future backwards-incompatible modifications. With those libraries installed, next we need to launch Ngrok to create a localhost tunnel so that our application will be reachable by Twilio.
Setup Step 4: Fire up Ngrok
Download and install the Ngrok application. From the command line, run Ngrok to create a localhost tunnel to the port our application will run on using the following command:
./ngrok http 5000
We should see a screen like the following that shows Ngrok started properly. Take note of the second line that begins with “Forwarding”. That’s the URL we need to set for the BASE_URL environment variable as well as our Twilio webhook.
With Ngrok up and running, we can turn our attention to setting up a phone number for Trinity to call into for customer service.
Setup Step 5: Obtain a number for our customer service line
Where do we get the phone number to use as a customer service line for our warm phone call transfer example? That phone number comes from our Twilio account.
If you don’t already have a Twilio account, you can sign up for a free trial account. After signing up, upgrade to a paid account as we’ll need its ability to call multiple telephone numbers other than our own verified number.
Obtain a Twilio phone number with calling capabilities that we can configure for our warm call transfer example. Go to the Twilio numbers page which should look like the following screenshot:
Click on the “Buy a Number” button and purchase a phone number with calling capabilities in the location of your choice. After obtaining the phone number, you’ll get to set up voice calling on the numbers configuration page:
As shown above, within the phone number’s screen there is a voice webhook we need to configure. WIthin the Request URL text box, type in the Ngrok Forwarding URL, such as “https://5341e8cd.ngrok.io” plus “/call” because that is the route we are going to write in the next section to respond to the webhook with appropriate TwiML. Make sure to scroll down and click the “Save” button at the bottom of the screen so our changes take effect.
With our Python development environment established, Ngrok started and a Twilio phone number in hand, we just need to use that information to set environment variables our application will need.
Setup Step 6: Set environment variables
How to set environment variables depends on your operating system. Here are some handy guides if you’re on Windows, Mac OS X or Ubuntu Linux that’ll help with the specific steps.
Note that if you don’t set these environment variables now there are spots within the application code to set them. However, in Python it’s a good practice to use environment variables instead of hardcoding these sensitive values.
There are six variables that need to be set:
-
TWILIO_ACCOUNT_SID
– found on your Twilio account dashboard -
TWILIO_AUTH_TOKEN
– also found on your Twilio account dashboard -
CUSTOMER_SERVICE_NUMBER
– a number you’ve purchased on Twilio -
AGENT1_NUMBER
– the phone number of the agent who will first answer the call. For testing purposes this is most likely your own phone number, specified in the +12025551234 format -
AGENT2_NUMBER
– the phone number of the agent who will be transferred in, in the +12025551234 format -
BASE_URL
– the forwarding URL given by Ngrok when it starts up, for example “https://5341e8cd.ngrok.io”
On Mac OS X and Linux, you can set environment variables with the
exportshell command. For example, you’d run
exportsix times to set the following variables:
export TWILIO_ACCOUNT_SID="ACxxxxxxxxxxxxxxxxxxxxxx" export TWILIO_AUTH_TOKEN="yyyyyyyyyyyyyyyyyyyyyyyyyyy" export CUSTOMER_SERVICE_NUMBER="+12025551234" # this will be a Twilio number export AGENT1_NUMBER="+19735551234" # your phone number export AGENT2_NUMBER="+14155551234" # a second phone number for testing export BASE_URL="https://5341e8cd.ngrok.io"
Now that our environment is ready to go, let’s write some code to handle inbound customer service phone calls.
Calling Agent Smith
Our environment is established and we have a Twilio phone number, so it’s time to write some Python code to handle an incoming phone call.
We’ll start out by importing our Flask and Twilio dependencies that we installed earlier. We also need a bit of boilerplate to initialize the Flask application and the Twilio helper library. This code will run the application in Flask’s debug mode in case we run into any issues while building our application.
import os from flask import Flask, Response, request from twilio import twiml from twilio.rest import TwilioRestClient app = Flask(__name__) client = TwilioRestClient() if __name__ == '__main__': app.run(debug=True)
Next our application needs to obtain the environment variables we exported to our shell. The
osmodule is used to get the environment variables for customer service number, the agent phone numbers and the base URL that Ngrok gives us when we fire it up.
import os from flask import Flask, Response, request from twilio import twiml from twilio.rest import TwilioRestClient app = Flask(__name__) client = TwilioRestClient() # this should be your Twilio number, format: +14155551234 CUSTOMER_SERVICE_NUMBER = os.environ.get('CUSTOMER_SERVICE_NUMBER', '') # your cell phone number (agent's number), format: +14155551234 AGENT1_NUMBER = os.environ.get('AGENT1_NUMBER', '') # second person's phone or Twilio number if testing, format: +14155551234 AGENT2_NUMBER = os.environ.get('AGENT2_NUMBER', '') # ngrok URL, such as "https://17224f9e.ngrok.io", no trailing slash BASE_URL = os.environ.get('BASE_URL', 'https://143e6ab2.ngrok.io') if __name__ == '__main__': app.run(debug=True)
We could run our Flask web application at this point but without any routes it doesn’t have any pages to load. Create a couple routes now, named
inbound_calland
conference_line, as shown below.
@app.route('/call', methods=['POST']) def inbound_call(): call_sid = request.form['CallSid'] response = twiml.Response() response.dial().conference(call_sid) call = client.calls.create(to=AGENT1_NUMBER, from_=CUSTOMER_SERVICE_NUMBER, url=BASE_URL + '/conference/' + call_sid) return Response(str(response), 200, mimetype="application/xml") @app.route('/conference/', methods=['GET', 'POST']) def conference_line(conference_name): response = twiml.Response() response.dial(hangupOnStar=True).conference(conference_name) return Response(str(response), 200, mimetype="application/xml")
The above code in
inbound_callhandles an inbound call by generating and returning a TwiML response when Twilio performs an HTTP POST request to our application. We also use our instantiated
TwilioRestClientto dial an outbound phone call to Agent Smith, our first agent who will take the customer’s call.
The second function,
conference_line, creates a TwiML response that looks like the following XML. We don’t need to hand code the TwiML because the Python helper library generates this when we call the
Response,
dialand
conferencefunctions.
callSid
Our application is now a simple conference call application. Let’s give the initial version of our warm call transfer application a spin to make sure everything works so far. Run the Flask app from the command line with the following command.
python app.py
With the application running and exposed to Twilio using Ngrok, we can test the application by calling the customer service Twilio number.
Now we’re connected to our initial customer service agent. However, let’s assume the agent can’t answer the question and needs to connect the caller to another person who knows what they’re talking about.
Bringing in Agent Johnson
We need to slightly modify our application to allow for the warm transfer. The
gatherfunction in our response will generate a TwiML response for us that instructs Twilio how to handle the phone call.
@app.route('/conference/', methods=['GET', 'POST']) def conference_line(conference_name): response = twiml.Response() response.dial(hangupOnStar=True).conference(conference_name) response.gather(action=BASE_URL + '/add-agent/' + conference_name, numDigits=1) return Response(str(response), 200, mimetype="application/xml") @app.route('/add-agent/ ', methods=['POST']) def add_second_agent(conference_name): client.calls.create(to=AGENT2_NUMBER, from_=CUSTOMER_SERVICE_NUMBER, url=BASE_URL + '/conference/' + conference_name) response = twiml.Response() response.dial(hangupOnStar=True).conference(conference_name) return Response(str(response), 200, mimetype="application/xml")
In the modified
inbound_callroute we create TwiML for a conference line and then dial our first agent into the conference call. A conference is actually the easiest way to handle adding and removing people from a call. Our customer calling in doesn’t even need to know it’s a conference call, it’s seamless to her as Agent Smith and Agent Johnson add and remove themselves from the line.
The
conference_lineroute creates the TwiML to put Agent Smith in the conference call. The
hangupOnStar=Trueparameter allows Agent Smith to put the call on hold by pressing the
*key on the dial pad. Then Agent Smith gets back into the call by pressing any number key on the dial pad, which causes the
gatherTwiML to add the agent back to the conference with our customer.
We’re ready to test out our application one more time to see it all put together. Here is a review of the entire
app.pyfile we wrote together throughout this post.
import os from flask import Flask, Response, request from twilio import twiml from twilio.rest import TwilioRestClient app = Flask(__name__) client = TwilioRestClient() # this should be your Twilio number, format: +14155551234 CUSTOMER_SERVICE_NUMBER = os.environ.get('CUSTOMER_SERVICE_NUMBER', '') # your cell phone number (agent's number), format: +14155551234 AGENT1_NUMBER = os.environ.get('AGENT1_NUMBER', '') # second person's phone or Twilio number if testing, format: +14155551234 AGENT2_NUMBER = os.environ.get('AGENT2_NUMBER', '') # ngrok URL, such as "https://17224f9e.ngrok.io", no trailing slash BASE_URL = os.environ.get('BASE_URL', 'https://143e6ab2.ngrok.io') @app.route('/call', methods=['POST']) def inbound_call(): call_sid = request.form['CallSid'] response = twiml.Response() response.dial().conference(call_sid) call = client.calls.create(to=AGENT1_NUMBER, from_=CUSTOMER_SERVICE_NUMBER, url=BASE_URL + '/conference/' + call_sid) return Response(str(response), 200, mimetype="application/xml") @app.route('/conference/', methods=['GET', 'POST']) def conference_line(conference_name): response = twiml.Response() response.dial(hangupOnStar=True).conference(conference_name) response.gather(action=BASE_URL + '/add-agent/' + conference_name, numDigits=1) return Response(str(response), 200, mimetype="application/xml") @app.route('/add-agent/ ', methods=['POST']) def add_second_agent(conference_name): client.calls.create(to=AGENT2_NUMBER, from_=CUSTOMER_SERVICE_NUMBER, url=BASE_URL + '/conference/' + conference_name) response = twiml.Response() response.dial(hangupOnStar=True).conference(conference_name) return Response(str(response), 200, mimetype="application/xml") if __name__ == '__main__': app.run(debug=True)
With the above code written and running, call into our customer service phone number and the app will use the Twilio Voice API to call Agent Smith. When Agent Smith picks up, press
*plus a number on the keypad to “hang up” which hits the
/add-agentroute and calls out to Agent Johnson.
Agent Smith will be placed back into the call. Agent Smith then transfers to Agent Johnson, they’re both on the line, then Agent Smith drops off while Agent Johnson continues on with Trinity.
Warm Transfer Complete
Now that’s how a warm call transfer should be done!
In this post, we solved the problem of poorly handled call transfers. Now when your customers call in, Agent Smith will be ready to hand off to Agent Johnson when the need arises.
If you’re looking for more tutorials and Python code to do additional voice communications to your application, check out one of my favorite tutorials named Click to Call that uses Python and Flask to allow visitors on the web to easily call your support or sales lines on the phone.
That’s all for now! If you run into any issues throughout the tutorial feel free to drop a comment below or contact me via:
- Email: makai@twilio.com
- GitHub: Follow makaimc for repository updates
- Twitter: @mattmakai
Warm Phone Call Transfers with Python, Flask and Twilio Voice