Twilio

If you’ve used Twilio to send or receive SMS messages before stop reading now, you already know how crazy simple it is.

Twilio is a cloud based service that enables you to programmatically make and receive phone calls and SMS messages using their simple API. We’ll see how to receive and send SMS messages using Twilio and a simple Django app.

Before you can start receiving SMS messages, you have to create a Twilio account. You can start with a limited version of the service for free, go to https://www.twilio.com/try-twilio. The free service will get you one phone number and any SMS messages sent will be prefixed with some promotional text inserted by Twilio. Additionally the free level limits the number of SMS messages that be sent and received.

Receiving and sending SMS Messages

SMS messages can be retrieved in a pull fashion via the SMS API, but for this example we’ll opt to have Twilio push any text messages sent to our number to our app. You can associate the Twilio phone number to a url to which Twilio will do a http POST.

We’ll be using the Twilio twilio python library. It is available via pypi and therefore can be installed via “pip install twilio” or “easy_install twilio”. This is the only python dependency.

Let’s get right to it. Here’s the Django view code:

import abc

from django.conf import settings
from django.core.exceptions import ValidationError
from django.http import HttpResponse, HttpResponseForbidden
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import View
from twilio import twiml
    from twilio.util import RequestValidator

from dvapp import models

class TwilioSmsView(View):
    __metaclass__ = abc.ABCMeta

    @staticmethod
    def _validate_request(request):
        """
        Make sure the request is from Twilio and is valid.
        Ref: https://www.twilio.com/docs/security#validating-requests
        """
        if 'HTTP_X_TWILIO_SIGNATURE' not in request.META:
            return 'X_TWILIO_SIGNATURE header is missing ‘ \
                   'from request, not a valid Twilio request.'

        validator = RequestValidator(TWILIO_AUTH_TOKEN)

        if not validator.validate(
            request.build_absolute_uri(), 
            request.POST, 
            request.META['HTTP_X_TWILIO_SIGNATURE']):

            return 'Twilio request is not valid.’


    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        bad_request_message = self._validate_request(request)
        if bad_request_message:
            logger.warn(bad_request_message)
            return HttpResponseForbidden()
        return super(TwilioSmsView, self).dispatch(
            request, *args, **kwargs)


    def post(self, request, *args, **kwargs):
        r = twiml.Response()
        # Twilio should put the message in the Body param.
        r.message(msg=self.get_response_message(
            request.POST.get('Body'), *args, **kwargs))
        return HttpResponse(r.toxml(), 
                            content_type='application/xml')

    @abc.abstractmethod
    def get_response_message(self, request):
        raise NotImplementedError()


class VerificationMessageView(TwilioSmsView):

    def get_response_message(self, message, *args, **kwargs):
        verification_request = models.VerificationRequest()
        verification_request.barcode = message
        verification_request.username = 'SMS'
        verification_request.verify()

        try:
            verification_request.full_clean()
        except ValidationError as e:
            logger.info('Validation error(s) creating a ‘
                        'verification request ‘
                        'via Twilio SMS: {}'.format(e))
            if 'barcode' in e.error_dict:
                return message + ' is not a valid barcode ‘ \
                                 'and could not be checked.'

        verification_request.save()

        # return some string which Twilio 
        # will send back to the originating number
        return verification_request.get_result_message()

The first class TwilioSmsView is used as a base class for any class based view whose response will be sent to the originating phone number via Twilio. It takes care of a few things:

  1. It makes sure that the incoming message is a valid request from Twilio and has not been tampered with. The _validate_request method makes use of the twilio RequestValidator class to compare a cryptographic hash against one sent as a header parameter.
  2. It bypasses csrf checking, notice "@method_decorator(csrf_exempt)". Text messages are inherently stateless. Twilio will not send the expected csrf token as a session-aware http request would.
  3. It extracts the text message sent from the origin number from the ‘Body’ request param.
  4. It wraps the plain text response provided by the subclass in Twiml, Twilio’s xml. Twiml is interpreted by Twilio in this case to send a message back to the origin phone number. This is where the twilio library comes in, we’re using it’s simple wrapper around Twiml. We could have generated the XML ourselves, it’s not a complicated schema.

Finally we need to wire up the url to our view. This is standard Django url.py code:

sms_patterns = patterns(‘', url(r'sms/verify/', sms_views.VerificationMessageView.as_view(), name='sms-verify'))

Note that Twilio will send all sorts of headers with the request in addition to the text message.

Crazy simple eh?