Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

Secure your Gin project by validating incoming Twilio requests


In this guide, you'll learn how to secure your Gin(link takes you to an external page) application by validating incoming requests to your Twilio webhooks are, in fact, from Twilio.

With a few lines of code, you'll write a custom middleware for our Gin project that uses the Twilio Go SDK(link takes you to an external page)'s validator struct method. You can then apply that middleware to any route which accepts Twilio webhooks to confirm that incoming requests genuinely originated from Twilio.

Let's get started!


Create and apply a custom middleware

create-and-apply-a-custom-middleware page anchor

The Twilio Go SDK includes a RequestValidator struct that you can use to validate incoming requests.

Building this into a middleware is a great way to reuse our validation logic across all routes that accept incoming requests from Twilio.

Custom middleware for Gin projects to validate Twilio requests

custom-middleware-for-gin-projects-to-validate-twilio-requests page anchor

Confirm incoming requests to your Gin routes are genuine with this custom middleware.


_61
package main
_61
_61
import (
_61
"fmt"
_61
"net/http"
_61
"os"
_61
_61
"github.com/gin-gonic/gin"
_61
"github.com/twilio/twilio-go/client"
_61
"github.com/twilio/twilio-go/twiml"
_61
)
_61
_61
// Custom Gin middleware that rejects non-Twilio requests
_61
func requireValidTwilioSignature(validator *client.RequestValidator) gin.HandlerFunc {
_61
return func(context *gin.Context) {
_61
// Your url will vary depending on your environment and how your application is deployed
_61
// Modify this url declaration sample as necessary
_61
url := "https://some-digits.ngrok.io" + context.Request.URL.Path
_61
signatureHeader := context.Request.Header.Get("X-Twilio-Signature")
_61
params := make(map[string]string)
_61
context.Request.ParseForm()
_61
for key, value := range context.Request.PostForm {
_61
params[key] = value[0]
_61
}
_61
_61
// Requests are validated based on the incoming url, parameters,
_61
// and the X-Twilio-Signature header.
_61
// If the request is not valid, return a 403 error
_61
if !validator.Validate(url, params, signatureHeader) {
_61
fmt.Println("Request isn't from Twilio 🚫")
_61
context.AbortWithStatus(http.StatusForbidden)
_61
return
_61
}
_61
// If the request is valid, execute the next middleware (in this case, the route handler)
_61
context.Next()
_61
}
_61
}
_61
_61
func main() {
_61
router := gin.Default()
_61
// Create a RequestValidator instance
_61
requestValidator := client.NewRequestValidator(os.Getenv("TWILIO_AUTH_TOKEN"))
_61
_61
// Apply the requireValidTwilioSignature middleware to your route handler(s), before any
_61
// code that you want to only apply to validated requests
_61
router.POST("/sms", requireValidTwilioSignature(&requestValidator), func(context *gin.Context) {
_61
message := &twiml.MessagingMessage{
_61
Body: "Yay, valid requests!",
_61
}
_61
_61
twimlResult, err := twiml.Messages([]twiml.Element{message})
_61
if err != nil {
_61
context.String(http.StatusInternalServerError, err.Error())
_61
} else {
_61
context.Header("Content-Type", "text/xml")
_61
context.String(http.StatusOK, twimlResult)
_61
}
_61
})
_61
_61
router.Run(":3000")
_61
}

To validate an incoming request genuinely originated from Twilio, you first need to create an instance of the RequestValidator struct using our Twilio auth token. After that you call its Validate method, passing in the request's URL, payload, and the value of the request's X-TWILIO-SIGNATURE header. Remember that the incoming request from a Twilio webhook is of type application/x-www-form-urlencoded, so you will need to create a map and populate it with all of the key/value pairs from the request in order for this to work.

The Validate method will return the boolean true if the request is valid or false if it isn't. Based on this result, this middleware then either calls context.Next() to pass the request onto the next middleware or handler code or returns a 403 HTTP response for inauthentic requests.

To apply this middleware, add it to the list of handlers for any route before any code that requires this validation.

(warning)

Warning

Your request validator may fail locally when you use a ngrok tunnel, or in production if your app is behind a load balancer, proxy, etc. This is because the request URL that your Gin application sees does not match the URL Twilio used to reach your application.

To fix this for local development with ngrok, you may want to manually set the value of the URL based on your tunnel's scheme and host. To fix this in your production app, your middleware will need to reconstruct the request's original URL using deployment environment variables and request headers like X-Forwarded-Proto, if available.


Disable request validation during testing

disable-request-validation-during-testing page anchor

If you write tests for your Gin app, those tests may fail for routes where you use your Twilio request validation middleware. Any requests your test suite sends to those routes will fail the validation check.

To fix this problem, we recommend adding an extra check in your middleware, telling it to only reject invalid requests if your app is running outside of a test environment. Implementation details such as checking for Go Flags vs environment variables will very depending on your development stack, but the principle remains the same.

Disable Twilio webhook middleware when testing Gin routes.

disable-twilio-webhook-middleware-when-testing-gin-routes page anchor

Disable webhook validation during testing.


_75
import (
_75
"flag"
_75
"fmt"
_75
"net/http"
_75
"os"
_75
"testing"
_75
_75
"github.com/gin-gonic/gin"
_75
"github.com/twilio/twilio-go/client"
_75
"github.com/twilio/twilio-go/twiml"
_75
)
_75
_75
// The init function is a great place to prepare application state prior to execution
_75
// In this case, parsing input flags to your app
_75
func init() {
_75
testing.Init()
_75
flag.Parse()
_75
}
_75
_75
// Helper method to determine if your Go code is being run in test mode
_75
func IsTestRun() bool {
_75
return flag.Lookup("test.v").Value.(flag.Getter).Get().(bool)
_75
// Some teams may prefer to use env vars to indicate testing instead, such as
_75
// return os.Getenv("GO_ENV") == "testing"
_75
}
_75
_75
// Custom Gin middleware that rejects non-Twilio requests
_75
func requireValidTwilioSignature(validator *client.RequestValidator) gin.HandlerFunc {
_75
return func(context *gin.Context) {
_75
// Your url will vary depending on your environment and how your application is deployed
_75
// Modify this url declaration sample as necessary
_75
url := "https://some-digits.ngrok.io" + context.Request.URL.Path
_75
signatureHeader := context.Request.Header.Get("X-Twilio-Signature")
_75
params := make(map[string]string)
_75
context.Request.ParseForm()
_75
for key, value := range context.Request.PostForm {
_75
params[key] = value[0]
_75
}
_75
_75
// Requests are validated based on the incoming url, parameters,
_75
// and the X-Twilio-Signature header.
_75
// If the request is not valid AND this isn't being run in a test env, return a 403 error
_75
if !validator.Validate(url, params, signatureHeader) && !IsTestRun() {
_75
fmt.Println("Request isn't from Twilio 🚫")
_75
context.AbortWithStatus(http.StatusForbidden)
_75
return
_75
}
_75
// If the request is valid, execute the next middleware (in this case, the route handler)
_75
context.Next()
_75
}
_75
}
_75
_75
func main() {
_75
router := gin.Default()
_75
// Create a RequestValidator instance
_75
requestValidator := client.NewRequestValidator(os.Getenv("TWILIO_AUTH_TOKEN"))
_75
_75
// Apply the requireValidTwilioSignature middleware to your route handler(s), before any
_75
// code that you want to only apply to validated requests
_75
router.POST("/sms", requireValidTwilioSignature(&requestValidator), func(context *gin.Context) {
_75
message := &twiml.MessagingMessage{
_75
Body: "Yay, valid requests!",
_75
}
_75
_75
twimlResult, err := twiml.Messages([]twiml.Element{message})
_75
if err != nil {
_75
context.String(http.StatusInternalServerError, err.Error())
_75
} else {
_75
context.Header("Content-Type", "text/xml")
_75
context.String(http.StatusOK, twimlResult)
_75
}
_75
})
_75
_75
router.Run(":3000")
_75
}


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.


Rate this page: