Skip to main content

Request Callback in a Queue - Python

In a past blog, we talked about the importance of having a concise phone menu so that customers don't have to wait through your IVR in order to resolve an issue. But what happens if a lot of customers call at once, making long hold and wait times that may make your customers aggravated as they stay on the line for extended periods of time? Have your customers keep their sanity while avoiding long hold times by offering a callback option. Using the SignalWire Python SDK, customers can call and request a call back by pressing a digit, or they can text message the number and request a call back when the next agent is available.

This code runs by navigating through a series of endpoints that accepts incoming text messages from your SignalWire Space in order to place and handle callers in a callback queue. Once the call-back request is processed, the agent will be connected back to the caller.

What do I need to run this application?

View the full code on our Github here!

You will need the Flask framework and the SignalWire Python SDK downloaded.

You will need a SignalWire phone number as well as your API Credentials (API Token, Space URL, and Project ID) which can all be found in an easily copyable format within the API tab of your SignalWire portal.

Running the application

Build and Run on Docker

  1. Use our pre-built image from Docker Hub

docker pull signalwire/snippets-queue-request-callback:python

(or build your own image)

  1. Build your image
docker build -t snippets-queue-request-callback .
  1. Run your image
docker run --publish 5000:5000 --env-file .env snippets-queue-request-callback
  1. The application will run on port 5000

Build and Run Natively

To run the application, execute export FLASK_APP=app.py then run flask run.

You may need to use an SSH tunnel for testing this code if running on your local machine. – we recommend ngrok. You can learn more about how to use ngrok here.

Step by Step Code Walkthrough

In the Github repo, you will find four files:

  • Dockerfile
  • README.md
  • .env file
  • app.py

We can ignore the README.md and Dockerfile and move on to configuring the .env file first, followed by a step-by-step walkthrough of app.py.

Setup Your Environment File

  1. Copy from example.env and fill in your values
  2. Save new file called .env

Your file should look something like this

## This is the full name of your SignalWire Space. e.g.: example.signalwire.com
SIGNALWIRE_SPACE=
# Your Project ID - you can find it on the `API` page in your Dashboard.
SIGNALWIRE_PROJECT=
#Your API token - you can generate one on the `API` page in your Dashboard
SIGNALWIRE_TOKEN=
# The phone number you'll be using for this guide. Must include the `+1` , e.g.: +15551234567
SIGNALWIRE_NUMBER=

Configuring app.py

Our first route /text_request_call will listen for text messages to process, send a message back to anyone who texts in, and process the call when the timer (for demo purposes) is complete. In a real example, you would wait for your queue to lessen and place the call when an agent is available.

@app.route('/text_request_call', methods=['GET', 'POST'])
def text_request():
# Setup the Signalwire client to process commands
client = signalwire_client(os.environ['SIGNALWIRE_PROJECT'], os.environ['SIGNALWIRE_TOKEN'], signalwire_space_url = os.environ['SIGNALWIRE_SPACE'])

# Read the 'From' variable from the GET/POST request
number = request.values.get('From')

# Send a text message back to the number that sent it
message = client.messages.create(
from_ = os.environ['SIGNALWIRE_NUMBER'],
body = "This is a message from Signalwire, thank you for requesting a call back. We will call you in about 3 min.",
to = number
)

# Sub routine to process the call and execute the connect agent script, for this demo, we inserted a timer for demonstration purposes
def do_call(number):

time.sleep(120)

# Place the call, and on connection execute connection agent script
call = client.calls.create(
from_ = os.environ['SIGNALWIRE_NUMBER'],
url = "http://" + os.environ['HOSTNAME'] + "/connect_agent",
to = number
)

# Run sub on seperate thread, so call does not block REST request
t = threading.Thread(target=do_call, args=[number,])

# Start thread
t.start()

return "200"

Our next endpoint /enter_queue will serve as the entry point for putting callers in the queue. We will thank the caller for their call and place them into the DemoQueue1 queue. We will then redirect them to the /wait_url route which will play music and handle the on-hold portion of the call.

# Listen on route '/enter_queue' for GET/POST requests
@app.route('/enter_queue', methods=['GET', 'POST'])
def enter_queue():

# Make an instance of Signalwire VoiceResponse
response = VoiceResponse()

# Synthesize text to speech, using Say verb
response.say('Thank you for calling SignalWire Demos.')

# Drop caller into DemoQueue1 and send to onhold /wait_url
response.enqueue('DemoQueue1', wait_url='/wait_url')

# Return the VoiceResponse as a string
return str(response)

In the /wait_url route, we will ask the user to hold and then play an audio file. After the audio file has ended, we will repeat the prompt and advise that the caller can press 1 to request a callback. If the caller presses one, they will redirect to the /request_callback route. If they do not, the audio file/prompts will continue to loop intermittently until the caller is next up in the queue.

# Listen on route '/wait_url' for GET/POST requests
@app.route('/wait_url', methods=['GET', 'POST'])
def wait_url():

# Make instance of VoiceResponse
response = VoiceResponse()

# Make instance of Gather
gather = Gather(action='/request_callback', input='dtmf', timeout="3", method='GET')
# Append Say verb to Gather verb
gather.say('Please hold for the next available representative.')
# Append Play verb to Gather verb
gather.play('https://sinergyds.blob.core.windows.net/signalwire/snoopclose.wav')
# Append Say verb to Gather verb
gather.say('Your call is very important to us, a representative will be with you shortly. To request a call back, press 1 and you will not lose your place in line.')
# Append Play verb to Gather verb
gather.play('https://sinergyds.blob.core.windows.net/signalwire/8d82b5_The_Muppet_Show_Theme_Song.mp3')
# Append Gather to Response
response.append(gather)

# Return the VoiceResponse as a string
return str(response)

If the caller presses 1 while on hold, they will be redirected to the /request_callback route. In this route, we will play a short message to let the caller know that they have been queued for callback and hang up the call. We will then enact the same routine as in the first route - we will use a timer to demo the caller being in a queue and then place a call. If you are using this example in practice, you can initiate the call whenever the next agent is ready to connect instead.

# Listen on route '/request_callback' for GET/POST
@app.route('/request_callback', methods=['GET', 'POST'])
def request_callback():

# Make instance of VoiceResponse
response = VoiceResponse()

# Use Say verb for TTS
response.say('OK. Your call has been queued for a call back, We will call you back shortly. Thank you.')
# Use Hangup verb to hang up the call
response.hangup()

# Read number to call back from From GET/POST request
number = request.values.get('From')

# Subroutine to handle call back
def do_callback(number):

# Setup a Signalwire Client
client = signalwire_client(os.environ['SIGNALWIRE_PROJECT'], os.environ['SIGNALWIRE_TOKEN'], signalwire_space_url = os.environ['SIGNALWIRE_SPACE'])

# For demo purposes, sleep for 15 seconds before sending text
time.sleep(15)

# Send a text message to tell user, thank you and an estimated call back time.
text = client.messages.create(
from_ = os.environ['SIGNALWIRE_NUMBER'],
body = "This is a message from Signalwire, thank you for requesting a call back. We will call you in about 3 min.",
to = number
)

# For demo purposes, sleep for 2 min, then continue
time.sleep(120)

# Perform the call back to user, and execute connect agent script
call = client.calls.create(
from_ = os.environ['SIGNALWIRE_NUMBER'],
url = "http://" + os.environ['HOSTNAME'] + "/connect_agent",
to = number
)
# Create a new thread to run call back, so web request is not blocking
t = threading.Thread(target=do_callback, args=[number,])
# Start the thread
t.start()

# return the VoiceResponse as a string
return str(response)

Once the agent is connected to the caller, the action url will redirect to /connect_agent where we will let the user know that we are executing the callback they requested, play a short audio file, then hang up. In practice, you can play a short message and skip the audio file as your customer/agent will now be connected, so nothing further is needed!


@app.route('/connect_agent', methods=['GET', 'POST'])
def connect_agent():

# Create an instance of VoiceResponse
response = VoiceResponse()
# Use Say verb for TTS
response.say('This is signalwire calling you back from your request earlier.')
# Use Play verb for audio file playback
response.play('https://sinergyds.blob.core.windows.net/signalwire/snoopclose.wav')
# Use Say verb for TTS
response.say('For more excellent demos and starting projects, visit signal wire dot com')
# Hangup the call
response.hangup()

# Return VoiceResponse as a string
return str(response)

Wrap Up

If you have a busy call center, your customers might grow frustrated having to wait on the phone for their turn to speak to an agent. You can make their day much easier by allowing them the option to receive a call back instead of waiting on the phone the whole time! This guide shows how easy it is to offer customers a chance to request a call back when they are next in line.

Required Resources:

Sign Up Here

If you would like to test this example out, you can create a SignalWire account and space here.

Please feel free to reach out to us on our Community Slack or create a Support ticket if you need guidance!