From 4aee8b9161753fa526952ae6ae23426cfab427b1 Mon Sep 17 00:00:00 2001 From: Aaron Forsander Date: Tue, 30 Sep 2025 14:00:18 -0700 Subject: [PATCH] Add documentation about verifying webhooks to the README --- README.md | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/README.md b/README.md index 0bac60aee..9f25b034e 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,95 @@ client.files.create( The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically. +## Webhook verification + +Verifying webhook signatures is _optional but encouraged_. + +For more information about webhooks, see [the API docs](https://increase.com/documentation/webhooks#events-and-webhooks). + +### Parsing webhook payloads + +For most use cases, you will likely want to verify the webhook and parse the payload at the same time. To achieve this, we provide the method `client.webhooks.unwrap()`, which parses a webhook request and verifies that it was sent by Increase. This method will raise an error if the signature is invalid. + +Note that the `body` parameter must be the raw JSON string sent from the server (do not parse it first). The `.unwrap()` method will parse this JSON for you into an event object after verifying the webhook was sent from Increase. + +```python +from increase import Increase +from flask import Flask, request + +app = Flask(__name__) + +# You can also configure the webhook secret with any of: +# - The `INCREASE_WEBHOOK_SECRET` environment variable. +# - The `webhook_secret` argument to the Increase client. +# - The `secret` argument to `webhooks.unwrap` +client = Increase() + +@app.route("/webhook", methods=["POST"]) +def webhook(): + request_body = request.get_data(as_text=True) + + try: + + event = client.webhooks.unwrap(request_body, request.headers, secret='your webhook secret') + + if event.type == "account.created": + print("Account created:", event.data) + elif event.type == "account.updated": + print("Account updated:", event.data) + else: + print("Unhandled event type:", event.type) + + return "ok" + except Exception as e: + print("Invalid signature:", e) + return "Invalid signature", 400 + + +if __name__ == "__main__": + app.run(port=8000) +``` + +### Verifying webhook payloads directly + +In some cases, you may want to verify the webhook separately from parsing the payload. If you prefer to handle these steps separately, we provide the method `client.webhooks.verify_signature()` to _only verify_ the signature of a webhook request. Like `.unwrap()`, this method will raise an error if the signature is invalid. + +Note that the `body` parameter must be the raw JSON string sent from the server (do not parse it first). You will then need to parse the body after verifying the signature. + +```python +import json +from increase import Increase +from flask import Flask, request + +app = Flask(__name__) + +# You can also configure the webhook secret with any of: +# - The `INCREASE_WEBHOOK_SECRET` environment variable. +# - The `webhook_secret` argument to the Increase client. +# - The `secret` argument to `webhooks.unwrap` +client = Increase() + +@app.route("/webhook", methods=["POST"]) +def webhook(): + request_body = request.get_data(as_text=True) + + try: + client.webhooks.verify_signature(request_body, request.headers) + + # Parse the body after verification + event = json.loads(request_body) + print("Verified event:", event) + + return "ok" + except Exception as e: + print("Invalid signature:", e) + return "Invalid signature", 400 + + +if __name__ == "__main__": + app.run(port=8000) +``` + ## Handling errors When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `increase.APIConnectionError` is raised.