Securing your Twilio webhooks in Java
Time to read: 4 minutes
Using webhooks is a common and powerful way to configure your Twilio phone numbers to respond to incoming phone calls or SMS (or faxes). You provide a URL and Twilio makes an HTTP request to that URL when a call or message comes in to find out how to respond.
One implication of this is that the URL you provide has to be accessible from the internet, so when you're building your web app to handle the webhooks, how can you know for sure that the requests are really coming from Twilio and not malicious or opportunistic internet scoundrels?
The answer is that all valid webhook requests from Twilio are signed in a header called X-Twilio-Signature
. The algorithm used to create the signature is described in detail in our docs. Briefly, the ingredients of the signature are:
- The details of the request
- The webhook URL as configured in Twilio
- Your account's auth token
- A secure hashing algorithm.
You can use the details of any incoming request to recreate the signature in your application and check that it matches the value in the header. Because your auth token is only known to you and Twilio, if the signatures match you know the request came from Twilio and you can process the message with confidence. The Twilio Java helper library has a RequestValidator
class to do just this.
In this post I'll show how to add this validation as a Spring HandlerInterceptor
. You can annotate your webhook methods with a custom annotation like @ValidateTwilioSignature
to apply the validation and keep validation code out of your @RestController
classes.
The code for this post is on GitHub, so start off by cloning the repository and importing it into your IDE. It uses Java 11.
What's in the code?
First of all, have a look in the WebhookHandler
class. This class has two methods, one each for handing GET
and POST
requests for an incoming SMS webhook. They are largely similar, so let's just look at the POST
method:
If you've built Spring apps to handle webhook requests before this will look familiar. The response from this method is TwiML to send an SMS response with a cheery dinosaur emoji:
The only unusual thing (apart from the dinosaur) is the @ValidateTwilioSignature
annotation on the method. Let's see where that comes from.
Creating a custom annotation
The annotation is defined in just a few lines of code in ValidateTwilioSignature.java
:
The annotation doesn't need to contain any logic. It just needs to exist so that it can be placed on a method and discovered at runtime.
Intercepting incoming HTTP requestsSpring provides a HandlerInterceptor
interface to preprocess incoming HTTP requests. Classes implementing this interface can implement the preHandle
method. This method will be called for every incoming HTTP request, and should return a boolean
which indicates whether the request should be passed on to the handler method. Handlers have access to the incoming HTTP request, the response, and the target handler method. You can see the whole TwilioValidationHandlerInterceptor
on GitHub - let's walk through it in detail:
This class is instantiated with two values from application.properties
, which you can find in src/main/resources
:
${twilio.auth.token}
- this is the auth token that you can find at https://twilio.com/console. This is needed to calculate the signature.${twilio.webhook.url.override}
- Twilio calculates its signature based on the webhook URL that you have configured. If your application is behind a proxy that rewrites URLs, such as a load-balancer, SSL-offload or ngrok, then the incoming request that your server sees might have a different URL than the request that Twilio made. In that case, you need to specify this value to be the URL that you configured Twilio to use. Don't include any query parameters on this URL as they will be passed through the proxy unchanged. If requests from Twilio reach your server without being rewritten you can leave this blank.
The constructor uses the authToken
to create a RequestValidator
which is used in the preHandle
method. Lets see that in detail:
First thing, check whether the handler method has our annotation on it. If getMethodAnnotation(ValidateTwilioSignature.class)
returns null
then the request is destined to reach a method without our custom annotation, so we have no business blocking this request. In this case, return true
to exit early and let the application handle the request.
We need to get the X-Twilio-Signature
from the headers. The normalizedRequestUrl
method builds validationUrl
either from the request or the override if you configured that. I won't go into details but you can see the method in full on GitHub.
Now we're ready to validate the request! We need to pass the validationUrl
, any body parameters and the signature from the X-Twilio-Signature
header.
Extracting parameters from the body of an HTTP request isn't as simple as it sounds using the Servlet API, because HttpServletRequest
merges body parameters and query string parameters into a single map. The extractOnlyBodyParams
works around this - the implementation is on GitHub.
Once we have extracted the correct values from the request, the validation is the same for GET
and POST
requests. Any other method will fall into the default
branch of the switch
and be rejected.
Joining it all together
The last thing to do with the WebhookHandler
class, the ValidateTwilioSignature
annotation and the TwilioValidationHandlerInterceptor
is to configure them all to work together in an application. This is done with a class annotated with Spring's @Configuration
annotation. I called mine WebConfig
:
Running the project
Once you have added your auth token to application.properties
(you can find your auth token on your Twilio console), and configured the webhook url override if you need one, you can start up the application with ./mvnw spring-boot:run
, set up any proxy you need, and set your webhook URLs for your phone number. Then you're good to go, and safe to boot!
Summing up
If you're using public URLs as webhooks for your Twilio phone numbers, you should definitely validate that HTTP requests are coming from Twilio and not malicious or opportunistic third-parties. This post has shown how to do that for a Spring application, and the same concepts will apply for any other web framework. For more details and recommendations to improve the security of your webhooks, check the Webhook Security page in our docs.
What are you securing? Tell me about it on Twitter or by email - I can't wait to see what you build!
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.