Skip to main content

Gathering User Input from Code

In Gathering User Input we showed how to set up an XML bin to collect input from users. The approach we showed had a limitation: we couldn't take choices based on the user's input.

In this article, we are going to see how to build an input-gathering application using Compatibility XML and SDKs. Using the SDKs will allow us to perform complex choices. For the sake of this guide, we are going to build a very simple IVR.

Prerequisites

We will take for granted that you know how to set up a basic application using the Compatibility SDKs. If not, make sure to read Handling Calls from Code first.

Entrypoint

We'd like to offer callers a set of choices, such as:

  • Press (1) to speak with Support
  • Press (2) to speak with Sales
  • Press (3) to speak with Marketing

We can implement this using the <Gather> verb. The following is the XML that we'd like to use as entry point:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Gather
action="https://example.com/choice"
timeout="15"
numDigits="1">
<Say>
Welcome! Please press 1 to speak with Support. Press 2 to speak with
Sales. Press 3 to speak with Marketing.
</Say>
</Gather>

<Say>We did not receive any input. Goodbye!</Say>
<Hangup />
</Response>

Instead of using this XML document directly (for example as a hosted XML bin), in this case we are going to generate it from our server. Let's define an endpoint at /:

main.py
from flask import Flask, request, Response
from twilio.twiml.voice_response import VoiceResponse, Gather

app = Flask(__name__)

@app.route("/", methods=['GET', 'POST'])
def call_handler():
"""
This endpoint will be queried by SignalWire whenever an incoming call is received.
We respond with an XML document which specifies the next instructions.
"""
response = VoiceResponse()
gather = Gather(action='https://example.com/choice', method='POST', numDigits=1)
gather.say('Welcome! Please press 1 to speak with Support. ' +
'Press 2 to speak with Sales. ' +
'Press 3 to speak with Marketing.')
response.append(gather)
response.say('We did not receive any input. Goodbye!')

return Response(response.to_xml(), mimetype='text/xml')

As soon as the user inputs a number, we execute the XML script at https://example.com/choice. This is an endpoint which will receive the user input as an HTTP parameter, and will emit new XML: make sure to replace this URL with your actual public URL (for example, the ngrok URL). Keep the /choice path though: we are going to show the implementation for this endpoint in the next section.

Branching

After the user makes a choice, SignalWire will fetch our server at /choice including the input as parameter. Let's implement the /choice endpoint to read the input and reply accordingly.

main.py
# ... previous code

@app.route("/choice", methods=['GET', 'POST'])
def choice_handler():
choice = request.values.get('Digits')

response = VoiceResponse()
if choice == '1':
response.say("You selected: support")
elif choice == '2':
response.say("You selected: sales")
elif choice == '3':
response.say("You selected: marketing")
else:
response.say("Invalid input. Please try again.")
response.redirect("https://example.com/")

return Response(response.to_xml(), mimetype='text/xml')

This time, we read the Digits parameter and we reply with a different message depending on its value. If the value is not one of the expected ones, we let the user try again by redirecting the script to the initial endpoint /: make sure to replace https://example.com/ with your actual public URL.

After configuring one of your numbers to handle incoming calls using the webhook that we implemented, you will be able to test the application. Refer to Handling Calls from Code to learn how to configure your number.

Conclusion

We have shown how to gather input from the user, and take decisions accordingly. We did all this in the context of the Compatibility API.

For more advanced, real-time applications, you'll want to check out our Realtime SDK. Find more details in the guide about First Steps with Voice in the Realtime SDK.