Salt la continutul principal

Webhook Signature Verification

Every webhook request from vitalera's healthcare API includes a cryptographic signature so you can verify that the payload is authentic and has not been tampered with. This is essential for remote patient monitoring integrations where webhook payloads carry sensitive clinical data such as patient vital signs, alarm triggers, and consent changes.

How It Works

  1. When you configure a webhook endpoint, you set a secret key in your organization settings.
  2. vitalera computes an HMAC-SHA256 signature of the raw request body using your secret key.
  3. The signature is included in the x-webhook-humanai-signature HTTP header.
  4. Your application recomputes the signature and compares it to the header value.

Verification Steps

  1. Extract the x-webhook-humanai-signature header from the incoming request.
  2. Read the raw request body as bytes (before any JSON parsing).
  3. Compute the HMAC-SHA256 digest of the raw body using your webhook secret.
  4. Compare the computed signature with the header value using a constant-time comparison function to prevent timing attacks.
  5. If the signatures match, the payload is authentic. If not, reject the request.

Code Examples

Python

import hmac
import hashlib

def verify_webhook(request_body: bytes, signature_header: str, secret: str) -> bool:
"""Verify the authenticity of a vitalera webhook payload."""
expected_signature = hmac.new(
key=secret.encode("utf-8"),
msg=request_body,
digestmod=hashlib.sha256,
).hexdigest()

return hmac.compare_digest(expected_signature, signature_header)


# Usage in a Flask endpoint
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = "your-webhook-secret"

@app.route("/webhooks/vitalera", methods=["POST"])
def handle_webhook():
signature = request.headers.get("x-webhook-humanai-signature", "")
if not verify_webhook(request.data, signature, WEBHOOK_SECRET):
abort(401, "Invalid signature")

payload = request.get_json()
event_type = payload["event_type"]

# Process the event
print(f"Received event: {event_type}")
return "", 200

Node.js

const crypto = require('crypto');

function verifyWebhook(rawBody, signatureHeader, secret) {
const expectedSignature = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');

return crypto.timingSafeEqual(Buffer.from(expectedSignature), Buffer.from(signatureHeader));
}

// Usage in an Express endpoint
const express = require('express');
const app = express();

const WEBHOOK_SECRET = 'your-webhook-secret';

app.post('/webhooks/vitalera', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-humanai-signature'] || '';

if (!verifyWebhook(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}

const payload = JSON.parse(req.body);
console.log(`Received event: ${payload.event_type}`);
res.sendStatus(200);
});

app.listen(3000);

Security Best Practices

  • Always verify signatures before processing webhook data. Never trust unverified payloads.
  • Use constant-time comparison (hmac.compare_digest in Python, crypto.timingSafeEqual in Node.js) to prevent timing attacks.
  • Read the raw body before JSON parsing. Parsing and re-serializing JSON can change whitespace or key ordering, invalidating the signature.
  • Use HTTPS endpoints to protect webhook payloads in transit.
  • Rotate secrets periodically and update your webhook configuration when you do.
  • Reject replayed requests by checking the timestamp field in the payload and discarding events older than a reasonable window (e.g., 5 minutes).

Troubleshooting

ProblemSolution
Signature mismatchEnsure you are hashing the raw bytes of the request body, not a parsed/re-serialized version.
Empty signature headerCheck that your webhook configuration includes a secret key.
Intermittent verification failuresEnsure your web framework is not modifying the request body before you read it.

Next Steps