Best Practices for Creating Your Own Webhooks

Webhooks have become a popular way for different pieces of software to communicate with each other and are now an industry standard. As events drive most of the web, this concept has become more relevant because, unlike an API that awaits requests, webhooks actively send data and alerts whenever triggered.

Using webhooks speeds up integration. Rather than waiting on requests from your clients, you can send data about live events as they happen. Your clients don’t need to poll you or set up third-party listening services. Instead, they simply provide an endpoint to call when you have something for them.

For example, webhooks can trigger a Slackbot when a service onboards a new user. Webhooks can also send a text message to a DevOps team when the service detects an outage. This event-driven approach to API development enables you to respond to mission-critical events and reduce server load.

How can you make sure that your webhooks are secure and efficient? Let’s review some of the best practices you should consider when creating your own webhooks.

Verify Endpoints

You want to reassure your clients that they are receiving data from you and not bad actors. So, clients provide an endpoint to which you can POST and get a timely response status of 200. You should encourage them to verify the event you are sending before processing the data.

"/service/webhook", async (req, res) => {
 // Verify event
 // Store event
 // Send 200 response
 // Do some work


An easy way to provide your clients with some level of security is to instead use an API key for validation. The two applications can use a shared API key to verify the message has not been intercepted and changed in a man-in-the-middle attack. Your application can use the key to create a cryptographic summary of the webhook’s payload and return that value in a header. The client’s application then computes the value, checks if the two values match, and if so, confirms the data is valid.

For more critical webhooks, like those dealing with billing, you can provide a signature validation to ensure data integrity. Setting this up involves creating a shared secret between you and the client. You use it to add a header to your request.

					import crypto from "crypto"

async function generateAndAddSignatureHeader({body, options, client}) {

 // Some secure function get secret for this client.
 const clientSecret = await getClientSecret(client);

 const signature = crypto.createHmac('sha256', clientSecret).update(body).digest('base64')

  options.headers["X-Webhook-Signature"] = signature

 return {body, options}


Then, the client uses a piece of middleware to handle this.

					import crypto from "crypto"

function validateSignature(req, res, next) {
 // Get the reader from the incoming webhook
 const sigHeader = req.headers["X-Webhook-Signature"]
  const signature = crypto.createHmac('sha256', process.env.SHARED_SECRET).update(req.body).digest('base64')

 if (signature !== sigHeader) {
   // Webhook hasn't been signed properly.
   res.status(401).send({ message: "Webhook is not properly signed"})



Ensuring that webhooks have properly signed incoming events allows clients to be more confident of the integrity of the data received. This confidence can inspire them to trust this event layer and develop even more exciting features with your product.

Perform Error Handling

You live globally connected, but sometimes those connections don’t work. Even services with a billion users can go offline. You want to ensure your webhooks don’t make it more challenging for clients to recover.

What should you do when your webhook doesn’t reach its destination? First, you can ensure the event isn’t lost and that you have stored it in a queue so you can retry.

While retrying the events in the queue, you can follow a back-off strategy. The most common one is exponentially backing off. This stops your webhooks from causing an unintentional denial of service to your clients. In JavaScript, a non-queue-based system might look like this:

					const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))

async function callWithRetry(fn, depth = 0) {
 try {
   return await fn()
 } catch(error) {
   if (depth > 7) {
     // Flag and log
     throw error

   await wait(2 ** depth * 10)

   return callWithRetry(fn, depth + 1)

As mentioned, you’d be better using a queue-based system as it is more memory-safe. In this example, though, you are logging and moving on after seven tries. As well as logging particular events that have failed, you can log endpoints that have failed on repeated consecutive occasions. While you can keep collecting and storing incoming events, you should stop triggering the endpoint and alert your clients to a possible problem in their API.

Avoid Sending Too Much Data

As you send data to your clients to describe events, try not to send too much data. For one, it makes it easier to maintain SOC2 and other compliance standards. To be compliant, organizations must maintain and update a list of “sub-processors” when sending data to third-party systems. Plus, they must notify customers when that list changes. It’s much easier to update and manage this list when you avoid sending new sensitive data to third parties. As well, event-driven systems have a tradeoff between sending extra data in the event versus sending the minimal information. Keep in mind that the consumer can always request specific information. Data that can change between the event creation and use is one type you may want to minimize. For example, you may send an event with the user email or phone number, then store and process that event later. By the time the system processes the event and needs to send an SMS to the user, the user may have already changed their phone number in the system. So, use webhooks to send non-sensitive information or status updates. If you do want to provide updates regarding sensitive data, don’t include the data itself. Instead, send an event notification with relevant UUIDs and labels to allow the client to request more. Has a new user signed up for an account? Send {“event_type”: “new_user_signup”} along with the UUID. If your client needs more information, they can request it more securely from your system. If a client has received a parcel, send {“event_type”: “parcel_received”} along with the UUID. Don’t send the address, package contents, or user details. Again, if the client needs that information, they can request it more securely now that they know it’s available.

Perform Logging

Webhooks are part of an event-driven architecture. With these events, you should be able to trace a user through your system from account creation to parcel delivery. Equally, when debugging or auditing, it’s helpful to have an in-depth log for each event that passes through your system, and a dashboard for clients to explore this themselves. When logging the event, along with the payload and the endpoint triggered, you should log the time and necessary retries. It’s also helpful to log how long the round-trip took to help with internal monitoring and client optimization. A detailed dashboard of event logs, visual explorer, and the ability to “replay” your test requests are nice to have. Each adds value to clients by enabling them to review the events to look for issues or improve the process on their end..

Provide Documentation

Documenting your webhook payloads, along with steps to verify the payloads, will make it easier for clients to integrate with your service. So, write API documentation and make it available to your users.


As more services offer a webhook-based approach to interaction, your clients will expect the same timely updates and alerts from your product. Transposit allows you to receive webhooks from any service and makes it seamless to perform all payload validation or processing. You can see more in the Webhooks section of the documentation. With these best practices, you can make sure that adding webhooks to your platform provides your clients with the information they need while keeping your platform secure. If you’re interested in developing expert technical content that performs, let’s have a conversation today.


If you work in a tech space and aren’t sure if we cover you, hit the button below to get in touch with us. Tell us a little about your content goals or your project, and we’ll reach back within 2 business days. 

Share via
Copy link
Powered by Social Snap