Skip to main content

Webhook Security Best Practices

Written by:
Gints Dreimanis
Gints Dreimanis
wordpress-sync/feature-webhook

July 6, 2022

0 mins read

Webhooks are one of the best ways to transfer information about occasional events from one system to another. In contrast to methods like HTTP polling — which involves the client repeatedly asking for information from the server — webhooks are triggered by events.

This makes them simple and effective. A client can subscribe to a webhook to send a message to an endpoint whenever a specific event happens.

But, since webhooks often involve sending messages to a public endpoint on the internet, security problems can arise. Without proper security measures, an attacker can read and change messages or even fully masquerade as you. Therefore, we must take every possible step to achieve the necessary level of security when running a webhook service.

In this blog, we’ll cover the most effective practices for securing webhooks so we can share data between applications without creating a large attack surface.

8 Security best practices to follow when creating webhooks

  1. Encrypt data sent through webhooks

  2. Sign webhooks

  3. Authenticate connections

  4. Add timestamps to messages

  5. Use certificate pinning

  6. Don't use webhooks for sensitive data

  7. Implement logging for all webhook messages sent out

  8. Use a subscription model with expiration dates

When creating a webhook implementation, it’s best to avoid relying on any single security practice. Instead, we should implement multiple approaches to ensure our system stays safe — even if an attacker overcomes some of our security measures.

At a bare minimum, we should use encryption so that messages to and from clients stay private, and two-way authentication so that the client and server are both sure who they’re talking to.

Encrypt data sent through webhooks

While we can’t prevent bad actors from intercepting our messages, we can prevent them from reading the contents. A simple way to ensure that all communication happens securely is using HTTPS instead of HTTP.

Using HTTPS is very easy, and there's little to no reason for not utilizing it. HTTPS encrypts all data, making it much harder for a third party to access.

Sign webhooks

Bad actors can intercept messages sent on the internet and change their contents to benefit themselves. To prevent unwanted changes, we should sign our messages. We can do this using a hash-based message authentication code (HMAC), which consists of a hashing algorithm and a secret code or key that both parties share.

HMAC hashes (or scrambles) assign a specific hash function to each message. Allowing a webhook consumer to verify a message’s authenticity and integrity by checking the message against the hash.

You can learn more about signing webhooks in this helpful Twilio article.

Authenticate connections

Webhook endpoints are frequently public and available for anyone on the internet. The availability of webhook endpoints creates multiple vulnerabilities for webhook consumers, including denial of service attacks. To solve this problem, webhook consumers should authenticate the messages’ sources. Consumers can authenticate sources with a username and password or an authentication token.

Additionally, it’s a good idea to authenticate the consumer to ensure the message arrives at the right place.

Add timestamps to your message

We can tag our messages with the time sent to help prevent replay attacks. Replay attacks don’t read or manipulate messages, but they can catch a legitimate, encrypted message and resend it at an opportune moment.

By tagging our messages with timestamps, we enable the client to ensure that the message sent is current and not something we sent out a couple of weeks ago. Since our messages are also signed, the attacker can’t change the timestamp or save the message for a replay attack.

Use certificate pinning

If our client receives a connection from a web server with a trusted certificate with our API, it doesn’t mean that the website (and the certificate) is ours. A potential attacker can make a copy of our API and add a trusted certificate to it. Then, intercept the legitimate message and send their own instead.

One way to solve this problem is with certificate pinning. The client can pin the server’s certificate in the code if the event comes from the same server each time. Pinning the certificate means they hardcode the certificate or its hash (fingerprint) in the app and compare it with the provided certificate each time they establish a connection.

However, we need to exercise caution when using this technique because it relies on hardcoded parameters. If there’s a certificate change or revocation, the client might not be able to update the certificate fast enough.

You can see an example of certificate pinning in this Mozilla documentation.

Don’t use webhooks for sensitive data

Even when secured, webhooks are inappropriate for sensitive data such as passwords or credit card information. In general, webhooks are for sending notifications about events. If you’re putting sensitive data inside messages sent out by webhooks, you should reconsider your use case.

Implement logging for all webhook messages sent out

We can log webhook messages using an in-house or third-party solution. Logs help us record every message sent, which is helpful during auditing. Additionally, we can see all the messages sent and connections initiated in case of a security incident. Monitoring logs can also help us catch questionable behaviors — like failed delivery attempts — before a security incident happens. 

Logging webhook messages is also useful for efficiency. For example, we can unsubscribe users that haven’t successfully received our logs for a long time, keeping the list accurate and up to date. Check out this NearForm video to learn more about logging webhook messages And, for a good webhook logging tool, check out this Node.js logger.

Use a subscription model with expiration dates

It’s best to allow users to provide an expiration date for their subscription. While an expiration date doesn’t impact a lot, using it alongside other security practices adds an extra layer of security.

When used in  combination with different encryption, authentication, and authorization methods, limiting the time a server or client has to provided privileges helps establish a depth of security. Additionally, expiration dates decrease the timeframe a bad actor has to find a weak spot in our security (like stolen credentials) since the client must go through the subscription process again when it expires.

Establishing webhook security

While encrypting our webhook messages is the foundation for providing clients with a way to validate messages, other security practices build upon this principle and, when combined, offer more complete protection.

Remember that the goal of webhooks is typically to inform clients about events happening in your system. Most of these events, such as new GitHub PR requests, new subscriber followers, and so on, should be harmless, so attackers won’t have a lot of interest in tampering with this communication. It’s strongly advised to avoid sending highly sensitive data via webhooks.

A comprehensive security approach begins with  choosing security measures suited to the domain of your application and nature of the information you’re sending.  Updates about upcoming events, for example, require fewer security measures than messages that include detailed information for individual clients. By tailoring your approach and establishing overlapping layers of security, you can work with greater confidence that your code and client data are protected from the various types of vulnerabilities we see today.