How to Validate Twilio Event Streams Webhooks in PHP
Time to read: 6 minutes
How to Validate Twilio Event Streams Webhooks in PHP
One of Twilio's stand out features is its Event Streams Webhooks. These are HTTP requests where the body of each webhook is a JSON array of CloudEvents.
Without going into too much detail, webhooks let your application know when specific events happen, such as receiving an SMS message or receiving an incoming phone call, and respond to them accordingly.
Requests include details of the event, such as the body of an incoming message, the phone or WhatsApp number it was sent from, and the event's name. With that information, your application can become extremely flexible and powerful.
However, like all application input — regardless of its source — you can't blindly trust it. You must validate it! The same goes for Event Streams Webhooks.
Gladly, Twilio makes this pretty trivial. Each webhook request is signed, contained in the X-Twilio-Signature
header. The signature, in combination with your account's Auth Token, the body of the webhook, and the request URL, are used to verify that the webhook came from Twilio.
In this short tutorial, you're going to learn how to do just that. Let's begin.
Prerequisites
To follow along with this tutorial, you'll need the following:
- PHP 8.3
- Composer installed globally
- ngrok and an an ngrok account (whether free or paid)
- A Twilio account (either free or paid). If you are new to Twilio, click here to create a free account
- Your preferred text editor or IDE, and your preferred web browser
How the app will work
The app is pretty small, having only one route (/webhook
), where it will receive webhook requests from Twilio. When requests are received the app will attempt to validate them. If the request is valid, it will write that to the app's log file, along with the event details and request headers. If it's invalid, then "Invalid signature" will be written to the log file.
Create the core of the project
Okay, as (almost) always in my tutorials, we're going to start off by creating the project's core file and directory structure. To do so, run the command below.
If you're using Microsoft Windows, don't worry about the -p
option, as it's not required.
Add the required packages
The next thing to do is to install the required packages, those being:
- The Slim framework: We'll be using Slim as it's one of PHP's lightest and most unobtrusive frameworks
- Slim's PSR-7: This is a PSR-7 implementation for the Slim framework which makes it super simple to work with HTTP requests and responses
- PHP-DI's Slim Bridge: This package simplifies integrating PHP-DI as Slim's DI container
- Twilio's PHP Helper Library: This package simplifies interacting with Twilio's APIs in PHP
- PHP Dotenv: This package loads environment variables into PHP's
$_SERVER
and$_ENV
superglobals, making it trivial to configure apps during local development
To install them, run the following command.
Add the ability to validate a webhook
Now, it's time to add the PHP validation code. Start by creating a file named index.php in the public directory. Then, in that file, add the following code.
The code starts by using PHP Dotenv to load the variables defined in .env, which we'll define shortly, into PHP's $_SERVER
and $_ENV
superglobals. It will throw an exception if either 'NGROK_URL'
or TWILIO_AUTH_TOKEN
are not defined or are empty.
It then defines a small Slim app with a single service in its DI container (LoggerInterface
) to provide application output. A single route (/webhook
) accessible via HTTP POST, for receiving and validating event stream webhooks.
The route validates the webhook with three components:
- The
X-Twilio-Signature
header - The request URL, built from a combination of the base URL, defined in
NGROK_URL
(which will be set shortly), the route's path (/webhook
), and the request's query string - The raw request body
If the request is valid, it writes "Valid signature. Processing event." to the log file, along with the request's body and headers, and "Valid signature." as the response's body.
Otherwise, it writes "Invalid signature." to the log file along with the contents of the X-Twilio-Signature
header, the compiled request URI, event data and request headers to the log file, and "Invalid signature." as the response's body.
Configure the required environment variables
With the code written, you next need to set a handful of environment variables which the app requires; these are NGROK_URL
and TWILIO_AUTH_TOKEN
, which are required to validate the webhook.
First, create a new file in the project's top-level directory named .env. Then, in that file paste the configuration below.
Next, in the Account Dashboard of your Twilio Console, copy your Auth Token and paste it into .env in place of <TWILIO_AUTH_TOKEN>
.
Make the application publicly accessible and retrieve its public URL
You next need to expose the application to the public internet, so that Twilio can send webhook requests to it. To do that, we're going to use ngrok, by running the command below.
Run the command below to create a secure tunnel to port 8080; the application doesn't need to be running for this to work.
In the terminal output, you'll see a Forwarding URL. Copy that and paste it into .env in place of <NGROK_URL>
.
Create a Sink Resource
The next step is to create a Sink resource. Technically, these are:
the destinations to which events selected in a subscription will be delivered
For the purposes of this application, they're a quick and easy way to simulate sending webhook requests to our application so that we can validate them.
To create one, in the Twilio Console, navigate to Explore Products > Developer Tools (right near the bottom of the page) > Event Streams. There, click "Create new sink" to start the process.
Then, set Sink description to "Validate Webhook Sink", set sink type to "Webhook", and click "Next step".
After that, set Destination to the Forwarding URL printed by ngrok to the terminal, with "/webhook" at the end, leave Method set to "POST", and click Finish. Then, in the confirmation popup that appears, click "View Sink Details", taking you to the sink's properties page.
Test that the application works
Right, let's check that the code works as expected. To do that, first start the application listening on port 8080 by running the following command in your terminal.
Now, back in the Twilio Console, on the Sink resource's properties page, click "Send test event". Back in your terminal, you should see output similar to the following printed to the terminal if the webhook validated successfully:
Subscribe to incoming messages
Finally, let's subscribe to an event so that you can see the application responding as it normally would in production. To do that, back in the Twilio Console, under Explore Products > Developers > Event Streams, click Create in the top right corner, then click New subscription.
Then, in the Create new subscription form, select the "Validate Webhook Sink" from the Select sink dropdown, and add a description for the subscription, in the Subscription description field, as in the screenshot below.
Then, scroll down to the Product groups section, click Messaging in the left-hand navigation menu, and under Messaging, scroll down to Inbound Message. There, under Action, you'll see Received. On its far right-hand side, set the dropdown to "1". Then, click Create Subscription in the bottom right-hand corner of the page.
Test the subscription
Now, let's test that the subscription works. Back in the Twilio Console, navigate to Explore Products > Messaging > Try it out > Send a Whatsapp message. There, first, follow the instructions to connect to the WhatsApp Sandbox.
After you've done that, with WhatsApp, send a message to your Twilio number. In your log file, you should see three log entries. The first should be "Valid signature. Processing event.". The second should be "Event received" along with the event's details (an example of which you can see below, formatted for readability). The third should be "Request headers", along with a list of the request's headers.
That's how to how to validate Twilio webhooks in PHP
There's a (little) bit to do to get it all set up and running. However, after it's done, you can now validate that the webhook data you receive truly is from Twilio. Remember, no input should ever be trusted unconditionally.
Want to validate Event Streams Webhooks even easier?
If you want to validate Event Streams Webhooks even easier than in the code you've just seen, I'm creating integrations for some of PHP's most popular frameworks.
Find yours in the list below (which I'll update as more are released):
Matthew Setter is a PHP and Go editor in the Twilio Voices team and a PHP and Go developer. He’s also the author of Mezzio Essentials and Deploy With Docker Compose. You can find him at msetter[at]twilio.com and on LinkedIn and GitHub.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.