--- title: Verifying Signatures | Vitable Docs description: Validate that webhook requests came from Vitable using HMAC-SHA512 signatures. --- Every webhook request includes an HMAC-SHA512 signature so you can verify it came from Vitable. The signature is computed over the timestamp and payload using your webhook secret key. To get your webhook secret key, [reach out to our team](mailto:felippe@vitablehealth.com). Each webhook subscription has its own secret key — store it securely and never expose it in client-side code. ## Headers ``` X-Vitable-Signature: sha512= X-Vitable-Timestamp: ``` ## Python ``` import hmac import hashlib import time def verify_webhook(payload: str, signature_header: str, timestamp_header: str, secret_key: str) -> bool: # Reject requests older than 5 minutes (prevents replay attacks) timestamp = int(timestamp_header) if abs(time.time() - timestamp) > 300: return False # Reconstruct the expected signature message = f"{timestamp}.{payload}" expected = "sha512=" + hmac.new( secret_key.encode("utf-8"), message.encode("utf-8"), hashlib.sha512, ).hexdigest() return hmac.compare_digest(signature_header, expected) ``` ## Node.js ``` import crypto from "node:crypto" function verifyWebhook(payload, signatureHeader, timestampHeader, secretKey) { // Reject requests older than 5 minutes const timestamp = parseInt(timestampHeader, 10) if (Math.abs(Date.now() / 1000 - timestamp) > 300) { return false } // Reconstruct the expected signature const message = `${timestamp}.${payload}` const expected = "sha512=" + crypto.createHmac("sha512", secretKey).update(message).digest("hex") return crypto.timingSafeEqual( Buffer.from(signatureHeader), Buffer.from(expected) ) } ``` Always use constant-time comparison (`hmac.compare_digest` in Python, `crypto.timingSafeEqual` in Node.js) to prevent timing attacks.