Webhooks provide a great way to integrate remote applications by sending notifications from one service to another through HTTP requests. In scenarios where the request processing will take time, the callee can return the response immediately. It can update the caller about the request through webhooks later, which allows the caller to avoid waiting or polling for the request results, thus improving the performance. For example, a text message-sending service can put the request from your application in a queue. Once the text message is delivered, it can send a webhook with updates to your application.
The Problem
As previously mentioned, webooks are sent through HTTP requests on public endpoints. These public endpoints can be called by anyone, including unauthorized or malicious users, thus making webhooks vulnerable to attacks. Moreover, since the HTTP protocol communicates data in plain text, anyone can view or tamper with webhook payload information by intercepting the network traffic. This article covers some approaches to secure webhooks.
Authentication Tokens
In this approach, the sender adds an authentication token to the headers of the webhook request, and the receiver validates the token to ensure it came from the correct sender. The approach is simple, but since the authentication token is shared in plain text, any malicious user intercepting the traffic can read the token and modify the webhook payload. There is no way for the receiver to guarantee that the request came from the authentic sender and that the webhook payload is valid.
Signing with HMAC -- Hash-based Message Authentication Code
In this approach, the sender and receiver use the same secret to sign and verify the webhook requests cryptographically. Unlike authentication tokens, this secret key is NOT sent with each webhook request and is decided beforehand. Both parties share a common secret key and agree upon a cryptographically secure hash function to use. The sender combines the webhook payload and the secret key together and then uses the agreed-upon hash function to generate the webhook signature. It then sends this signature as part of the webhook request.
On the receiver side, once the webhook request is received, the receiver generates the hash from the combination of the webhook payload and the secret key. Then, it compares that hash with the request signature sent by the sender. If the signature matches, the webhook request is untampered and sent by the original sender. In a scenario where the request was intercepted and tampered with by a malicious user, the payload will change, and the signature calculated on the receiver side will not match the signature sent with the original webhook request.
One problem the above approach doesn't solve is the duplicate webhook request sent by the malicious user. There is no way for the receiver to verify if the request is fresh and hasn't been sent before. Several times, the receiver uses the webhook updates to make non-idempotent changes in the system. In such scenarios, any duplicate webhook requests can lead to an inconsistent state for the receiver.
HMAC + Timestamp
In this approach, the sender calculates the signature hash using the timestamp, secret key, and webhook payload and sends the same timestamp value as a part of the webhook payload. The receiver recalculates the hash using the request's timestamp and webhook payload and the agreed-upon secret key and hash function. If it matches the signature sent by the sender, the webhook is valid. The timestamp allows the receiver to drop expired or stale webhooks and helps detect the replayed ones. If the attacker tries to change the timestamp value to a more recent one while replaying, the webhook signature will no longer be valid, and if the attacker attempts to replay the webhook payload as is. The receiver will drop it if the timestamp on the request is outside the expiration window.
In a scenario where the attacker replays the webhook payload within the receiver's expiration window, the receiver may accept the webhook as a valid one. This can be tackled by keeping track of the IDs for the already processed webhooks on the receiver side.
Mutual TLS (mTLS) Authentication
All the above approaches are useful for verifying the webhook payload and sender's authenticity. However, authenticating the receiver can also be required in some scenarios, as the malicious actor intercepting the webhooks may send it to the wrong destination.
TLS, or Transport Layer Security, is often used in web applications to validate the server's identity and set up a secure channel for client-server communication. This process is known as a TLS handshake, during which the server presents its TLS certificate to the client upon connection. The certificate is verified by the client, and both client and server agree on how encryption will take place.
However, in mTLS, both client and server have certificates that they present to each other. The parties verify each other's identities through their certificates before setting up an encrypted channel to share information. Once the communication channel is set up, the sender shares the webhook payload with the receiver.
This approach is complex to set up and can sometimes lead to an attacker creating an account on the sender's service and replaying webhooks through that or sending them to a different receiver. Since the account is on the sender's service, the mTLS handshake will pass because the sender certificates will be valid.
To address this, the sender can send verification requests with verification tokens to the receiver's endpoint, and the receiver can generated a cryptographic hash from the combination of the verification token and the shared secret and send an encrypted signature back to the server to prove its identity (eg. Twitter Challenge-Response Checks). This is similar to the HMAC signature approach above. The only difference is that in this case, the webhook receiver is sending an encrypted signature, and the webhook sender is verifying the signature.
References
What is MTLS? | mutual TLS | cloudflare. (n.d.-d). https://www.cloudflare.com/learning/access-management/what-is-mutual-tls/
Securing webhooks. (n.d.). Docs | Twitter Developer Platform. https://developer.x.com/en/docs/x-api/enterprise/account-activity-api/guides/securing-webhooks
Wikipedia contributors. (2024b, June 21). Confused deputy problem. Wikipedia. https://en.wikipedia.org/wiki/Confused_deputy_problem