1

I'm attempting to verify a webhook signature from Patreon using Node.js. Here is my code:

const crypto = require("crypto");

...

function validateJsonWebhook(request) {
  const secret = SECRET_KEY_GIVEN_BY_PATREON;

  const hash = crypto.createHmac("md5", secret)
      .update(JSON.stringify(request.body))
      .digest("hex");

  if (request.header("x-patreon-signature") === hash) {
    return true;
  } else {
    return false;
  }
}

Patreon webhooks use MD5 - see https://docs.patreon.com/#webhooks.

I've verified the secret key multiple times so I know that's not the issue.

Both "request.header("x-patreon-signature")" and "hash" are returning the correct format (i.e. they're both a 32 digit letter-number combination) but they're just not matching.

Any idea about what's going on?

Jason
  • 41
  • 4
  • I would verify that `JSON.stringify(request.body)` is what you expect it to be – gaiazov Jun 29 '21 at 03:20
  • 1
    Hi @gaiazov, thanks for the reply. At first glance they look the same. However, it's a lot of data and "request.body" has chunks that are "attributes: [Object]" and those Objects are written out with "JSON.stringify(request.body)". Could that be the issue? – Jason Jun 29 '21 at 03:59
  • Glad you figured it out! – gaiazov Jun 30 '21 at 19:29

1 Answers1

3

So @gaiazov's comment led me to do some Googling which led me to the first two comments on Stripe verify web-hook signature HMAC sha254 HAPI.js by Karl Reid which led me to https://github.com/stripe/stripe-node/issues/331#issuecomment-314917167.

For anyone who finds this in the future: DON'T use JSON.stringify(request.body) - use request.rawBody instead, as the signature is calculated based on the raw JSON. I feel like this should be emphasized in Patreon's documentation, as all the examples I found used the code I originally posted. My new, working code is as follows (I cleaned up the "if (request.header("x-patreon-signature") === hash)" part at the end):

const crypto = require("crypto");

...

function validateJsonWebhook(request) {
  // Secret key given by Patreon.
  const secret = patreonSecret;

  const hash = crypto.createHmac("md5", secret)
      .update(request.rawBody)
      .digest("hex");

  return (request.header("x-patreon-signature") === hash);
}
Jason
  • 41
  • 4