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===. Usehmac.compare_digest(Python) ortimingSafeEqual(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-Signatureheader is missing, return a401immediately. - Use HTTPS endpoints. Always expose your webhook handler over HTTPS to prevent the payload (and signature) from being intercepted in transit.