Webhook Signature validation fails

Hi,

I am developing an application to handle patron web hooks and process event based on new pledges, updates and deletes. I created a webhook and when I try to test it, it always fails.

My application is on Node, Express 4. I followed your documentation to on signature validation

Blockquote where the message signature is the HEX digest of the message body HMAC signed (with MD5) using your webhook’s secret viewable on the webhooks page. You can use this to verify us as the sender of the message.

Followed an example from here

and below is my code

app.post(‘/XXXX’, (req, res, next) => {
let webhookSecret = ‘XXXXXXX’;
console.log(req.header(‘X-Patreon-Signature’), req.headers);
console.dir(req.body);
let data = JSON.stringify(req.body);

const hash = crypto.createHmac(‘md5’, webhookSecret).update(data).digest(‘hex’);
console.log(hash);

if (req.header(‘X-Patreon-Signature’) !== hash) {
res.status(403).json({
error: ‘Invalid signature’
})
}

if (req.header(‘X-Patreon-Event’) === ‘pledges:create’) {
console.log(“new pledge created at patreon”);
// Todo: create new subscription
return res.status(200).json({message: ‘pledge created’});
}
if (req.header(‘X-Patreon-Event’) === ‘pledges:update’) {
console.log(“pledge updated at patreon”);
// Todo: update subscription
return res.status(200).json({message: 'pledge updated '});
}
if (req.header(‘X-Patreon-Event’) === ‘pledges:delete’) {
console.log(“pledge deleted at patreon”);
//Todo: Delete subscription;
return res.status(200).json({message: 'pledge deleted '});
}

});

When I test the web hook from application settings, its always giving me signature failed response.

If I don’t user JSON.stringify it throws back a 500 error saying data must be string or buffer.

Hi reachsampath,

I validated the code using a test webhook, and it seemed to work perfectly. You are correct that you need to call JSON.stringify. Can you clarify what you mean by testing it from “application settings”?

Thanks,
Jeffrey

I mean by sending the test webhook under webhook registration page in patreon.

Can you share your code if possible? Just to compare and cross check.

Are you using the body-parser for Express 4, configured to parse JSON POST requests? i.e:

const bodyParser = require('body-parser')
app.use(bodyParser.json());

Without that Express won’t parse the body of the POST request – which will result in the signature validation failing. The code you’ve provided works for me, too, when body-parser is included with the json parser chosen. you can try it here:

const express = require('express')
const app = express()
const crypto = require('crypto')
const bodyParser = require('body-parser')

app.use(bodyParser.text({type: '*/*'}));

app.post("/webhook", (request, response) => {
  const webhookSecret = 'secret';
  
  let hash = crypto.createHmac('md5', webhookSecret).update(request.body).digest('hex');
  let success = (request.header('x-patreon-signature') === hash);
  
  console.log('Signature received: ' + request.header('x-patreon-signature'));
  console.log('Signature generated: ' + hash);
  console.log('Signature validation status: ' + success);
    
  return response.status(success ? 200 : 400).json({result: success});
})

app.listen(process.env.PORT)

Edit: this code was updated on May 31st, 2018, to fix a bug: parsing the input as JSON and then turning it back into a string can cause some encoding issues which will cause signature verification to fail sometimes.

Hey, I have a similiar problem.
Using this code the hash check is always false:

let webhookSecret = 'SECRET_FOR_WEBHOOK';
let data = JSON.stringify(req.body);

const hash = crypto.createHmac('md5', webhookSecret).update(data).digest('hex');

let msg = 'Webhook received, signature validation status: ' + (req.get('X-Patreon-Signature') === hash);
console.log(msg, req.get('X-Patreon-Signature'), hash, req.get('X-Patreon-Event'));

Express.js 4.16.3
body-parser 1.18.2

And yes I do app.use(bodyParser.json());
Data seems also fine if I look at it, but the check fails all the time. Any hints? Is this supposed to fail via the Send test webhook? Would appreciate it.

Edited on May 31st 2018: the issue experienced in this thread, and discussed below, was due to encoding. Please see the note in post #4.


The example code should (and does) verify successfully when using the example webhooks provided by Patreon, I’ve just tried it now and confirmed that for each of the events (pledges:create, pledges:update, pledges:delete).

The request body and webhook secret must be character-for-character accurate, so any trailing spaces or newlines will cause verification to fail. Additionally, each webhook secret is unique to the webhook URL, ensure that you’re using the correct webhook secret – and make sure it’s the webhook secret not your client secret.

You can follow these steps to use the example code with glitch:

  1. Visit the example project
  2. At the top right click “Remix This”
  3. Click “Share” then copy the “Share your App” URL
  4. Visit the Patreon Platform “Register Wehooks” page
  5. Paste your app URL and append /webhook to the end, e.g: https://patreon-signature.glitch.me/webhook
  6. Click the coral coloured + button
  7. Copy the “Secret” displayed
  8. Return to Glitch and set the webhookSecret value on line 9 of server.js to the Secret you just copied
  9. Click on the “Logs” button on the left of the Glitch panel to open the log viewer
  10. Return to the Patreon webhooks page and click “Send Test” for one of the example events
  11. Watch the Glitch log, and observe the output

At this point you should see the following:

Webhook received, signature validation status: true
{…request json…}

That allows you to confirm that the example code works, and that the issue is somewhere in your application. I’d expect it’s an issue with your webhook secret not being correct, otherwise it’s probably to do with how you’re obtaining the request body.

Scratch all that. I think this is the problem: X-Patreon-Signature is a header, not a parameter. You’re using get which (I guess, I’m not an express developer) means parameter not header, so you need to do req.header instead.

Edit: nevermind, req.get is the right method for fetching a header value in Express 4, what an odd method name choice: Express 4.x - API Reference. So, my original suggestions apply.

Thanks for the fast reply! I did the glitch example and even there: it shows false for every event. I can even provide screenshots if you would like. I checked the secret multiple times. And again,. the data looked fine there, only the hash check is false. Should I post you the example maybe?

req.get() and req.header() are pretty much the same so that’s probably not it. :frowning:

That’s really strange, perhaps there’s an issue with the secrets that Patreon is providing you? That’s all that really makes sense to me right now if the code is working for me with my webhook secrets, but isn’t working for you with yours. Unfortunately I’m just another user of the platform so I wouldn’t be able to debug any issues like that for you.

Feel free to post the full example, I’ll take a look. My best guess for debugging would be to simply print out all of the values involved and compare them to what is expected because I expect the issue is with the values somewhere. If you don’t mind using PHP to test, my Patreon PHP library has unit tests for signature verification so you could use that to verify whether the issue is with the values or the code I guess.

Here is the example: https://glitch.com/edit/#!/opposite-prepared

Here is the screenshot of the webhook secret etc. Since I dont plan to use this anywhere I think its fine to post it here.

That’s really weird. Thanks again for your replies. Is there a place to contact the devs directly? Or is this the correct place?

  1. I clicked on your Glitch example
  2. I clicked Remix
  3. I visited the webhook management page and created a new webhook with the Glitch URL
  4. I returned to Glitch and added the new secret
  5. I clicked “Send Test” via Patreon webhook management page
  6. I saw this log:

Webhook received, signature validation status: true

I think we’ve ruled out the code as the issue, although I’m not primarily a Javascript developer so I could be missing something.

This forum is monitored by the Patreon platform team. As far as I know they are here during the week, so they’ll probably be along in the morning to provide input. Sorry I couldn’t help further!

Thank you. Well, there is probably something wrong with my secrets which seems super weird honestly. Let’s hope some dev stops by here. oauth is working decently for me in combination with auth0. Only the webhook is behaving weird. I mean it works, data-wise, but It’s probably not a good idea to not check the signature.

Hi Xzandro, in fact you must not do JSON.stringify() or use the req.body at all for the hmac.

The string you want to test is the raw body and using JSON.stringify() on the parsed JSON body is not gonna reproduce the same exact string as the original body. A single change byte and the signatures are no going to match.

You need to apply the hmac to req.rawBody. If req.rawBody is not present or not set, you might need to use body-parser and/or raw-body.

This is still a problem for me. I have no idea how to accurately get the rawBody from express.

I’m using the body parser: app.use(bodyParser.json())


router.post('/', (req, res) => {
  const webhookSecret = 'SECRET'

  console.info('text', req.body)

  let hash = crypto
    .createHmac('md5', webhookSecret)
    .update(req.body)
    .digest('hex')
  let success = req.header('x-patreon-signature') === hash

  console.log('Signature received: ' + req.header('x-patreon-signature'))
  console.log('Signature generated: ' + hash)
  console.log('Signature validation status: ' + success)
})

However req.body inside the update doesn’t work. As noted above JSON.stringify() while correcting the “Data argument must box one of type string…” error, does NOT match the expected signature.

I don’t want to change how my server handles all requests either, this is the only endpoint which requires the raw body.

SOLUTION (and cause)

So… turns out that because I’m using bodyParser it causes all manner of issues with this type of endpoint being able to calculate properly… It has taken me hours but I found a post by Scott Dixon which has the solution.

Basically the solution is… for the route that needs it only, add an app.use() before the app.use(bodyParer.json()) line.

...
app.use('/api/v1/patreon', bodyParser.raw({ type: 'application/json' }))
app.use(bodyParser.json())
...

Then you can properly use req.body to create the validation as above (including again to be thorough)

router.post('/', (req, res) => {
  const webhookSecret = 'SECRET'

  console.info('text', req.body)

  let hash = crypto
    .createHmac('md5', webhookSecret)
    .update(req.body)
    .digest('hex')
  let success = req.header('x-patreon-signature') === hash

  console.log('Signature received: ' + req.header('x-patreon-signature'))
  console.log('Signature generated: ' + hash)
  console.log('Signature validation status: ' + success)
})
1 Like