PHP: How to Validate an SMS from SignalWire Webhook

SignalWire - How to validate incoming smsI ran into this problem recently, but was unable to find an answer.  There is very little (essentially non-existant) documentation from SignalWire on how to validate / verify that an SMS coming to your webhook was really sent by SignalWire, especially in PHP which seems to be the forgotten stepchild of SignalWire. 

I finally found the answer, so I wanted to share it here for anyone else with the same problem.

For those that may not know, SignalWire's LAML webhook method is meant to be 100% compatible with Twilio, so it took some digging into Twilio's documentation to find the answer.

First, you will want to use composer to download the PHP libraries as described in SignalWire's documentation as you normally might.

Note: This assumes you are receiving your data through the POST method from the webhook.

In your "handle incoming sms" function, you will start something like this:

require_once('signalwire/vendor/autoload.php');  // bring in the signalwire PHP library we've downloaded using composer
$signing_key = 'PF_KJ45jk3jhy....uU3';   // On your SignalWire API page.
$validator = new Twilio\Security\RequestValidator($signing_key);
$headers = getallheaders();
$signature = $headers['X-Twilio-Signature'];
$url = 'https://example.com/handle-incoming-sms';

First, let's examine these variables:

  • $signing_key - a value found on your SignalWire API page.
  • $validator - Notice that we are using a class from the Twilio package, and passing it the $signing_key. This is correct, as SignalWire uses Twilio's open source code in its PHP library.
  • $headers - Take advantage of a built-in PHP function to get all the headers we've been sent through the POST.
  • $signature - This is a string which we've been sent in the header. Notice it is still named after Twilio.  The validator object will need this string to do its job.
  • $url - This is the exact URL that the webhook sent the POST values to.

Let's keep going:

unset($_POST['some_non_signalwire_var']);
unset($_POST['some_other_non_signalwire_var']);
ksort($_POST);

Notice that we remove any POST variables which might be in there which did not come from SignalWire.  This may not be necessary in your specific case, but it was in mine.

Vitally important: We must use ksort() to make sure the $_POST array's keys are in alphabetical order!  This was the magic ticket which I found which made everything else work.

Finally, let's actually perform the validation:

$result = $validator->validate($signature, $url, $_POST);
if (intval($result) === 0) {
  // FAILURE.  DID NOT VALIDATE.  Do code here to handle that.
}

Here I explicitly convert $result into an integer so I can test for "0".  It is unclear in the documentation if it returns FALSE, 0, etc.  This might be overkill, but I like to have explicit types and use === whenever possible.

And that's it!  If your code executes past the validate() check, that means it was validated and you can now manage your incoming sms from SignalWire.