Receive Webhooks with Python

In this guide we will explain the basics of receiving webhooks, show how to receive webhooks and validate their signatures using Python and the Svix Python SDK.


Tip: You can use the Svix Play webhook debugger to inspect, test and debug your webhooks during development.


If you are interested in a tool to manage receiving third-party webhooks, please contact us for more information.

Receiving Webhooks 101

Webhooks are how services notify each other of events. At their core they are just a POST request to a pre-determined endpoint. You normally use one endpoint per service, and that endpoint listens to all event types. For example, if you receive webhooks from Acme Inc., you can structure your URL like this:

https://www.example.com/acme/webhooks/.

The way to indicate that a webhook has been processed is by returning a 2xx (status code 200-299) response to the webhook message within a reasonable time-frame (e.g. 15s with Svix).

Another important aspect of handling webhooks is to verify the signature and timestamp when processing them.

Verifying Webhook Signatures

Because of the way webhooks work, attackers can impersonate services by simply sending a fake webhook to an endpoint. Think about it: it's just an HTTP POST from an unknown source. This is a potential security hole for many applications, or at the very least, a source of problems.

In order to prevent it, Svix signs every webhook and its metadata with a unique key for each endpoint. This signature can then be used to verify the webhook does indeed come from Svix, and only gets processed if it is.

Another potential security hole are replay attacks. A replay attack is when an attacker intercepts a valid payload (including the signature), and re-transmits it to your endpoint. This payload will pass signature validation, and will therefore be acted upon.

To mitigate this attack, Svix includes a timestamp for when the webhook attempt occurred. Our libraries automatically reject webhooks with a timestamp that are more than five minutes away (past or future) from the current time. This requires your server's clock to be synchronised and accurate, and it's recommended that you use NTP to achieve this.

For additional information about webhook vulnerabilities, please refer to the webhook security section of our docs.

Example: Verifying Svix Webhook Signatures

When using Svix you can either verify your signatures manually or with the Svix Python library as shown in the examples below.

The Python library supports Python 3.7+, and Python 3.6 (with the dataclass package installed). The package includes PEP 484 type hints.

In addition to Python we also have guides for: JavaScript, TypeScript, Go, Java, Kotlin, Rust, C#, PHP, Ruby, and Svix CLI.

To verify the webhook signatures you first need to get the signing secret for the endpoint (usually from the same UI you register the webhook). In addition, you should also make sure you use the raw (string/bytes) representation of the request body. Using the non-raw body is the number one reason for verification failures. The header values are just the headers as received from the webhook HTTP call.

from svix.webhooks import Webhook

secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"

# These were all sent from the server
headers = {
  "webhook-id": "msg_p5jXN8AQM9LWM0D4loKWxJek",
  "webhook-timestamp": "1614265330",
  "webhook-signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
}
payload = '{"test": 2432232314}'

wh = Webhook(secret)
# Throws on error, returns the verified content on success
payload = wh.verify(payload, headers)

Important: use the raw request body

You need to use the raw request body when verifying webhooks, as the cryptographic signature is sensitive to even the slightest changes. You should watch out for frameworks that parse the request as JSON and then strigify it because this too will break the signature verification. The examples above show how to get the raw request body.


Please note, that the example above requires the Svix library to be installed. Please install it if you haven't already:

pip install svix

Verifying Signatures Manually

Each webhook call includes three headers that are used for verification:

  • Webhook-Id: the unique identifier for the webhook message. This id is unique across all messages (except is a message is resent due to a previous failure).
  • Webhook-Timestamp: timestamp in seconds since epoch.
  • Webhook-Signature: the Base64 encoded list of signatures (space delimited).

Note: Customers of the Svix free tier may have Svix- as the prefix instead of Webhooks-. The Svix libraries support both.


Constructing the Signed Content

The content to sign is composed by concatenating the id, timestamp and payload, separated by the full-stop character (.). In code, it will look something like:

signed_content = "${webhook_id}.${webhook_timestamp}.${body}";

Where body is the raw body of the request. The signature is sensitive to any changes, so even a small change in the body will cause the signature to be completely different. This means that you should not change the body in any way before verifying.

Determining the Expected Signature

Svix uses an HMAC with SHA-256 to sign its webhooks.

To calculate the expected signature, you should HMAC the signed_content from above using the base64 portion of your signing secret (this is the part after the whsec_ prefix) as the key. For example, given the secret whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw you will want to use MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw.

This generated signature should match one sent in the Webhook-Signature header.

The Webook-Signature header is composed of a list of space delimited signatures and their corresponding version identifiers. The signature list is most commonly of length one. Though there could be any number of signatures. For example:

v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=
v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo=
v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo=

Make sure to remove the version prefix and delimiter (e.g. v1,) before verifying the signature.

Please note that to compare the signatures it's recommended to use a constant-time string comparison method in order to prevent timing attacks.

Verify Timestamp

As mentioned above, Svix also sends the timestamp of the attempt in the Webhook-Timestamp header. You should compare this timestamp against your system timestamp and make sure it's within your tolerance in order to prevent timestamp attacks.

Further Reading

That's it! We covered everything you need to know in order to integrate Svix with your service. Though there are many more APIs that you can use to improve the experience of your users or your own development experience. Two such examples are event types and the Svix CLI. For the most up to date information please refer to the Svix docs.

If you have any questions, or you just want to chat, please join the Svix Slack community.

Related articles

You may also be interested in reading the following related guides: