Skip to content

Webhooks

Christian KASSE edited this page Apr 2, 2026 · 1 revision

Webhooks

Overview

Essabu sends webhook events to your application when important actions occur on the platform. Webhooks are signed with HMAC-SHA256 to ensure authenticity.

Supported Events

Event Type Description
invoice.created A new invoice was created
invoice.paid An invoice was fully paid
payment.succeeded A payment was completed
payment.failed A payment attempt failed
employee.created A new employee was added
employee.terminated An employee was terminated
loan.approved A loan application was approved
loan.disbursed Loan funds were disbursed
subscription.created A new subscription started
subscription.cancelled A subscription was cancelled

Webhook Payload

{
  "id": "evt_abc123",
  "type": "invoice.created",
  "created_at": "2026-03-26T10:00:00Z",
  "data": {
    "id": "inv-uuid",
    "total": 1500.00,
    "currency": "USD",
    "customer_id": "cust-uuid"
  }
}

Signature Verification

Every webhook request includes an X-Essabu-Signature header. Always verify this before processing.

import hashlib
import hmac


def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
    """Verify the webhook HMAC-SHA256 signature."""
    expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

Flask Example

import hashlib
import hmac
import json

from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = "whsec_your_webhook_secret"


def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)


@app.route("/webhooks/essabu", methods=["POST"])
def handle_webhook():
    payload = request.get_data()
    signature = request.headers.get("X-Essabu-Signature", "")

    if not verify_signature(payload, signature, WEBHOOK_SECRET):
        return jsonify({"error": "Invalid signature"}), 401

    event = json.loads(payload)
    event_type = event.get("type", "")
    data = event.get("data", {})

    if event_type == "invoice.created":
        print(f"New invoice: {data.get('id')} - {data.get('total')}")
    elif event_type == "payment.succeeded":
        print(f"Payment succeeded: {data.get('id')} - {data.get('amount')}")
    elif event_type == "employee.created":
        print(f"New employee: {data.get('first_name')} {data.get('last_name')}")
    elif event_type == "loan.approved":
        print(f"Loan approved: {data.get('id')} - {data.get('amount')}")
    else:
        print(f"Unhandled event type: {event_type}")

    return jsonify({"received": True}), 200

FastAPI Example

import hashlib
import hmac
import json

from fastapi import FastAPI, Request, HTTPException

app = FastAPI()
WEBHOOK_SECRET = "whsec_your_webhook_secret"


@app.post("/webhooks/essabu")
async def handle_webhook(request: Request):
    payload = await request.body()
    signature = request.headers.get("X-Essabu-Signature", "")

    expected = hmac.new(WEBHOOK_SECRET.encode(), payload, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(f"sha256={expected}", signature):
        raise HTTPException(status_code=401, detail="Invalid signature")

    event = json.loads(payload)
    event_type = event["type"]
    data = event["data"]

    # Process the event
    print(f"Received: {event_type}")

    return {"received": True}

Best Practices

  1. Always verify signatures before processing webhook data
  2. Respond with 200 quickly -- do heavy processing asynchronously
  3. Handle duplicates -- webhooks may be delivered more than once
  4. Use HTTPS for your webhook endpoint
  5. Log all received events for debugging and audit trails

Clone this wiki locally