Weather By Phone

Weather By Phone lets a caller check the weather by phone. Twilio answers the call, the application asks the caller for their US zip code, the application looks up the weather using a remote web service and reads the response to the caller.

This demo is written in Python for Google App Engine. This code can be adapted for other Python web frameworks such as Django.


A user enters his/her phone number. Twilio will call a specified third party and initialize a call between that party and the user. It's that simple.


This demo shows the usage of the TwiML <Play>, <Say>, and <Gather> verbs.



  • 1

    • When a call is answered by Twilio, Twilio fetches the '/' page which is configures to query the GatherPage object.

    • 2

      • The GatherPage object contains two methods get() and post() which map to the same function and return a page generated by the template gather.xml

          • howtos/weatherbyphone/
            def xml_response(handler, page, templatevalues=None):
                Renders an XML response using a provided template page and values
                path = os.path.join(os.path.dirname(__file__), page)
                handler.response.headers["Content-Type"] = "text/xml"
                handler.response.out.write(template.render(path, templatevalues))
            class GatherPage(webapp.RequestHandler):
                Initial user greeting.  Plays the welcome audio file then reads the
                "enter zip code" message.  The Play and Say are wrapped in a Gather
                verb to collect the 5 digit zip code from the caller.  The Gather
                will post the results to /weather
                def get(self):
                def post(self):
                    templatevalues = {
                        'postprefix': BASE_URL,
                    xml_response(self, 'gather.xml', templatevalues)

          Here is the template used to render the initial TwiML greeting.

            • howtos/weatherbyphone/gather.xml
              <?xml version="1.0" encoding="UTF-8"?>
                  <Gather method="POST" numDigits="5" action="{{ postprefix }}weather">
                      <Say>Please enter your 5 digit zipcode to hear the weather.</Say>
        • 3

          • Twilio then plays the greeting sound file, reads the "Please enter..." text to the caller and waits for 5 digits from the caller. When 5 digits have been entered by the caller, Twilio performs an HTTP POST with the results back to the 'action' handler. In this case, the hander is configured to be '' Control then passes to the /weather portion of the application and the WeatherPage object.

              • howtos/weatherbyphone/
                    def post(self):
                        zipcode = self.request.get('Digits')
                        if not zipcode:
                            self._error("Invalid zip code.", BASE_URL)
                        # strip off extra digits and keys from the Digits we got back
                        zipcode = zipcode.replace('#', '').replace('*', '')[:5]
                        weatherxml = self._fetch(zipcode)
                        if not weatherxml:
                            self._error("Error fetching weather. Good Bye.")
                            xml_response(self, 'weather.xml', self._parse(weatherxml))
                            self._error("Error parsing weather. Good Bye.")

              Because we have mapped the get() handler in the WeatherPage object we can simulate digits pressed by the caller directly in a web browser. Twilio passes back digits pressed using the 'Digits' parameter. Here is link simulating the situation if someone entered the zip code 02138:

          • 4

            • The final response is generated by fetching the weather conditions in XML format from the remote weather web service, parsing the XML, and feeding the values back in to the weather.xml template.

                • howtos/weatherbyphone/weather.xml
                  <?xml version="1.0" encoding="UTF-8"?>
                      <Say>It is currently {{ temp }} degrees fahrenheit and {{ conditions }}
                          in {{ location }}.</Say>

                And that's it! The rest of the code is input validation and error handling.