Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

Gather User Input via Keypad (DTMF Tones) in Python


In this guide, we'll show you how to gather user input during a phone call through the phone's keypad (using DTMF(link takes you to an external page) tones) in your Python application. By applying this technique, you can create interactive voice response (IVR(link takes you to an external page)) systems and other phone based interfaces for your users.

The code snippets in this guide are written using the Flask web framework(link takes you to an external page) and the Twilio Python SDK. Let's get started!


Set up your Flask application to receive incoming phone calls

set-up-your-flask-application-to-receive-incoming-phone-calls page anchor

This guide assumes you have already set up your web application to receive incoming phone calls. If you still need to complete this step, check out this guide. It should walk you through the process of buying a Twilio number and configuring your app to receive incoming calls.


Collect user input with the <Gather> TwiML verb

collect-user-input-with-the-gather-twiml-verb page anchor

The <Gather> TwiML verb allows us to collect input from the user during a phone call. Gathering user input through the keypad is a core mechanism of Interactive Voice Response (IVR) systems where users can press "1" to connect to one menu of options and press "2" to reach another. These prompts can be accompanied by voice prompts to the caller, using the TwiML <Say> and <Play> verbs. In this example, we will prompt the user to enter a number to connect to a certain department within our little IVR system.

Use <Gather> to collect user input via the keypad (DTMF tones)

use-gather-to-collect-user-input-via-the-keypad-dtmf-tones page anchor

This example returns TwiML containing a <Say> tag nested within a <Gather> tag. The user will be prompted to choose sales or support.

Python

_24
from flask import Flask
_24
from twilio.twiml.voice_response import VoiceResponse, Gather
_24
_24
app = Flask(__name__)
_24
_24
_24
@app.route("/voice", methods=['GET', 'POST'])
_24
def voice():
_24
"""Respond to incoming phone calls with a menu of options"""
_24
# Start our TwiML response
_24
resp = VoiceResponse()
_24
_24
# Start our <Gather> verb
_24
gather = Gather(num_digits=1)
_24
gather.say('For sales, press 1. For support, press 2.')
_24
resp.append(gather)
_24
_24
# If the user doesn't select an option, redirect them into a loop
_24
resp.redirect('/voice')
_24
_24
return str(resp)
_24
_24
if __name__ == "__main__":
_24
app.run(debug=True)

If the user doesn't enter any input after a configurable timeout, Twilio will continue processing the TwiML in the document to determine what should happen next in the call. When the end of the document is reached, Twilio will hang up the call. In the above example, we use the <Redirect> verb to have Twilio request the same URL again, repeating the prompt for the user

If a user were to enter input with the example above, the user would hear the same prompt over and over again regardless of what button you pressed. By default, if the user does enter input in the <Gather>, Twilio will send another HTTP request to the current webhook URL with a POST parameter containing the Digits entered by the user. In the sample above, we weren't handling this input at all. Let's update that logic to also process user input if it is present.

Branch your call logic based on the digits sent by the user

branch-your-call-logic-based-on-the-digits-sent-by-the-user page anchor

By default, <Gather> will 'loop' on the current TwiML URL, requesting the same URL every time input is entered.

Python

_41
from flask import Flask, request
_41
from twilio.twiml.voice_response import VoiceResponse, Gather
_41
_41
app = Flask(__name__)
_41
_41
_41
@app.route("/voice", methods=['GET', 'POST'])
_41
def voice():
_41
"""Respond to incoming phone calls with a menu of options"""
_41
# Start our TwiML response
_41
resp = VoiceResponse()
_41
_41
# If Twilio's request to our app included already gathered digits,
_41
# process them
_41
if 'Digits' in request.values:
_41
# Get which digit the caller chose
_41
choice = request.values['Digits']
_41
_41
# <Say> a different message depending on the caller's choice
_41
if choice == '1':
_41
resp.say('You selected sales. Good for you!')
_41
return str(resp)
_41
elif choice == '2':
_41
resp.say('You need support. We will help!')
_41
return str(resp)
_41
else:
_41
# If the caller didn't choose 1 or 2, apologize and ask them again
_41
resp.say("Sorry, I don't understand that choice.")
_41
_41
# Start our <Gather> verb
_41
gather = Gather(num_digits=1)
_41
gather.say('For sales, press 1. For support, press 2.')
_41
resp.append(gather)
_41
_41
# If the user doesn't select an option, redirect them into a loop
_41
resp.redirect('/voice')
_41
_41
return str(resp)
_41
_41
if __name__ == "__main__":
_41
app.run(debug=True)


Specify an action to take after user input is collected

specify-an-action-to-take-after-user-input-is-collected page anchor

You may want to have an entirely different endpoint in your application handle the processing of user input. This is possible using the "action" attribute of the <Gather> verb. Let's update our example to add a second endpoint that will be responsible for handling user input.

Add another route to handle the input from the user

add-another-route-to-handle-the-input-from-the-user page anchor

This technique creates a separate route to handle user input.

Python

_53
from flask import Flask, request
_53
from twilio.twiml.voice_response import VoiceResponse, Gather
_53
_53
app = Flask(__name__)
_53
_53
_53
@app.route("/voice", methods=['GET', 'POST'])
_53
def voice():
_53
"""Respond to incoming phone calls with a menu of options"""
_53
# Start our TwiML response
_53
resp = VoiceResponse()
_53
_53
# Start our <Gather> verb
_53
gather = Gather(num_digits=1, action='/gather')
_53
gather.say('For sales, press 1. For support, press 2.')
_53
resp.append(gather)
_53
_53
# If the user doesn't select an option, redirect them into a loop
_53
resp.redirect('/voice')
_53
_53
return str(resp)
_53
_53
_53
@app.route('/gather', methods=['GET', 'POST'])
_53
def gather():
_53
"""Processes results from the <Gather> prompt in /voice"""
_53
# Start our TwiML response
_53
resp = VoiceResponse()
_53
_53
# If Twilio's request to our app included already gathered digits,
_53
# process them
_53
if 'Digits' in request.values:
_53
# Get which digit the caller chose
_53
choice = request.values['Digits']
_53
_53
# <Say> a different message depending on the caller's choice
_53
if choice == '1':
_53
resp.say('You selected sales. Good for you!')
_53
return str(resp)
_53
elif choice == '2':
_53
resp.say('You need support. We will help!')
_53
return str(resp)
_53
else:
_53
# If the caller didn't choose 1 or 2, apologize and ask them again
_53
resp.say("Sorry, I don't understand that choice.")
_53
_53
# If the user didn't choose 1 or 2 (or anything), send them back to /voice
_53
resp.redirect('/voice')
_53
_53
return str(resp)
_53
_53
if __name__ == "__main__":
_53
app.run(debug=True)

The action attribute takes a relative URL which would point to another route your server is capable of handling. Now, instead of conditional logic in a single route, we use actions and redirects to handle our call logic with separate code paths.


If you're building call center type applications in Python, you might enjoy our IVR Phone Tree (Flask) tutorial, which implements a full sample application using these techniques.


Rate this page: