Skip to content

Instantly share code, notes, and snippets.

@coolaj86
Last active October 23, 2018 20:45
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save coolaj86/81a3b61353d2f0a2552c to your computer and use it in GitHub Desktop.
Save coolaj86/81a3b61353d2f0a2552c to your computer and use it in GitHub Desktop.

Here's how you validate a mailgun webhook in Node.js (as per the mailgun docs for securing webhooks)

'use strict';

var scmp = require('scmp')
  , crypto = require('crypto')
  . mailgunPrivateKey = 'XXXXXXXXXXXXX'
  , mailgunTokens = {}
  , mailgunExpirey = 15 * 60 * 1000
  , mailgunHashType = 'sha256'
  , mailgunSignatureEncoding = 'hex'
  ;

function validateMailgun(apiKey, timestamp, token, signature) {
  var actual
    , adjustedTimestamp = parseInt(timestamp, 10) * 1000
    , fresh = (Math.abs(Date.now() - adjustedTimestamp) < mailgunExpirey)
    ;

  if (!fresh) {
    console.error('[mailgun] Stale Timestamp: this may be an attack');
    console.error('[mailgun] However, this is most likely your fault\n');
    console.error('[mailgun] run `ntpdate ntp.ubuntu.com` and check your system clock\n');
    console.error('[mailgun] System Time: ' + new Date().toString());
    console.error('[mailgun] Mailgun Time: ' + new Date(adjustedTimestamp).toString(), timestamp);
    console.error('[mailgun] Delta: ' + (Date.now() - adjustedTimestamp));
    return false;
  }

  if (mailgunTokens[token]) {
    console.error('[mailgun] Replay Attack');
    return false;
  }
  
  mailgunTokens[token] = true;

  setTimeout(function () {
    delete mailgunTokens[token];
  }, mailgunExpirey + (5 * 1000));

  return scmp(
    signature
  , crypto.createHmac(mailgunHashType, apiKey)
    .update(new Buffer(timestamp + token, 'utf-8'))
    .digest(mailgunSignatureEncoding)
  );
}

function router(app) {
  app.post('/webhooks/mailgun/*', function (req, res, next) {
    var body = req.body
      ;

    if (!validateMailgun(mailgunPrivateKey, body.timestamp, body.token, body.signature)) {
      console.error('Request came, but not from Mailgun');
      res.send({ error: { message: 'Invalid signature. Are you even Mailgun?' } });
      return;
    }

    next();
  });

  app.post('/webhooks/mailgun/catchall', function (req, res) {
    // actually handle request here
  });
}
@wakatanka
Copy link

Hi, thanks for sharing, i'm trying your script and got this error:
Error: Both scmp args must be Buffers

can you help me?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment