Webhook Secret issue

Hoping someone can tell me what I’m doing wrong here.

I’ve set up a Discourse Webhook and it works as expected without a Secret. I then write a string into the Secret, and then copy that string into the receiving Webhook as a Header, like so:

X-Discourse-Event-Signature: secret_here

This produces the following error in the Body when the sending webhook is sent:

{"code":"rest_forbidden","message":"Sorry, you are not allowed to do that.","data":{"status":401}}

I expect that I’m just not doing it right, so please enlighten me!

I’m aware that the secret is encrypted using sha256, so the Header that is sent is:

X-Discourse-Event-Signature: sha256=XXX

But I’m not sure what I should be doing with this information in the receiving Webhook Security Headers.

I’m not sure if this is just the way you are writing the post but the actual header would be

X-Discourse-Event-Signature: XXX

You can (optionally) use this at the receiving end to validate that the webhook was actually sent by Discourse. If you don’t do the verification then a malicious party could potentially forge webhook events.

What does this mean?

1 Like

So you are suggesting that the Signature in the receiving webhook should be the encrypted secret, NOT the unencrypted chosen secret?

I am indeed writing the Header in the receiving application like so:
X-Discourse-Event-Signature: XXX

I keep getting a 401 forbidden error though when I am using a Secret.

Where is that screenshot from?
Maybe you can elaborate a bit more about your setup.

My Wordpress website. I’m using a Wordpress Plugin to receive Webhooks. If I do not add a Security Header (Secret), the Webhook works fine. It’s only when I add the secret that the problem occurs.

Here’s the plugin: Incoming Webhook Triggers - Uncanny Automator

Thanks for your time Richard!

1 Like

I’ve since narrowed down the problem:

Custom security headers may be included in the incoming webhook trigger, but one important note is how WordPress can change dashes to underscores. As an example, trying to use “x-api-key” may require using “x_api_key” instead.

I do however have a related question though @RGJ . Let’s say my secret is “1234”. In the receiving webhook, should the X-Discourse-Event-Signature value be:

  1. 1234;
  2. sha256=encrypted_value_here;
  3. encrypted_value_here


It’s #2, sha256=XXX.

It looks like the Wordpress plugin you’re using can only compare against a static header value, not a dynamic signature. This is something Discourse specific.

1 Like

Have a look at how the WP Discourse plugin handles the secret:


I’ve since discovered that the sha value changes every time the webhook is sent, so putting the sha value in the receiving webhook would not work. I would think it would have to be example #1, and as I think you’re suggesting (re-static header), the plugin would actually need to decode the hash before generating the header response?

In conclusion, I don’t think I can use the hashed X-Discourse-Event-Signature in this plugin, because it will only accept static values.

Is it unsafe to use a webhook without a Secret? I would be sending User Events from my Discourse to my main website, using a non-obvious url such as main-site.com/wp-json/fol/fil/532563-5312534 . I can also add static headers that must match on the receiving hook.

Could you give me an example of an application that could handle a dynamically changing hmac ?

The value is calculated against the webhook’s payload, so it’s going to be different for every request.

That’s an interesting question. The only application I know of is the WP Discourse plugin, but it was configured specifically to handle Discourse webhooks. If Discourse’s implementation is preventing people from validating webhooks with secrets, maybe that needs to be looked into.

I’ll leave that for others to answer.

If you are concerned about not validating the webhook, maybe the Incoming Webhook Triggers plugin has an action hook in its webhook receiving code that’s fired before the webhook’s data is processed. If it does, it should be possible to write a function that hooks into it, checks to see if the data is for a Discourse webhook, then validates the secret with something like the code I linked to in my previous post.


I was curious, so I looked into this a bit. I’m unsure about applications that are configured to handle receiving a dynamically changing HMAC signature, but authenticating webhook requests with a HMAC signature that’s calculated against the payload is a fairly standard practice for applications that are sending webhooks. For example, GitHub, Stripe, and Shopify all use this method.

There are also quite a few popular applications that use the secret token method of authenticating webhooks. For example (as of 2021) Mailchimp, Trello, Slack, and Discord all use this approach. It seems that Slack is still using this method: FAQ | Slack. I can see how that would make things easier for the application that receives the webhook.

I guess there’s a trade off between security and convenience in terms of deciding which method the sending application will use. My concern with Discourse’s HMAC signature method is that it might result in people configuring webhooks without secret keys in order to get around the difficulty of handling the changing HMAC signatures. For example, there are a few references on Meta for sending Discourse webhooks to Zapier without any mention of how to validate the signature on Zapier. (It should be technically possible to validate the signatures on Zapier though. I’m not setup to test that at the moment.)


I absolutely agree with this. Perhaps this is something that the developers can explore - an easier and more compatible method of securing webhook data.

Also I’m not so sure that Zapier is even capable of validating the signature.

The webhook secret feature is implemented according to this:

It appears the software you want does not support this standard security communication feature, but you can still utilize Discourse webhooks without it.

There are no plans to add a fixed header with a shared secret, as the security value of that is questionable. However, if you need this, it should be doable in a plugin.