Skip to main content

How to Build a Webhook Sender

Today, we're diving into the world of webhook senders. We'll start with a naive implementation, and then level it up by addressing its shortcomings and implementing best practices. Finally, we'll discuss how to scale the system for heavy loads. And, of course, I'll provide code samples to help you out. Let's get started!

Building webhooks?
Svix is the enterprise ready webhooks sending service. With Svix, you can build a secure, reliable, and scalable webhook platform in minutes. Looking to send webhooks? Give it a try!

Naive Webhook Sender

First, let's create a simple webhook sender using Python and the requests library.

import json
import requests

def send_webhook(url, payload):
response = requests.post(url, json=payload)
print(f"Webhook sent to {url}, status: {response.status_code}")

if __name__ == '__main__':
webhook_url = 'https://example.com/your-webhook-listener-url'
payload = {'foo': 'bar'}

send_webhook(webhook_url, payload)

This script is pretty basic. It defines a send_webhook function that sends a POST request with a JSON payload to a specified URL.

Problems with the Naive Implementation

  1. Lack of authentication: The receiver has no way to verify the authenticity of the webhook, which makes it vulnerable to spoofing attacks.
  2. No error handling or retries: If the webhook delivery fails, the sender doesn't attempt to resend it, potentially leading to data loss.
  3. Synchronous: The sender blocks other tasks while sending webhooks, potentially impacting application performance.

Best Practices for Sending Webhooks

  1. Sign your webhooks: Use a shared secret key to sign your webhooks. The receiver can verify the signature to ensure the webhook came from a trusted source.
  2. Handle errors and retries: Implement a retry mechanism with an exponential backoff strategy to avoid overwhelming the receiving server.
  3. Send webhooks asynchronously: Use asynchronous processing or a queueing system to prevent blocking other tasks in your application while sending webhooks.
  4. Monitor and log webhook activity: Keep track of webhook deliveries, failures, and retries to identify issues and improve reliability.

Scaling Your Webhook Sender

  1. Use a message queue: Integrate a message queue like RabbitMQ or Apache Kafka to handle webhook payloads, distributing the load across multiple workers.
  2. Implement rate limiting: If you're sending webhooks to third-party services, implement rate limiting to avoid exceeding their API limits or triggering denial of service protections.
  3. Distribute webhook processing: Deploy multiple webhook sender instances behind a load balancer or use container orchestration systems like Kubernetes to distribute the workload.

Upgraded Webhook Sender

Now, let's implement these best practices and improvements in our webhook sender.

import json
import requests
import hmac
import hashlib
import time
from threading import Thread

def generate_signature(payload, secret_key):
mac = hmac.new(secret_key.encode(), payload.encode(), hashlib.sha256)
return mac.hexdigest()

def send_webhook(url, payload, secret_key):
headers = {
'Content-Type': 'application/json',
'X-Signature': generate_signature(json.dumps(payload), secret_key)
}
response = requests.post(url, headers=headers, data=json.dumps(payload))
return response.status_code

def send_webhook_with_retry(url, payload, secret_key, retries=3, backoff_factor=2):
for i in range(retries):
status_code = send_webhook(url, payload, secret_key)
if status_code == 200:
print(f"Webhook sent to {url}, status: {status_code}")
break
else:
print(f"Webhook failed with status: {status_code}. Retrying...")
time.sleep(backoff_factor ** i)

def send_webhook_async(url, payload, secret_key):
thread = Thread(target=send_webhook_with_retry, args=(url, payload, secret_key))
thread.start()

if name == 'main':
webhook_url = 'https://example.com/your-webhook-listener-url'
payload = {'foo': 'bar'}
secret_key = 'your-secret-key'

send_webhook_async(webhook_url, payload, secret_key)

In this upgraded version, we added the generate_signature function to create an HMAC-SHA256 signature using a secret key, and modified the send_webhook function to include the signature in the request headers.

The send_webhook_with_retry function wraps our webhook sender with a retry mechanism, utilizing an exponential backoff strategy to avoid overwhelming the receiving server.

We wrapped everything in the send_webhook_async function, which starts a new thread to send the webhook without blocking other tasks.

And that's it! Now you have a webhook sender that follows best practices and is built to scale. Happy coding, and remember, when it comes to webhooks, a well-designed sender makes all the difference!