Webhook Verification

The SDK provides utilities to verify webhook signatures, ensuring that incoming webhook payloads genuinely originated from Felloh.


Verifying Signatures

Felloh signs every webhook request with an HMAC-SHA256 signature sent in the X-Signature header. The SDK provides two functions to verify this signature:

  • verify_webhook_signature — returns True or False
  • assert_webhook_signature — raises FellohWebhookSignatureError if invalid

Both use timing-safe comparison to prevent timing attacks.

Parameters

  • Name
    payloadrequired
    Type
    str | bytes
    Description

    The raw request body. Must not be parsed or modified — use the raw body as received.

  • Name
    signaturerequired
    Type
    str
    Description

    The value of the X-Signature header from the webhook request.

  • Name
    secretrequired
    Type
    str
    Description

    Your webhook signing secret from the Felloh dashboard.

Boolean Check

from felloh import verify_webhook_signature

is_valid = verify_webhook_signature(
    payload=raw_request_body,
    signature=request.headers["X-Signature"],
    secret="your-webhook-secret",
)

if not is_valid:
    return Response("Invalid signature", status=401)

# Process the webhook...

Assert (raises on failure)

from felloh import assert_webhook_signature
from felloh import FellohWebhookSignatureError

try:
    assert_webhook_signature(
        payload=raw_request_body,
        signature=request.headers["X-Signature"],
        secret="your-webhook-secret",
    )
except FellohWebhookSignatureError:
    return Response("Invalid signature", status=401)

# Signature is valid, process the webhook...

FastAPI Example

When using FastAPI, use Request.body() to get the raw request body for signature verification.

FastAPI Webhook Handler

from fastapi import FastAPI, Request, Response
from felloh import assert_webhook_signature
from felloh import FellohWebhookSignatureError
import json, os

app = FastAPI()

@app.post("/webhooks/felloh")
async def handle_webhook(request: Request):
    body = await request.body()

    try:
        assert_webhook_signature(
            payload=body,
            signature=request.headers["X-Signature"],
            secret=os.environ["FELLOH_WEBHOOK_SECRET"],
        )
    except FellohWebhookSignatureError:
        return Response("Invalid signature", status_code=401)

    event = json.loads(body)
    print("Webhook received:", event)

    return Response("OK", status_code=200)

Flask Example

When using Flask, use request.get_data() to get the raw request body.

Flask Webhook Handler

from flask import Flask, request
from felloh import assert_webhook_signature
from felloh import FellohWebhookSignatureError
import os

app = Flask(__name__)

@app.route("/webhooks/felloh", methods=["POST"])
def handle_webhook():
    body = request.get_data()

    try:
        assert_webhook_signature(
            payload=body,
            signature=request.headers["X-Signature"],
            secret=os.environ["FELLOH_WEBHOOK_SECRET"],
        )
    except FellohWebhookSignatureError:
        return "Invalid signature", 401

    event = request.get_json()
    print("Webhook received:", event)

    return "OK", 200