Background image

Webhooks

The double standard of webhook security and API security

David Adler

David Adler

January 2, 2025

Featured blog post image

Interesting fact: 80% of API producers sign their webhook requests with HMAC-SHA256 (opens in a new tab).

Request signing provides authenticity by enabling you, the webhook consumer, to verify who sent the webhook. However, webhooks aren’t so different from any other API request. They’re just an HTTP request from one server to another. So why not use an API key just like any other API request to check who sent the request? Signing requests does give extra security points, but why do we collectively place higher security requirements on webhook requests than API requests?

What is request signing?

Disclaimer: this is not an exhaustive explanation of cryptographic signatures. This is a practical introduction to what is meant by “request signing” in this article and by the average webhooks service.

function sign_request(request) {
// The secret is never included in the request
// Also, the request contents form the signature input
request.headers['x-signature'] = sign(secret, request.body)
}

When consuming the request, you’d do something like:

function verify_request(request) {
actual = request.headers['x-signature']
// This is the same secret used for the sender and consumer
expected = sign(secret, request.body)
return actual == expected
}

The three security benefits of signing requests

There are three benefits to signing your requests (in addition to encrypting the request in transit over TLS):

  1. Reduced risk of leaking secrets: Though traditional API requests are likely over HTTPS, the application server is likely not the TLS-terminating gateway. Once decrypted, it’s common for API Keys to leak into logs, queues, third parties and traverse several layers of infrastructure. Signed requests never contain the sensitive secret, so there’s a smaller surface area that the secret will touch and thus reduced risk of leaking the secret.
  2. Replay protection: With an API Key, you have weaker guarantees on when the message was sent. Since you can include a timestamp and/or a nonce in the signed message, you have stronger guarantees somebody didn’t attempt to maliciously recycle the same request.
  3. Integrity: With an API Key, you have fewer guarantees that the request contents were created by the same party that “owns” the API Key. Maybe some malicious HTTP client middleware added or modified it? With signing, the signature is built from the request contents.

Why aren’t most API requests signed?

If there are so many benefits, why aren’t most API requests signed? While it’s a lot less common, some big names like Amazon (opens in a new tab), Azure (opens in a new tab) and Oracle (opens in a new tab) all employ request signing in their APIs. Indeed, there is even a standard for signing HTTP requests (opens in a new tab). So are API keys just a phase?

I’ll posit that the primary reason most people don’t opt for signing their API requests is because of herd mentality. That’s a valid reason. If simple API Keys are good enough for Stripe requests (opens in a new tab) then they’re probably good enough for you, too. Is request signing really that much more secure? Security is like stacked slices of Swiss cheese (opens in a new tab) with lots of randomly placed holes. The more layers you have, the more holes the attacker has to find. Knowing when to stop adding layers is hard. There’s always better security. Inline with mantras like “don’t roll your own crypto” and “not invented here” it’s safer and easier to follow the wisdom of the crowd.

Having said that, there are some principled reasons for why you wouldn’t bother signing your API requests:

  • API Keys are good enough: At least for the comparison in this article, you can assume the API producer does enforce HTTPS for both API requests and webhook endpoints. If the request will be encrypted over TLS, the risk of 2 & 3 above are massively diminished. TLS guarantees confidentiality and integrity. This makes the integrity guarantees provided by 3 somewhat redundant as the attacker would need to be able to view or modify the unencrypted request, which would likely require compromising the request sender’s machine or infrastructure. At which point, as the victim, requests being replayed is the least of your concerns.
  • Complexity of implementation: For example, how you will sign server sent events or streams? Which parts of the HTTP request will be included in the signature input? Webhook requests are mostly small-ish POST requests which makes them practical to sign.
  • Performance overhead: signed requests are guaranteed to be slower than traditional API key requests, especially for big payloads.

Why do we place higher security requirements on webhooks?

Programming opinions

It is valid to argue that webhook requests don’t inherently deserve stronger security measures than traditional API requests. The security benefits of request signing can provide value in either context. Therefore, there is a double standard. If you do decide to use shared secrets for webhooks, it is a reasonable decision and that doesn’t mean your webhooks service is insecure.

Having said that, there are some reasons why webhooks often receive this different security treatment:

  • Webhooks are untrusted URLs: It’s one thing if you accidentally hand your API key to a malicious site—now they can impersonate you. It’s another if Stripe, for example, sends a secret to a malicious site—now that site can impersonate Stripe. What is the risk of a misconfigured webhook URL vs a misconfigured API request? Then again, if a URL is truly untrusted, Stripe shouldn’t be sending data there in the first place. In practice, Stripe and similar providers trusts URLs their customers configure. By using a separate secret for each customer, a single compromised secret doesn’t compromise all customers.
  • Historical precedent: This precedent likely dates back to the PubSubHubbub standard from 2008 (opens in a new tab) and snowballed from there. Signing is also included as a best practice in the more recent Standard Webhooks (opens in a new tab). As before, most webhooks services follow the wisdom of the crowd.
  • Non-HTTPS Webhooks: While increasingly rare, some webhooks still use plain HTTP. Request signing can provide some level of protection against potential man-in-the-middle attacks when TLS is absent. Most modern webhook providers, however, enforce HTTPS by default and still use signing.

Regardless, request signing adds valuable security layers to any type of request, which is why we support API Producers to configure request signing in our generated SDKs at Speakeasy (opens in a new tab). We’re following the wisdom of the crowd, too.

CTA background illustrations

Speakeasy Changelog

Subscribe to stay up-to-date on Speakeasy news and feature releases.