0

I'm working on a project with Twilio and NodeJS. I set up a twilio.webhook() and as a requirement, I need to catch when there is an error and log that.

For example, if the webhook fails with 403 Forbidden I should be able to catch it and log an error in the console about it.

I cannot find a way online and I read Twilio docs here Twilio doc

I made a validation utility to be able to validate Twilio in several places but cannot log which would be important

const validate = (req, res, next) => {
  log.info('Twilio Access Validation');

    const { NODE_ENV } = process.env;
    const { authToken } = config.get('twilio');

    const shouldValidate = NODE_ENV !== 'development';

    const validateTwilio = twilio.webhook({ validate: true }, 'authToken');

    router.use(validateTwilio);
};

Jakub
  • 2,367
  • 6
  • 31
  • 82

1 Answers1

1

Twilio developer evangelist here.

You can't hook into the validation failure with the webhook function, but you could write your own middleware function that takes inspiration from the webhook function.

Something like this might work:

const { validateExpressRequest } = require("twilio");

function webhook() {
  var opts = {
    validate: true,
  };

  // Process arguments
  var tokenString;
  for (var i = 0, l = arguments.length; i < l; i++) {
    var arg = arguments[i];
    if (typeof arg === 'string') {
      tokenString = arg;
    } else {
      opts = _.extend(opts, arg);
    }
  }

  // set auth token from input or environment variable
  opts.authToken = tokenString ? tokenString : process.env.TWILIO_AUTH_TOKEN;

  // Create middleware function
  return function hook(request, response, next) {
    // Do validation if requested
    if (opts.validate) {
      // Check if the 'X-Twilio-Signature' header exists or not
      if (!request.header('X-Twilio-Signature')) {
        
        // Log error here

        return response.type('text/plain')
          .status(400)
          .send('No signature header error - X-Twilio-Signature header does not exist, maybe this request is not coming from Twilio.');
      }
      // Check for a valid auth token
      if (!opts.authToken) {

        // Log error here

        console.error('[Twilio]: Error - Twilio auth token is required for webhook request validation.');
        response.type('text/plain')
          .status(500)
          .send('Webhook Error - we attempted to validate this request without first configuring our auth token.');
      } else {
        // Check that the request originated from Twilio
        var valid = validateExpressRequest(request, opts.authToken, {
          url: opts.url,
          host: opts.host,
          protocol: opts.protocol,
        });

        if (valid) {
          next();
        } else {
 
          // Log error here

          return response
            .type('text/plain')
            .status(403)
            .send('Twilio Request Validation Failed.');
        }
      }
    } else {
      next();
    }
  };
}
philnash
  • 70,667
  • 10
  • 60
  • 88
  • 1
    This is a good idea though, I might propose a pull request to the library that allows you to pass a function to the `webhook` function via the options that gets called when the webhook fails for some reason. – philnash Feb 26 '22 at 03:28
  • Cool that nice. So instead to use the twilio webhook directly will use similar to the one you shared it here. That nice and I can then log the things as I need. – Jakub Feb 26 '22 at 11:03
  • 1
    Exactly. That is actually the code from the `webhook` function in the Twilio library, you could write your own similar one, which could be simpler (that handles a bunch of different ways to use it) but as long as you call `validateExpressRequest` and handle the result, you can log errors or pass the request on. – philnash Feb 26 '22 at 12:30
  • Perfect I'll be giving a try but really nice – Jakub Feb 26 '22 at 16:10
  • I'm having an issue here var valid = validateExpressRequest(request, opts.authToken, { url: opts.url, host: opts.host, protocol: opts.protocol, }); Is always forbidden and have no clue why. Do I have to call the hook inside every single route endpoint? I did like router.use(webhook()) is that ok? – Jakub Feb 27 '22 at 20:02
  • 1
    You only need to run that middleware on endpoints that are called by Twilio. Do not add the middleware for your entire application. If your Twilio webhook endpoint is `router.post("/messages", (req, res) => {`, for example, then do `router.post("/messages", webhook(), (req, res) => {`. – philnash Feb 27 '22 at 22:29