Skip to main content
Docs vCurrentAPI
Version: Current

Webhooks

Webhooks let you integrate SauBit with medical records (HIS/EMR) and internal hospital automations.

When configured, SauBit sends events via POST to a hospital endpoint (e.g. https://hospital.example.com/webhooks/events).

How to configure

Webhook registration is done in the Admin (organization):

  • Destination URL
  • Desired events (or all)
  • Secret (generated and shown only once)

Sent headers

  • X-SauBit-Event: event type
  • X-SauBit-Timestamp: epoch seconds
  • X-SauBit-Signature: HMAC signature

Signature (HMAC)

The body is signed with:

HMAC_SHA256(secret, "{timestamp}.{raw_body}")

Header format:

X-SauBit-Signature: sha256=<hex>

Events

analysis.completed

Fired when an analysis finishes (check or recheck).

{
"event": "analysis.completed",
"data": {
"analysis_id": "uuid",
"endpoint": "/interactions/check",
"cached": false,
"recheck_of": "optional-uuid"
}
}

chat.message

Fired when the hospital sends a chat message.

{
"event": "chat.message",
"data": {
"session_id": "abc123",
"analysis_id": "optional-uuid"
}
}

Verification example (Node.js)

import crypto from "crypto";

// Accepts the current secret and, during rotation, the previous one (X-SauBit-Signature-Previous).
export function verifySauBitWebhook(req, secrets) {
const timestamp = req.headers["x-saubit-timestamp"];
const rawBody = req.rawBody; // raw body (bytes/string)
const msg = `${timestamp}.${rawBody}`;
const candidates = [
req.headers["x-saubit-signature"],
req.headers["x-saubit-signature-previous"],
].filter(Boolean);

for (const secret of [].concat(secrets)) {
const expected = `sha256=${crypto.createHmac("sha256", secret).update(msg).digest("hex")}`;
const expectedBuf = Buffer.from(expected);
for (const sig of candidates) {
const sigBuf = Buffer.from(String(sig));
// timingSafeEqual throws if lengths differ — check first.
if (sigBuf.length === expectedBuf.length && crypto.timingSafeEqual(sigBuf, expectedBuf)) {
return true;
}
}
}
return false;
}

Verification example (Python)

import hmac
import hashlib

def verify_saubit(signature: str, timestamp: str, raw_body: bytes, secret: str) -> bool:
msg = timestamp.encode() + b"." + raw_body
digest = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
expected = f"sha256={digest}"
return hmac.compare_digest(signature, expected)

Event envelope

Each delivery carries a stable envelope:

{
"event_id": "evt_uuid",
"event": "analysis.completed",
"schema_version": "1.0",
"created_at": "2026-06-30T12:00:00Z",
"delivery_id": "dlv_uuid",
"attempt": 1,
"data": { "...": "..." }
}
  • At-least-once delivery: the same event_id may arrive duplicated — dedupe by event_id. delivery_id/attempt identify the attempt.
  • Events may arrive out of order — do not assume ordering; use created_at.

Delivery, retry and replay

Each event is delivered with up to 3 attempts (retry on network error or non-2xx response, with backoff). Every delivery — success or failure — is logged (event, URL, HTTP status, attempt count, last error). Respond 2xx to acknowledge receipt.

Manual replay of a past delivery, listing the delivery log and a test send (webhook.test) are admin operations, in the Admin Swagger (/docs/admin).

Secret rotation (two valid secrets)

When the secret is rotated (admin), the previous one stays valid during the window: each delivery includes X-SauBit-Signature (current) and X-SauBit-Signature-Previous (previous). Validate against either — the example above already does — to rotate without dropping deliveries.