How to Gather Keypad Input from a Caller

So you've learned how to set up voicemail, record phone calls, and forward calls. What if you want to ask a user to make a decision and then execute different instructions based on their response? This guide will teach you how to do exactly that by gathering input from the caller and redirecting to a different set of instructions!

What do I need to get started?

You will need a SignalWire phone number that we can configure to handle inbound calls or to make outbound calls. You can read our handy guide on how to purchase a phone number here if you have not done so already.

You can also read here to learn more about webhooks and how to configure them. We will be using inbound voice call webhooks for recording inbound calls.

If you want to do very simple logic, you can use in an XML bin for serverless code hosting. However, you can only have one instructional option! For example, you could tell your customers "Press any key to leave a message, or stay on the line to be connected to an agent". This is the example that we will show below for the XML bin version of this example.

If you want to create an application that can handle complex logic, you will also need to create an XML document in one of the SignalWire SDKs. This allows for greater customizability and the ability to build expansive applications serving multiple purposes in one document. You will need to make sure you have installed one of the SignalWire client SDKs in Python, Node.JS, PHP, or Ruby to write the XML document. You can view installation instructions here.

Lastly, if you are using the SignalWire SDK, you will need to host your code on a server to make it publically accessible! For local testing without using a server, ngrok is a great tool to allow you to create an SSH tunnel to your code on localhost making it publically accessible by SignalWire. You will then need to use it as your webhook for handling inbound calls.

XML Bin Walkthrough

The code sample below is meant to be used within multiple XML bins to recreate a simple "IVR" type option for your customers. When a call comes in, the caller will hear a short prompt advising them that they can stay on the line to connect to an agent or press any key to record a message instead. If the caller records a message, a short prompt will be played followed by a command to hang up the call.

This bin uses Gather and specifies an action URL to execute if on keypress, the type of input we expect, the number of seconds we want to wait, and the number of digits we expect to be pressed.

Within the Gather, we will nest a Say that tells the caller what their options are within the message. If the caller presses any key, we will execute the action URL. If no key is pressed in 5 seconds, the next instruction will be executed. The next instruction is Dial and will connect the caller to the agent.

<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Gather action='path-to-voicemail-bin'  input='dtmf' timeout='5' numDigits="1">
<Say>Hello! Thanks for calling our Support line. If you would like to speak to an agent directly, please stay on the line. If you would like to leave a message and receive a callback, press any key now! </Say>
</Gather>
<Dial>+11234567890</Dial>
</Response>

In the next bin, we specify instructions to execute if the caller presses any key to indicate they would like to record a message and receive a callback. We will use a simple Say to play the voicemail prompt and then use Record to initiate the recording. We will also specify a max length of seconds for the recording and a key that when pressed will execute a new document of instructions. If you do not specify an action URL within the record verb, the recording prompt will loop over and over.

<?xml version="1.0" encoding="UTF-8"?>
<Response>
    <Say>
        Please leave a message with your name and number at the beep.
        Press the pound key when finished.
    </Say>
    <Record
        action='Path-To-Hangup-Bin'
        maxLength="15"
        finishOnKey="#"
        />
</Response>

The last bin will play a short prompt advising the caller that they will be contacted and then it will hang up the call. Even though this bin is simple, it's very important to avoid looping calls after a recording!

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say> Thank you for your message. A member of our team will contact you shortly. Goodbye!</Say> 
  <Hangup/>
</Response>

Python SDK Code Walkthrough

The code sample below is an example of a basic IVR on a Flask framework with the Python SDK. However, you can replicate this demo with any of our other SignalWire SDKs and a valid web framework.

This sample will accept incoming calls and play a prompt for callers asking them if they would like to record a voice message or speak to an agent. If the caller selects the record message option, it will redirect to a Flask route containing instructions to record a voice message and play a goodbye prompt before hanging up.

If the caller selects the team member option, the call with be forwarded to a variety of agent phone numbers. You can simultaneously call up to 10 numbers. The first caller to pick up the phone will be connected to the caller and the rest of the called numbers will be hung up. IVR's can of course get much more complex than this, and you can view some examples of full IVRs with conference functionality, queueing/enqueuing, and messaging in our guides and applications sections.

from flask import Flask, request
from signalwire.voice_response import VoiceResponse, Dial, Gather

app = Flask(__name__)

# This route will accept the incoming call and direct to other routes based on selection. 
@app.route("/default", methods=['GET', 'POST'])
def acceptCall():
        # instantiate the voice response as repsonse 
    response = VoiceResponse()

    # check if any keypad digits were already pressed when accessing this route 
    if 'Digits' in request.values:
        selection = request.values['Digits']
                
        # redirect to record message route if selection is 1 
        if selection == '1':
            response.redirect('/recordMessage')
            return str(response)
        # redirect to forward call route if selection is 2 
        elif selection == '2':
            response.redirect('/forwardCall')
            return str(response)
        # if no input is selected, ask user to make a choice and replay prompt 
        else:
            response.say("Please select 1 or 2 so that we can direct your call!")

    # if no digits have been passed through, assume this is callers first time hearing prompt and play welcome message          
    response.say("Hello! Thank you for calling SignalWire.")

    # begin gather and specify that we only expect 1 digit to be pressed 
    gather = Gather(num_digits=1)
    
    # play gather prompt with decisions for user to make 
    gather.say("If you would like to leave a message for our team, please press 1. "
               "If you would like to speak to a team member, please press 2.")
        
    # append the gather to response, as response is the base element 
    response.append(gather)
    
    # now that input is gathered, redirect to default so the input can be processed and we can go to the correct route  
    response.redirect('/default')
    
   
    # convert response to string (this is KEY and required for the compatibility API in ALL languages))
    return str(response)


 # this route defines instructions to record a voicemail from the caller 
@app.route("/recordMessage", methods=['GET', 'POST'])
def recordVoicemail():
    # instantiate voice response as response 
    response = VoiceResponse()
    
    # play a short prompt for caller 
    response.say(
        "Please leave your name and number along with how we can help after the beep, and we will get back to you as "
        "soon "
        "as we can. When you are finished recording, hang up, or press the pound key.")
        # start recording, set max length 15 seconds, set transcribe to true, and set the action url to redirect to the hangup call route when # is pressed
    response.record(max_length=15, finish_on_key='#', transcribe=True, action="/hangupCall")
    
    # convert response to string (this is KEY and required for the compatibility API in ALL languages)
    return str(response)

# this route hangs up the call after the voicemail is complete 
@app.route("/hangupCall", methods=['GET', 'POST'])
def hangupCall():
    # instantiate voice response as response
    response = VoiceResponse()
    
    # play a short goodbye message and hangup the call 
    response.say("Thank you for your message. Goodbye!")
    response.hangup()
    
    # convert response to string (this is KEY and required for the compatibility API in ALL languages))
    return str(response)



@app.route("/forwardCall", methods=['GET', 'POST'])
def forwardCall():
    # instantiate voice response as response
    response = VoiceResponse()
    # create a dial, record from when ringing starts, send updates to user's server with recording status callbacks
    dial = Dial(record='record-from-ringing', recording_status_callback='https://example.com/recording_status')
    
    # dial three agent numbers, whichever picks up first gets the call 
    dial.number('123-456-7890')
    dial.number('987-654-3210')
        dial.number('102-938-4750')

        # add dial to response 
    response.append(dial)
    
    # convert response to string (this is KEY and required for the compatibility API in ALL languages))
    return str(response)

# run app in debug mode - allows for you to make changes without having to stop and rerun the code
if __name__ == "__main__":
    app.run(debug=True)

Did this page help you?