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.
Cómo funciona
- When you configure a webhook endpoint, you set a secret key in your organization settings.
- vitalera computes an HMAC-SHA256 signature of the raw request body using your secret key.
- The signature is included in the
x-webhook-humanai-signatureHTTP header. - Your application recomputes the signature and compares it to the header value.
Pasos de verificación
- Extract the
x-webhook-humanai-signatureheader from the incoming request. - Read the raw request body as bytes (before any JSON parsing).
- Compute the HMAC-SHA256 digest of the raw body using your webhook secret.
- Compare the computed signature with the header value using a constant-time comparison function to prevent timing attacks.
- If the signatures match, the payload is authentic. If not, reject the request.
Ejemplos de código
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);
Mejores prácticas de seguridad
- Always verify signatures before processing webhook data. Never trust unverified payloads.
- Use constant-time comparison (
hmac.compare_digestin Python,crypto.timingSafeEqualin 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
timestampfield in the payload and discarding events older than a reasonable window (e.g., 5 minutes).
Solución de problemas
| Problem | Solution |
|---|---|
| Signature mismatch | Ensure you are hashing the raw bytes of the request body, not a parsed/re-serialized version. |
| Empty signature header | Check that your webhook configuration includes a secret key. |
| Intermittent verification failures | Ensure your web framework is not modifying the request body before you read it. |
Siguientes pasos
- Webhooks Overview -- How webhooks work and how to configure them
- Event Types -- See all available webhook events