A dynamic Interactive Voice Response (IVR) lets you change call menus without rewriting your call-handling logic. This article shows a simple approach where the IVR menu is defined in a JavaScript Object Notation (JSON) file, and a lightweight Python service reads that file to generate menu responses at runtime. It explains the core endpoints used to serve menus and voicemail-style responses, how Dual-Tone Multi-Frequency (DTMF) input selects actions in a menu tree, and how to run the example either natively or in Docker.
Note: The world of telecom moves fast, and while you can definitely follow this example to build an IVR, SignalWire now offers even more tools to build both simpler or more complex IVRs. Call Flow Builder allows you to drag-and-drop your way to a phone menu, while AI agents allow you to modernize your call flows with AI at the telecom layer.
In this code snippet, we take the well-known functionality of an interactive voice response system (IVR) and introduce a simple yet effective mechanism to make it almost infinitely flexible.
We're all familiar with the menu system that greets us on many of the calls we make to companies in order to route us to the proper agent. They are, perhaps, loved (by the companies using them for effective call direction) and hated (by those calling the companies) in equal measure.
By applying some good sense to their construction, they can be more palatable to callers. We have some ideas about how to build an IVR that won't send your customers running.
This is where the mechanism in this post comes in. We've created a framework that can be populated with the contents of a JSON file, thus making menu systems both flexible and scalable without having to go back to the code.
You will need a machine with Python installed, the SignalWire SDK, a SignalWire phone number (which you can purchase at any time in your dashboard), and optionally, Docker if you decide to run it in a container.
Methods and Endpoints
Endpoint: /get_menu
Methods: GET OR POST
Endpoint: /get_voicemail
Methods: GET OR POST
Requests generate a menu. get_menu will default to main menu if no menu is specified. The get_menu will look for dtmf entries to select an action for routing and moving along a menu tree.
Requests to get_voicemail produce a LaML Voice response to simulate an endpoint.
Set up menus
Edit the menus.json file.
This example file has 3 menus. Each menu contains an index, which is equal to the key press on the menu, a verbiage, and an action. You can edit the verbiage, action, and key presses. You can also add as many menu options as you want.
{
"main": {
"1": {
"verbiage": "For sales press 1",
"action": "/get_menu?menu=sales"
},
"2": {
"verbiage": "For tech support press 2",
"action": "/get_menu?menu=tech"
}
},
"sales": {
"1": {
"verbiage": "For partners, press 1",
"action": "/get_voicemail?group=sale_partners"
},
"2": {
"verbiage": "For assistance with a purchase, press 2",
"action": "/get_voicemail?group=sale_support"
}
},
"tech": {
"1": {
"verbiage": "For issues with your internet, press 1",
"action": "/get_voicemail?group=internet_support"
},
"2": {
"verbiage": "For issues with your cell phone, press 2",
"action": "/get_voicemail?group=mobile_support"
}
}
}Set up the Environment File
Copy from example.env and fill in your values, and 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 Snippets. Must include the `+1` , e$ SIGNALWIRE_NUMBER= # Hostname HOSTNAME=
Build and run on Docker
You can use our pre-built image from Docker Hub. For Python:
docker pull signalwire/snippets-simple-dynamic-ivr:pythonOr, build your own image:
docker build -t snippets-simple-dynamic-ivrRun your image:
docker run --publish 5000:5000 --env-file .env snippets-simple-dynamic-ivrThe application will run on port 5000.
Build and run natively
Replace the environment variables, and from command line run python3 app.py
If you have any questions while building your IVR, please join our Discord and chat with others in the SignalWire community!
Frequently asked questions
What is a dynamic Interactive Voice Response (IVR)?
A dynamic IVR is an IVR menu system where the menu structure and routing options can be updated without changing application code. Instead of hard-coding every option, the application loads menu definitions at runtime, for example from a JSON file, and uses those definitions to decide what to play and where to route calls.
How can an IVR menu be driven by a JSON file?
One common pattern is to store each menu as a set of options in a JSON file, including the key press, the prompt text, and the action to take. The IVR service reads the file, returns the correct menu when requested, and routes to the next menu or action based on the caller’s selection.
What is Dual-Tone Multi-Frequency (DTMF), and how does it control IVR routing?
Dual-Tone Multi-Frequency (DTMF) is the keypad input callers send when they press numbers on a phone. In an IVR, DTMF input is matched against menu options, and the system routes the call to the corresponding action or next menu.