Skip to main content

How to set up webhooks

Setting up webhooks is one of two jobs. As a receiver, you expose an HTTPS endpoint, register its URL with the provider, verify each request’s signature, and return a 2xx quickly. As a sender, you define event types, let customers register endpoints, sign every payload, and deliver it from a queue with retries.

Sending webhooks?
Svix is the enterprise-ready webhook sending service. It handles signing, retries, and delivery observability, so you can ship a reliable webhook platform in minutes instead of months. Start sending webhooks with Svix.

Most tutorials cover only one side, usually a specific provider’s settings page. This guide covers both, without assuming a particular vendor. If you need the basics first, start with what a webhook is.

How do I set up webhooks?​

To set up webhooks as a receiver, create a public HTTPS route that accepts POST requests, paste its URL into the provider’s webhook settings, and choose which events to subscribe to. In your handler, verify the signature, reject stale timestamps, queue the payload for processing, and return a 2xx within a few seconds.

Here is what each of those steps protects you from:

  1. Create the endpoint. A webhook endpoint is an ordinary HTTP route, such as POST /webhooks. It must be reachable over HTTPS from the public internet. That means localhost won’t work without a tunnel.
  2. Register the URL and pick events. Subscribe only to the event types you handle. Every extra event is traffic you have to accept and ignore.
  3. Store the signing secret. The provider gives you a secret when you register. Keep it in your secrets manager, not in code.
  4. Verify every request. Anyone who finds your URL can POST to it, so check the webhook signature before trusting the body.
  5. Acknowledge fast, process later. Providers time out slow responses, commonly somewhere between 5 and 30 seconds, and treat a timeout as a failure. Put the payload on a queue and return immediately.
  6. Handle duplicates. Delivery is at-least-once, so the same event can arrive twice. Store the message ID and skip ones you’ve already processed. This is covered in more depth under idempotency.

Many providers now follow the Standard Webhooks spec. It signs {id}.{timestamp}.{body} with HMAC-SHA256 and sends the result in three headers. A minimal verifying receiver in Flask looks like this:

import base64, hashlib, hmac, time
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = base64.b64decode(os.environ["WEBHOOK_SECRET"].removeprefix("whsec_"))

@app.post("/webhooks")
def receive():
msg_id = request.headers["webhook-id"]
ts = request.headers["webhook-timestamp"]
if abs(time.time() - int(ts)) > 300: # 5-minute tolerance
abort(400)
signed = f"{msg_id}.{ts}.".encode() + request.get_data()
expected = base64.b64encode(hmac.new(SECRET, signed, hashlib.sha256).digest()).decode()
sigs = [s.split(",", 1)[1] for s in request.headers["webhook-signature"].split()]
if not any(hmac.compare_digest(expected, s) for s in sigs):
abort(401)
enqueue(msg_id, request.get_json()) # your job queue; dedupe on msg_id
return "", 204

The code handles three details you shouldn’t skip:

The receiving best practices page goes further.

How do I add webhooks to my API product?​

To add webhooks to your API product, define a catalog of event types, give customers an API or UI to register endpoint URLs, and generate a signing secret per endpoint. When an event happens, write it to a queue. Workers then deliver it with a signature, a timeout, and retries on an exponential backoff schedule.

The sending side is where most of the work lives, because you own reliability for every customer’s flaky server. The pieces break down like this.

Event catalog and payloads. Pick names like invoice.paid and keep them stable. Our guide to event naming conventions covers the patterns. You also need to decide between full objects and IDs the consumer fetches. That choice is covered under fat vs thin payloads. Version payloads from day one, because customers will parse every field you ship.

Endpoint management. Customers need to create, update, disable, and delete endpoints, and filter which events each one receives. If you serve many customers, isolate them so one tenant’s slow endpoint can’t delay another’s deliveries. The multi-tenant webhook service guide covers that design. Validate URLs against internal IP ranges, or your webhook system becomes an SSRF vector.

Delivery pipeline. Never send webhooks inline in the request that triggered the event. Write the event to a durable queue (Postgres, Redis, or a broker), then have workers POST it with a short timeout.

Retries. Treat any non-2xx response or timeout as a failure and retry with exponential backoff. A schedule such as the following spreads attempts over roughly a day, which lets consumers survive a deploy or an outage:

  • immediately
  • 5 seconds
  • 5 minutes
  • 30 minutes
  • 2 hours
  • 5 hours
  • 10 hours
  • 10 hours

After the last attempt, move the message to a dead letter queue and consider disabling endpoints that fail persistently. For the reasoning behind the schedule, see the retry best practices.

Observability for customers. Log every attempt with its status code, response body, and latency, and expose those logs to customers. Also give them a way to replay failed messages. Without that, every failed delivery becomes a support ticket asking you to check the logs.

The webhook sender guide walks through the code for each piece.

How do I test a webhook setup?​

Test webhooks by sending real or simulated events to an endpoint you can inspect.

  • Receivers: point the provider at a temporary URL from a request inspector such as Svix Play to see exact headers and bodies. Then replay those payloads against your local handler through a tunnel.
  • Senders: test against endpoints that return 500s, time out, and respond slowly. Confirm that retries, signatures, and the dead letter path behave as designed.

The webhook testing guide and debugging guide cover the specifics.

Build the receiver yourself, decide carefully on the sender​

The receiving side is small enough that you should just write it: one route, a signature check, a queue, and a dedupe table.

The sending side is different. The first version takes a week. The next year goes to retries, tenant isolation, a customer-facing log viewer, secret rotation, and chasing dropped messages. If webhooks are a feature of your product rather than the product itself, a service like Svix can run the delivery pipeline, retries, and customer portal while your team keeps ownership of the events and payloads. The guide to evaluating webhook infrastructure lays out the build-vs-buy tradeoffs.

Either way, the design decisions in this guide still apply: stable event types, signed payloads, fast acknowledgments, and idempotent consumers. Get those right and webhooks stay something you rarely have to think about.