# Aurabase Webhooks & HMAC Signatures Reference > Documentation: https://aurabase.cloud/en/docs/guides/webhooks > Signature standard: Stripe convention `X-Aurabase-Signature: t={timestamp},v1={hmac-sha256}` ## Security Headers Every webhook sent by Aurabase includes: - `X-Aurabase-Event-Id`: Unique event ID for idempotent processing. - `X-Aurabase-Signature`: Cryptographic HMAC-SHA256 signature calculated over `"{timestamp}.{rawBody}"`. ## Multi-Language Verification ### Node.js / TypeScript ```typescript import crypto from 'node:crypto' export function verifyWebhook(rawBody: string, signatureHeader: string, secret: string): boolean { const parts = Object.fromEntries(signatureHeader.split(',').map((p) => p.split('='))) const { t: timestamp, v1: signature } = parts // 1. Replay attack protection (5 min max) if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false // 2. Compute expected HMAC-SHA256 const expected = crypto.createHmac('sha256', secret) .update(`${timestamp}.${rawBody}`) .digest('hex') // 3. Constant-time comparison return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)) } ``` ### Python ```python import hmac, hashlib, time def verify_webhook(raw_body: bytes, sig_header: str, secret: str) -> bool: parts = dict(x.split("=") for x in sig_header.split(",")) timestamp, signature = int(parts.get("t", 0)), parts.get("v1", "") if abs(time.time() - timestamp) > 300: return False payload = f"{timestamp}.".encode("utf-8") + raw_body expected = hmac.new(secret.encode("utf-8"), payload, hashlib.sha256).hexdigest() return hmac.compare_digest(signature, expected) ```