Complete Guide to Webhook Security

A webhook is a fast, simple, and efficient HTTP communication mechanism for integrating remote applications. This simplicity and efficiency, while very exciting, involves a very critical trade-off: security. Webhooks were not built to be secure out-of-the-box, and the entire security burden falls on the developer. One argument for this is that the amount of security required for a webhook is mostly relative to its use case.

While cases such as sending application log data to visualization systems may not really care about encryption of data in transit, other cases involving the communication of financial data will.

This “build as much as you care” security model allows any application working with webhooks (webhook producers and consumers) to adopt the security controls that best suit the sensitivity of the data that is being communicated via webhooks.

In this article, we take a look at the security vulnerabilities of webhooks. We will then explore the different strategies for mitigating the security flaws.

Webhook security concerns

In this section, we start by discussing the security flaws in the webhook design. This will help you better understand the problems you’re trying to solve and the “why” behind any of the security strategies you choose to adopt. I cover the main things to keep in mind in this article, but for more information on webhook vulnerabilities you can keep reading here.

No verification for the source and destination of a webhook

The webhook communication mechanism does not have a native way of identifying the source or destination of a webhook. This means that a webhook producer has no way to verify that it is sending its webhook to the right destination, and the webhook consumer cannot verify that it is receiving its webhook from the expected source.

This vulnerability allows anyone to act as a webhook producer or receiver. Threat actors can easily take advantage of this flaw to act as webhook producers and send malicious webhooks to a webhook consumer in order to compromise the receiving application.

Human error can also cause developers to enter the wrong webhook URL when setting up a webhook. Because a webhook does not verify its destination, webhook requests will be fired to the wrong endpoint which causes data to be lost or compromised if the error is not quickly detected.

Webhook data is exposed in transit

Webhooks use the HTTP protocol by default. The HTTP protocol communicates data in plain text, which means that data is transmitted openly from one application to another. Webhooks use the protocol as-is with no extra layer of protection added for webhook data transmission.

This vulnerability allows anyone with visibility into the network traffic involving webhooks to easily view and extract information from the webhooks. The information that can be compromised this way is not only limited to the webhook payload, sensitive headers containing authentication data can also be read and even tampered with.

Webhook data is not verified

Imagine that you ordered a table microphone from Amazon. Amazon charges you the right price, gets your address right, and delivers your package within the projected delivery period. All is good right? The delivery process was smooth and successful, wasn’t it? You then open your package only to realize that it contains a set of tea cups. Even though the package delivery process was hitch-free, you got the wrong item and that automatically ruins the whole shopping experience for you.

This is similar to the flaw in verifying the data contained in a webhook. Another major issue, unlike the Amazon example where the customer knew what to expect, is that webhook consumers have no idea which data they are to expect and yet do not have a way to verify that it is the right data.

Accepting incorrect data into your system due to a verification flaw can have really damaging effects on the integrity of your application.

No restriction on where a webhook can come from

Another security vulnerability in the design of webhooks is that while producers can specifically target webhook consumers, there is no way for consumers to restrict the sources they are receiving webhooks from.

This allows just any application to send webhooks to a webhook consumer's webhook URL, similar to the effect of the lack of verification for webhooks on both ends of the transaction.

While IP whitelisting can sometimes be implemented to mitigate this flaw by restricting the producers that can send webhooks to a certain client, communication through proxies or queue servers does not use the producer’s IP address. Also, IP spoofing, a process used by attackers to impersonate a host by mimicking its IP address, is not so difficult to achieve these days.

A webhook request can be duplicated

Webhooks are often sent with data that can cause a change in the receiving application’s database or trigger an action like sending an email. Some of these actions, if repeated, can lead to an inconsistent state in the receiving application. This can compromise the integrity of the database or cause an action to be repeatedly annoying (for example sending multiple of the same notification to a user).

A webhook, on the consumer side of things, has no way of detecting if it is being repeated. Oftentimes, producers have a way of getting feedback that a webhook failed on the receiving end and some producers will retry the webhook automatically in this case. However, the failure message that the consumer sends back may have occurred after a change in the application state has already been caused by the webhook. Thus, an automatic or manual retry of the webhook request will duplicate the effect.

Also, attackers that get hold of a webhook request can replay it several times, causing the webhook consumer to repeat the intended change in its state multiple times. For example, an account credit transaction that adds $5 to a customer’s wallet in a financial application can be replayed multiple times to add up to $200.

How do you secure a webhook?

Now that we have looked at the vulnerabilities in the webhook communication mechanism, how do we secure it?

Should webhooks have authentication? How do we make the data more secure so that it is not compromised if hijacked in transit? How can webhook sources be restricted?

A security checklist

When it comes to securing webhooks, there are certain best practices that help you overcome a good number of the vulnerabilities that we have described above.

Fortunately, we have an entire article dedicated to detailing these security checks that need to be put in place for adequate webhook security. However, I will be summarizing the strategies below and you can refer to the security checklist article for further details.

  • Verify the source and consumer: Using an authentication framework like token authentication and combining that with Mutual TLS can be used to verify the source and destination of webhooks respectively.
  • Encrypt all data: TLS helps add an extra layer to the TCP/IP stack that encrypts all HTTP data before they are sent for transmission. Thus, it is recommended that webhook URLs use the secure HTTP protocol, HTTPS. This ensures that all data in transit is encrypted and rendered useless to anyone that hijacks it for malicious purposes.
  • Verify the message: Message verification can be done through the use of a signature verification system. Cryptographic authentication strategies like the Hash-based Message Authentication Code (HMAC) are used to create a hash value of the payload on both the server and client sides. The value on both sides is then compared for a match to verify the accuracy of the data.
  • Use a timestamped “nonce” to prevent replay attacks: This strategy involves attaching a message expiration timestamp to the webhook before calculating the signature. Once the time added expires, the message will no longer be valid and anyone that replays the webhook will be denied. The expiration time for the timestamp can be set to your preference. One of the webhook providers that enforces this strategy is Stripe.

With these in place, you can prevent almost all the vulnerabilities that we discussed in the previous section.

Webhook authentication

Webhook authentication helps confirm that we are receiving our webhooks from the expected source and that they are being sent to the actual destination. Through authentication, we can also validate the message being sent in the webhook to ensure that the message has not been tampered with.

Some of the most common webhook authentication methods used today are:

  • Basic authentication
  • Token authentication
  • Signature verification

As we will see in the next article in this series entitled “Webhook Authentication Strategies,” webhook authentication takes care of a decent amount of the security vulnerabilities described above.

One way to check if your provider requires authentication, the type of authentication and the authentication behaviour is to test a single webhook on your endpoint in your development environment. Setting up this type of test can be challenging as it requires a publicly available HTTPs endpoint. Luckily, you can use our online webhook test tool to easily test webhooks by routing them to your development environment.

Example: Implementing signature verification in Node JS

One of the strongest and most used forms of webhook authentication is signature verification. Signature verification is powerful in its ability to not only authenticate the webhook producer and consumer but also validate the webhook payload.

Signature verification achieves this by having the webhook provider and consumer calculate a unique cryptographic signature and then compares the two. If there is a match, the webhook is valid, if not, the webhook is rejected.

If you want to dive deeper into how signature verification is implemented and see more code samples in different languages, check out our article on how to implement signature verification.

Here is an implementation of signature verification for a webhook in a Node.js express server:

const express = require("express");
const routes = require("./routes");
const bodyParser = require("body-parser");
const crypto = require("crypto");

// App
const app = express();

const sigHeaderName = "X-Signature-SHA256";
const sigHashAlg = "sha256";
const sigPrefix = ""; //set this to your signature prefix if any
const secret = "my_webhook_api_secret";

//Get the raw body
app.use(
  bodyParser.json({
    verify: (req, res, buf, encoding) => {
      if (buf && buf.length) {
        req.rawBody = buf.toString(encoding || "utf8");
      }
    },
  }),
);

//Validate payload
function validatePayload(req, res, next) {
  if (req.get(sigHeaderName)) {
    //Extract Signature header
    const sig = Buffer.from(req.get(sigHeaderName) || "", "utf8");

    //Calculate HMAC
    const hmac = crypto.createHmac(sigHashAlg, secret);
    const digest = Buffer.from(
      sigPrefix + hmac.update(req.rawBody).digest("hex"),
      "utf8",
    );

    //Compare HMACs
    if (sig.length !== digest.length || !crypto.timingSafeEqual(digest, sig)) {
      return res.status(401).send({
        message: `Request body digest (${digest}) did not match ${sigHeaderName} (${sig})`,
      });
    }
  }

  return next();
}
app.use(validatePayload);

app.use("/", routes);

const port = process.env.PORT || "1337";
app.set("port", port);

app.listen(port, () => console.log(`Server running on localhost:${port}`));

In the code above, the webhook consumer calculates the webhook signature using a secret key, the webhook payload, and the SHA-256 hashing function. The calculated signature is then compared with the one sent in the webhook signature header X-Signature-SHA256 to confirm a match.

Conclusion

Webhook security is easy to overlook and this can lead to very damaging effects on the integrity of the applications involved. However, following best practices that ensure that you tie all loose ends where webhooks can be vulnerable to attacks helps you avoid the consequences of a breach in the data pipeline.

In this article, we have discussed the security flaws in the architecture of a webhook and looked at how these flaws can be removed.

Happy coding!