Simple Twilio based IVR

The Twilio node is not needed to handle voice requests as this can be accomplished using regular HTTP input and output nodes. The way it works is that when a call is received on your Twilio number, the Twilio server will pass the information to you via the Voice URL you define in your number setup. The Twilio requests require a response that is the TWiML payload. This forms the basis of the outgoing message. Full details of the TWiML markup are found in the Twilio help pages. The example flows use the HTTP input and output nodes to provide the link to Twilio.

My Node-RED flow listens for a /twiliovoice url. This must be accessible from outside of your local network. I did this using my DuckDNS account. My router then uses port forwarding to redirect the incoming request to my linux server running Node-RED . The external URL uses different ports to Node-RED and this is all catered for in the port forwarding on the router.

Your Twilio number needs to be have the correct Request URL and method setting up. This flow uses the POST method. You can also set a fallback URL by selecting the Optional settings. This ideally would be a simple TWiML file located on an externally hosted URL to respond when your Node-RED system fails to respond for any reason. At present I've not set this up.

The flows here demonstrate a simple voice menu with two choices, one to read a global context value and return the current power usage. The second is to control a device, in this case publish a value to a MQTT topic that is picked up by another subscriber to ring a bell.

The final part of the flows is to convert the created JSON body to the XML based TWiML response. This is done using the handy Json2XML node.

The flows can be further extended to handle more commands by adding more conditions to the switch node. The main thing to remember is that the flows must return a valid TWiML response in the payload.

To aid in debugging this functionality, use the Twilio dev-tools menu to show any failures.

Your Twilio account has a lot of information that includes the dev tools plus what requests and responses have been processed.

[{"id":"3fd7311e.c028ce","type":"mqtt-broker","broker":"localhost","port":"1883","clientid":""},{"id":"698c27e6.9673d8","type":"json2xml","name":"J2XML TwiML Response","root":"Response","x":717,"y":283,"z":"8a59ed8a.75a61","wires":[["f767454d.0898b8","c8cdcfd4.37323"]]},{"id":"f767454d.0898b8","type":"http response","name":"Send TwiML","x":958,"y":283,"z":"8a59ed8a.75a61","wires":[]},{"id":"6c74d317.938b2c","type":"http in","name":"TwilioVoice incoming","url":"/twiliovoice","method":"post","x":121,"y":280,"z":"8a59ed8a.75a61","wires":[["88efab86.771058","52a0fad4.ad5f04"]]},{"id":"88efab86.771058","type":"function","name":"Process Call","func":"// Create JSON object for payload response\n\nvar messageObj = { \"Gather\": [{ \"@\": { \"action\": \"http://yourhost:port/twilioivr\",\"numDigits\":\"1\"},\n\t\t \"Say\":  [ { \"@\": { \"voice\": \"woman\" }, \"#\": \"Welcome to Node-Red. For power usage press 1. To ring the bell press 2. For anything else press 3\" }]}],\n\t\t \"Say\":  [ { \"@\": { \"voice\": \"woman\" }, \"#\": \"You didnt press a key, goodbye\"}]};\n/* The sort of example XML we are trying to create is shown below:\n\n    <Gather action=\"handle-user-input.php\" numDigits=\"1\">\n        <Say>Welcome to TPS.</Say>\n        <Say>For store hours, press 1.</Say>\n        <Say>To speak to an agent, press 2.</Say>\n        <Say>To check your package status, press 3.</Say>\n    </Gather>\n    <!-- If customer doesn't input anything, prompt and try again. -->\n    <Say>Sorry, I didn't get your response.</Say>\n*/\nmsg.payload = messageObj;\nreturn msg;","outputs":1,"x":433,"y":282,"z":"8a59ed8a.75a61","wires":[["698c27e6.9673d8"]]},{"id":"49cc3109.b633d","type":"http in","name":"TwilioVoice Handle","url":"/twilioivr","method":"post","x":125,"y":360,"z":"8a59ed8a.75a61","wires":[["b07a8d73.4f857","21e1cdbe.de1e32"]]},{"id":"b07a8d73.4f857","type":"function","name":"Handle Voice","func":"// Use 2 outputs, 2nd is for mqtt topic/payload combination\n// Could be useful to post other caller info to a topic\nvar mqttOut = null;\nvar responseMsg = \"\";\n\nvar name = \"wholehouse\";\nvar reading = context.global[name];\nvar digit = msg.payload.Digits;\nif( digit == \"1\" ) {\n\tresponseMsg = \"Your power usage is now \" + reading + \" Watts.\";\n} else if( digit == \"2\" ) {\n\tresponseMsg = \"Ring my bell.\";\n\tmqttOut = {topic: \"bellduino/ring\", payload: \"ringmybell\" };\n} else {\n\tresponseMsg = \"You pressed \" + digit;\n}\n\n// Create JSON object for payload response\nvar messageObj = { \"Say\": [ { \"@\": { \"voice\": \"woman\" }, \"#\": responseMsg } ] } ;\n\nmsg.payload = messageObj;\nreturn [msg, mqttOut];","outputs":"2","x":433,"y":369,"z":"8a59ed8a.75a61","wires":[["698c27e6.9673d8"],["753eb82a.8ac148","a7f09d49.580f6"]]},{"id":"c8cdcfd4.37323","type":"debug","name":"","active":true,"complete":false,"x":952,"y":390,"z":"8a59ed8a.75a61","wires":[]},{"id":"21e1cdbe.de1e32","type":"debug","name":"","active":true,"complete":false,"x":422,"y":426,"z":"8a59ed8a.75a61","wires":[]},{"id":"753eb82a.8ac148","type":"mqtt out","name":"Post message","topic":"","broker":"3fd7311e.c028ce","x":728,"y":391,"z":"8a59ed8a.75a61","wires":[]},{"id":"a7f09d49.580f6","type":"debug","name":"","active":true,"console":"false","complete":"true","x":702,"y":437,"z":"8a59ed8a.75a61","wires":[]},{"id":"52a0fad4.ad5f04","type":"debug","name":"","active":true,"console":false,"complete":false,"x":430,"y":214,"z":"8a59ed8a.75a61","wires":[]}]

Flow Info

created 4 months, 2 weeks ago

Node Types

  • debug (x4)
  • function (x2)
  • http in (x2)
  • http response (x1)
  • json2xml (x1)
  • mqtt out (x1)
  • mqtt-broker (x1)
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option