Securing Webhooks from Twilio in PHP

November 26, 2019
Written by

Securing Webhooks from Twilio in PHP

Webhooks make up the backbone of interactions with services like Twilio. When an event happens on one of our numbers, maybe it receives an SMS, then Twilio sends an HTTP request to our server with information on what happened. Typically we'll reply with instructions on how to respond to the event, perhaps we'll want to reply with an SMS of our own.

Diagram showing how SMS messages interact with Twilio and your application

Webhooks make interacting with events from Twilio really straightforward, but they also open our application to be manipulated in unexpected ways. If someone knows your webhook URL, they can craft HTTP requests to generate fake events that were never intended.

To protect from counterfeit requests Twilio includes a signature header X-Twilio-Signature in every webhook sent to your server. We can use our secret Twilio authentication token plus the data submitted to the server to generate our own signature. We can then compare this to the header signature to ensure we only handle genuine requests sent by Twilio.

Before we get started, you’ll need the Twilio SDK for PHP, we can install it using Composer by running the command from the root of our project:

composer require twilio/sdk

The documentation explains how the signature is generated and how we can make our own for comparison. We don't need to roll our own implementation as the Twilio SDK already includes the RequestValidator class that can perform a comparison for us. The RequestValidator class takes our authentication token as a constructor argument, and the request signature, request URL and an array of data to tell us if the request is valid or not.

<?php declare(strict_types=1);
require_once('vendor/autoload.php'); // enable autoloading to use the Twilio SDK

$authToken = getenv('TWILIO_AUTH_TOKEN'); // stored in an env var for safety
$validator = new \Twilio\Security\RequestValidator($authToken);

$signature = $_SERVER['HTTP_X_TWILIO_SIGNATURE'] ?? null; // get the signature from the HTTP header
$url = 'https://geeh.ngrok.io/hook.php';
// the url that this page is found at - should match the url set for this webhook in the Twilio console

if(!$validator->validate($signature, $url, $_POST)) {
   throw new \Exception('Invalid signature'); // throw an exception if the signatures don't match
   // we may want to consider returning a custom status code like 403 
}

header('Content-Type: text/plain'); // cool hack for easily replying to SMS
echo 'Ahoy hoy'; // see https://www.youtube.com/watch?v=XWgzf-4lsFY

We must include this check on every endpoint of our application that handles webhooks. It's also vital that we turn on TLS and only use HTTPS so that requests are end-to-end encrypted and can't be affected by man-in-the-middle attacks. In a production system, it's worth baking this into your framework. For example, if you use Laravel, you could write a middleware that would handle this automatically on any routes you add it to.

Let me know how you get on protecting your webhooks with the RequestValidator or if you'd like to know more. I can't wait to see what you build.

  • Email: ghockin@twilio.com
  • Twitter: @GeeH