> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.vitablehealth.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.vitablehealth.com/_mcp/server.

# Verifying Signatures

> 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:dev@vitablehealth.com). Each webhook subscription has its own secret key — store it securely and never expose it in client-side code.

## Headers

```text
X-Vitable-Signature: sha512=<signature>
X-Vitable-Timestamp: <unix_timestamp>
```

## Python

```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

```javascript
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)
  )
}
```

> **Warning**
>
> Always use constant-time comparison (`hmac.compare_digest` in Python, `crypto.timingSafeEqual` in Node.js) to prevent timing attacks.