In this guide we'll cover how to secure your Express application by validating incoming requests to your Twilio webhooks are, in fact, from Twilio.
Securing your Express app with Twilio Node SDK's is simple. The Twilio SDK comes with an Express middleware which is ready to use.
Let's get started!
The Twilio Node SDK includes a webhook()
method which we can use as an Express middleware to validate incoming requests. When applied to an Express route, if the request is unauthorized the middleware will return a 403 HTTP response.
Confirm incoming requests to your Express routes are genuine with this custom middleware.
_41// You can find your Twilio Auth Token here: https://www.twilio.com/console_41// Set at runtime as follows:_41// $ TWILIO_AUTH_TOKEN=XXXXXXXXXXXXXXXXXXX node index.js_41//_41// This will not work unless you set the TWILIO_AUTH_TOKEN environment_41// variable._41_41const twilio = require('twilio');_41const app = require('express')();_41const bodyParser = require('body-parser');_41const VoiceResponse = require('twilio').twiml.VoiceResponse;_41const MessagingResponse = require('twilio').twiml.MessagingResponse;_41_41app.use(bodyParser.urlencoded({ extended: false }));_41_41app.post('/voice', twilio.webhook(), (req, res) => {_41 // Twilio Voice URL - receives incoming calls from Twilio_41 const response = new VoiceResponse();_41_41 response.say(_41 `Thanks for calling!_41 Your phone number is ${req.body.From}. I got your call because of Twilio´s_41 webhook. Goodbye!`_41 );_41_41 res.set('Content-Type', 'text/xml');_41 res.send(response.toString());_41});_41_41app.post('/message', twilio.webhook(), (req, res) => {_41 // Twilio Messaging URL - receives incoming messages from Twilio_41 const response = new MessagingResponse();_41_41 response.message(`Your text to me was ${req.body.Body.length} characters long._41 Webhooks are neat :)`);_41_41 res.set('Content-Type', 'text/xml');_41 res.send(response.toString());_41});_41_41app.listen(3000);
If your Twilio webhook URLs start with https://
instead of http://
, your request validator may fail locally when you use ngrok or in production if your stack terminates SSL connections upstream from your app. This is because the request URL that your Express application sees does not match the URL Twilio used to reach your application.
To fix this for local development with ngrok
, use ngrok http 3000
to accept requests on your webhooks instead of ngrok https 3000
.
If you write tests for your Express routes those tests may fail for routes where you use the Twilio request validation middleware. Any requests your test suite sends to those routes will fail the middleware validation check.
To fix this problem we recommend passing {validate: false}
to the validation middleware twilio.webhook()
thus disabling it. In Express applications it's typical to use NODE_ENV
as the value to use to determine the environment the application is running in. In the code example, when NODE_ENV
is 'test'
, the validation middleware should be disabled.
Use environment variable to disable webhook validation during testing.
_48// You can find your Twilio Auth Token here: https://www.twilio.com/console_48// Set at runtime as follows:_48// $ TWILIO_AUTH_TOKEN=XXXXXXXXXXXXXXXXXXX node index.js_48//_48// This will not work unless you set the TWILIO_AUTH_TOKEN environment_48// variable._48_48const twilio = require('twilio');_48const app = require('express')();_48const bodyParser = require('body-parser');_48const VoiceResponse = require('twilio').twiml.VoiceResponse;_48const MessagingResponse = require('twilio').twiml.MessagingResponse;_48_48const shouldValidate = process.env.NODE_ENV !== 'test';_48_48app.use(bodyParser.urlencoded({ extended: false }));_48_48app.post('/voice', twilio.webhook({ validate: shouldValidate }), (req, res) => {_48 // Twilio Voice URL - receives incoming calls from Twilio_48 const response = new VoiceResponse();_48_48 response.say(_48 `Thanks for calling!_48 Your phone number is ${req.body.From}. I got your call because of Twilio´s_48 webhook. Goodbye!`_48 );_48_48 res.set('Content-Type', 'text/xml');_48 res.send(response.toString());_48});_48_48app.post(_48 '/message',_48 twilio.webhook({ validate: shouldValidate }),_48 (req, res) => {_48 // Twilio Messaging URL - receives incoming messages from Twilio_48 const response = new MessagingResponse();_48_48 response.message(`Your text to me was ${req.body.Body_48 .length} characters long._48 Webhooks are neat :)`);_48_48 res.set('Content-Type', 'text/xml');_48 res.send(response.toString());_48 }_48);_48_48app.listen(3000);
Validating requests to your Twilio webhooks is a great first step for securing your Twilio application. We recommend reading over our full security documentation for more advice on protecting your app, and the Anti-Fraud Developer's Guide in particular.
To learn more about securing your Express application in general, check out the security considerations page in the official Express docs.