Aira

Webhook Signature Verification

Verify that incoming webhook payloads are authentic using HMAC-SHA256 signatures.

Overview

Every webhook request sent by Aira includes an HMAC-SHA256 signature in the X-Aira-Signature header. You should verify this signature before processing any payload to ensure the request genuinely originated from Aira and has not been tampered with.

Signature Format

The X-Aira-Signature header contains a hex-encoded HMAC-SHA256 digest computed over the raw request body using your webhook secret:

X-Aira-Signature: sha256=a1b2c3d4e5f6...

The value after the sha256= prefix is the hex digest.

Verification with the Python SDK

The Python SDK provides a helper that handles parsing and constant-time comparison:

from aira.extras.webhooks import verify_signature

webhook_secret = "whsec_your_secret_here"

# Inside your webhook handler (e.g., Flask / FastAPI)
def handle_webhook(request):
    payload = request.body  # raw bytes
    signature = request.headers["X-Aira-Signature"]

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

    # Signature is valid — process the event
    event = json.loads(payload)
    print(f"Received event: {event['type']}")

Verification with the TypeScript SDK

The TypeScript SDK exposes an equivalent helper:

import { verifySignature } from "aira-sdk/extras/webhooks";

const webhookSecret = "whsec_your_secret_here";

// Inside your webhook handler (e.g., Express / Next.js)
app.post("/webhooks/aira", (req, res) => {
  const payload = req.rawBody; // raw Buffer
  const signature = req.headers["x-aira-signature"] as string;

  if (!verifySignature(payload, signature, webhookSecret)) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  // Signature is valid — process the event
  const event = JSON.parse(payload.toString());
  console.log(`Received event: ${event.type}`);
  res.sendStatus(200);
});

Manual Verification (Without SDK)

If you are not using an Aira SDK, you can verify the signature manually.

Python

import hmac
import hashlib

def verify_webhook(payload: bytes, header: str, secret: str) -> bool:
    """Verify the X-Aira-Signature header manually."""
    if not header.startswith("sha256="):
        return False

    expected = header[len("sha256="):]
    computed = hmac.new(
        secret.encode("utf-8"),
        payload,
        hashlib.sha256,
    ).hexdigest()

    return hmac.compare_digest(computed, expected)

TypeScript / Node.js

import { createHmac, timingSafeEqual } from "crypto";

function verifyWebhook(payload: Buffer, header: string, secret: string): boolean {
  if (!header.startsWith("sha256=")) return false;

  const expected = header.slice("sha256=".length);
  const computed = createHmac("sha256", secret).update(payload).digest("hex");

  return timingSafeEqual(Buffer.from(computed), Buffer.from(expected));
}

Security Best Practices

  • Verify before processing. Always validate the signature before parsing or acting on the payload. This prevents forged events from triggering side effects.
  • Use constant-time comparison. Never compare signatures with == or ===. Use hmac.compare_digest (Python) or timingSafeEqual (Node.js) to prevent timing attacks.
  • Keep your webhook secret secure. Store it in environment variables or a secrets manager — never commit it to source control.
  • Reject requests without a signature. If the X-Aira-Signature header is missing, return a 401 immediately.
  • Use HTTPS endpoints. Always expose your webhook handler over HTTPS to prevent the payload (and signature) from being intercepted in transit.

On this page