Help needed verifying webhooks

Hi,

I’m having some issues trying to verify a webhook. I’m trying to send a payload from a Wordpress instance via woocomerce and verify.

I have never dealt with webhooks or verifying with keys before

I have successfully got the webhook working using skipVerifier, but when I try to authenticate, my requests 401.

The WordPress side
According to the WordPress docs woocomerce will generate a HMAC-SHA256 hash of the request body.

I have a secret in wordpress - I won’t paste it here but let’s call it XXXXXX

When the request comes through the signature is in the header here → ‘x-wc-webhook-signature’: ‘thehash’,

I have followed along with the docs here

Inside my try-catch, my code looks like this

  const options = {
      signatureHeader: 'X-WC-Webhook-Signature',
    } as VerifyOptions

    verifyEvent('sha256Verifier', {
      event,
      secret: process.env.WEBHOOK_SECRET, //  === the secret in wordpress - XXXXXX'
      options,
    })
    verifyEvent('skipVerifier', { event })

I have tried changing the case of the signature and that did not work.

There is a stack overflow post here that talks about verification in a node express app but it’s going over my head.

Help appreicated

Still stuck on this issue. Just been checking out this StackOverflow post node.js - Proper way to verify the signature coming from WooCommerce webhooks - Stack Overflow

And I am wondering if the event.body being returned from Redwood could be the issue?

The write up for Webhooks in the documentation is rather comprehensive.

https://redwoodjs.com/docs/webhook

And you can see from the pattern that all a webhook verification has to do is extract whatever the issuer sends as the mechanism to verify it (usually a mix of a signature in a header and some payload) and then you can be certain the payload comes from a trusted source.

I suggest logging out parts of the event especially the header and body and determine what you need to look at to verify the request.

It could also be that your body is bae64encoded and you’ll need to decode that before anything else.

Yeah, i have done some pretty extensive debugging here with no luck, including making my own custom verifier and I can’t get anything to match e.g

const signRequestBody = (secret: string, body: string) => {
      return `sha256=${createHmac('sha256', secret)
        .update(Buffer.from(event.body, 'utf8'))
        .digest('hex')}`
    }
    console.log(JSON.stringify(event.body))

    const theirSignature = event.headers['x-wc-webhook-signature']
    const ourSignature = signRequestBody(
      process.env.WEBHOOK_SECRET,
      JSON.stringify(event.body)
    )
    if (theirSignature !== ourSignature) {
      console.log('Fail')
    } else {
      console.log('Pass')
    }

The body is not encoded.

I willing to pay for a hand on this if someone wants a brief contracting job

Oh and I agree the docs are comprehensive. Hence me feeling very stuck right now

And the standard SHA256 verifier doesn’t work?

Your custom code looks to do pretty much what it needs to do to verify:

Maybe write some tests using the woocommerce signatures and payload and your secret?

Like seen in redwood/sha256Verifier.test.ts at 19e3ea5df4740fe8a2f77f1639827428abe9d717 · redwoodjs/redwood · GitHub

No the standard one is not working

So I did a bit of testing there.

The the signature in the header that is generated from woocommerce and sent back looks like this

0xgKvD7mDllCK6aHDZuRbPvyF+Ac0OKh1BmlS6QFMkU=

But if I mock a signed webhook I get below…

sha256=7d916807f8bbef2376c8b0224c5d4f7303664cecc0345fbafe8b89a1ac2ea9b6 with the SHA at the front.

Noting the “sha256=…”

There is some conflicting docs out there for WC. I found a reference to the fact that the woocommmerce signature is base64 encoded and if I mock the signed webhook using base64Sha256Verifier the key looks to be in a similar shape (without the sha…)

So I have

  • changed my code to use base64Sha256Verifier
  • successfully tested in jest
  • tried to send the real webhook my way and it’s still returning 401.

Not sure where to go to from here from a troubleshooting point of view

I too had problems using the built-in webhook verifiers in my own application in Redwood.
I ended up basically reverse engineering it all, but my situation is different. The Webhook documentation was indeed helpful in leading the blind horse to the well (me being the blind horse haha) so no blame there.
I am using Vercel Serverless Functions and have a webhook from Shipstation being sent over to a js function in the app’s functions folder.
I couldn’t get it working with the normal verifier for Vercel intially and it seemed to be the verifier not my DB code triggering my Vercel errors.
I then just had the Serverless function just save whatever the request was skipping the verifier in case it wasn’t reading it right or something changed from Vercel’s side without notice.
That went through to my Railway DB so that was working, but it was indeed encoded as body was gibberish.
So I just basically did what the verifiers were doing in block but in js as the source was ts from what I found so some code was slightly different.

I’m curious if it is something with Vercel.
Maybe they modify the body in some way.
When the body is hashed it has to be the exact string that was hashed by the system that generates the HMAC signature.

If there’s even one character (e.g. new line or whitespace) modified then the hashes won’t match.
I experienced this with Slack webhooks and AWS lambda.

Have you tried using NGrok to test and debug locally?

1 Like

Could you write up a bug with an example of the payload and secret and signature that failed verification? Thanks!

This thread on the Netlify forums indicates it may be a hosting limitation How do I get raw body in my Lambda? - Support - Netlify Support Forums

It looks like Vercel does support access to the raw body How do I get the raw body of a Serverless Function? – Vercel Docs

I’ll try out NGrok. Pretty new to Web Dev world still haha.
I had tried going through Docs on the webhook testing via code and that was verbose.

And yes Vercel allowed me access to raw body. I basically passed along the whole Vercel event as a string to figure out what it was (which I’m pretty sure is also AWS Lambda under the hood).
Then the Vercel event has an isBase64Encoded boolean key and I just manually decoded to get a decoded raw event body.
Then in my case had to still do more decoding to get event decoded body parts.
Somewhere in there did seem to add new line breaks into the json which I believe I processed out before passing JSON to parsing.

The Webhooks have an extensive testing section specifically there because testing live web hooks over a tunnel can be cumbersome and error prone.

Please see: Serverless Functions | RedwoodJS Docs for How to test Webhooks.

By mocking the event body and using one of the verifiers provided you can test that these work as expected.

If they do not, please log an issue in GitHub with your tesdtcase are we can fix the verifier if there is a bug.

Often the order in which the payload is stringified or encoded matters and perhaps is what you encountered.

In any case, if the verifier you need is the [Base64Sha1Verifier[(redwood/base64Sha1Verifier.ts at main · redwoodjs/redwood · GitHub) and this did not work in your Vercel case, them please do let us know.

You can find a test case here – and for all verifiers – that you can adapt and then use to report a bug.

Thanks.

1 Like