How to Secure Webhook Endpoints with HMAC

How to Secure Webhook Endpoints with HMAC

Webhooks are ubiquitous in SaaS integrations, and there's a good reason for that. They are a simple and speedy way to transfer data via HTTP callbacks between systems based on changes to data in those systems. My company helps SaaS teams build native integrations to their customers' other apps, and many of our customers use webhooks for their integrations.

Over the last few weeks, we've helped several of our customers who needed to ensure that their webhook endpoints were secure. In this post, we'll describe the approach we recommend. But first, let's lay some groundwork.

How do webhooks work?

In short, the source app has a webhook, and the destination app has a webhook endpoint; based on some event occurring in the source app, the webhook sends an HTTP request to the webhook endpoint.

Here's a simple example of an HTTP request body (or payload):

{
  "event": "WAREHOUSE_UPDATE",
  "updates": [
    {
      "item": "gadgets",
      "action": "add",
      "quantity": 20
    },
    {
      "item": "widgets",
      "action": "remove",
      "quantity": 10
    }
  ]
}

But how do you ensure that the destination app receives valid data from the source app and not bogus data from a bad actor who has spoofed the webhook?

The short answer is that you need to set up the webhook to provide the endpoint with the HTTP request and a unique key that the endpoint can use to verify the data. But, before we get into the details, let's briefly cover hashing.

What is hashing?

At its simplest, hashing is the process of converting a value (or key) into another value. Even if you've not worked extensively with hashing before, you are probably aware of MD5, SHA-256, or RipeMD-128. Each of these is the name of a hashing algorithm (aka cryptographic hash function).

Let's see what each algorithm does to a classic string:

  • MD5 hashes Hello World! to ed076287532e86365e841e92bfc50d8c
  • SHA-256 hashes Hello World! to 7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069
  • RipeMD-128 hashes Hello World! to 24e23e5c25bc06c8aa43b696c1e11669

The important part is that an algorithm hashes a value the same way every time. If we don't change our string ('Hello World!'), the resulting hash value doesn't change either.

However, if anything in the string changes, the hash will also change. For example, let's lower-case the 'H' so we have 'hello World!' and see what that does:

  • MD5 hashes hello World! to 41d0c351efedf7fdb9a5dc8a1ed4b4e3
  • SHA-256 hashes hello World! to e4ad0102dc2523443333d808b91a989b71c2439d7362aca6538d49f76baaa5ca
  • RipeMD-128 hashes hello World! to b5cf338f17d6796ba0312e0d78c70831

A slight change, but the resulting differences are evident.

Though hashing doesn't allow us to completely solve our original problem (someone sending bogus data to a webhook endpoint), it does lead us directly to HMAC.

What is HMAC?

HMAC, or hashed message authentication code, is an authentication method that uses not one but two keys. The first key is the HTTP request body, while the second one is a secret cryptographic key. When you implement HMAC for your webhook, you'll be using both these keys plus an algorithm such as MD5, SHA-256, or RipeMD-128 to ensure the HTTP request that shows up at your webhook endpoint is legit.

How does HMAC work?

Before the source app sends an HTTP request via the webhook, it hashes the payload (request body) with HMAC using the secret key. The resulting hash is then bundled into the HTTP request as a header, and the entire request (header and body) is sent to the webhook endpoint.

Upon receiving the HTTP request, the destination app hashes the body with the secret key and then compares the result to the hash provided in the header. If the values match, the destination app knows the data is legit and processes it. If the values do not match, the destination app rejects the data and executes whatever code was written for that scenario — perhaps creating a log entry or sending a notification.

If someone tries to spoof the payload, they won't be able to generate a valid hash since they don't have the secret key. Door closed.

Let's imagine that you have an e-commerce platform connected to your app. Your app regularly sends payloads to the platform's webhook endpoint to create orders and issue refunds. Using HMAC ensures that you won't have random (or not so random) people sending bogus orders or refunds to the e-commerce platform.

But, you say, couldn't someone capture an HTTP request and reverse engineer the hash in the header to figure out the secret? Short answer: no. Hashing is a one-way function. To crack a hash with a sufficiently complex secret, we would need more computing power and time than any of us has available.

Apps that rely on HMAC for webhook endpoints

Some well-known apps currently use HMAC to secure their webhook endpoints:

  • Slack: Provides a Signing Secret when you create a Slack app. When it sends a webhook payload, it hashes both the payload and webhook's timestamp with that secret using SHA256. The webhook request includes the resulting hash as a header called X-Slack-Signature.
  • Dropbox: Generates an App Secret when you create a Dropbox app and uses that secret to generate webhook HMAC hashes and authenticate users with OAuth 2.0. It hashes webhook payloads using SHA256 and sends the hash as a header called X-Dropbox-Signature.
  • Shopify: Creates an API Secret Key and hashes its payloads with that key and SHA256. It sends the hash as a header called X-Shopify-Hmac-SHA256.

HMAC has broad language support

You can use just about any modern language to compute HMAC hashes. Here are some links to popular languages with HMAC capabilities:

Example code for HMAC

Finally, what would all of this be without code? Here is an example of how this might be set up in NodeJS using the built-in crypto module:

const crypto = require("crypto");

const SECRET_KEY = "secret-FA782CF7-060E-484E-B3DC-055CF2C9ED99";

const payload = JSON.stringify({
  event: "REFUND_REQUEST",
  user: "realcustomer@notabaddie.com",
  amount: "50.25",
});

const hash = crypto
  .createHmac("sha256", SECRET_KEY)
  .update(payload, "utf-8")
  .digest("hex");

console.log(hash); // Prints d12f95e3f98240cff00b2743160455fdf70cb8d431db2981a9af8414fc4ad5f8

The corresponding HTTP request using HMAC might look like this:

curl https://my.webhook.endpoint.com/callback \
  --request POST \
  --header "x-hmac-hash: d12f95e3f98240cff00b2743160455fdf70cb8d431db2981a9af8414fc4ad5f8" \
  --data '{"event":"REFUND_REQUEST","user":"realcustomer@notabaddie.com","amount":"50.25"}'

Even if a bad actor intercepted your HTTP request, they couldn't issue a refund request of a million dollars to their own email address, since they couldn't sign the request properly without the secret key.

Wondering how you're going to build all those integrations?

Wondering how you're going to build all those integrations?

Download our Build vs Buy guide for Native Integrations to explore your options.

Get my Copy

Conclusion

Using HMAC does not require that you learn a new language or gain an advanced understanding of encryption, but it does allow you to protect the integrity of the data you are transferring via webhooks.

Do you spend a lot of time building integrations to your customers' other apps? Connect with us to learn how an embedded iPaaS can expedite your integration delivery.


About Prismatic

Prismatic, the world's most versatile embedded iPaaS, helps B2B SaaS teams launch powerful product integrations up to 8x faster. The industry-leading platform provides a comprehensive toolset so teams can build integrations fast, deploy and support them at scale, and embed them in their products so customers can self-serve. It encompasses both low-code and code-native building experiences, pre-built app connectors, deployment and support tooling, and an embedded integration marketplace. From startups to Fortune 100, B2B SaaS companies across a wide range of verticals and many countries rely on Prismatic to power their integrations.

Get the latest from Prismatic

Subscribe to receive updates, product news, blog posts, and more.