Webhooks
Webhooks permitem integrar a SauBit com prontuários (HIS/EMR) e automações internas do hospital.
Quando configurado, a SauBit envia eventos via POST para um endpoint do hospital (ex: https://hospital.exemplo.com/webhooks/events).
Como configurar
O cadastro do webhook é feito no Admin (organização):
- URL do destino
- Eventos desejados (ou todos)
- Secret (gerado e exibido uma única vez)
Headers enviados
X-SauBit-Event: tipo do eventoX-SauBit-Timestamp: epoch secondsX-SauBit-Signature: assinatura HMAC
Assinatura (HMAC)
O corpo é assinado com:
HMAC_SHA256(secret, "{timestamp}.{raw_body}")
Formato do header:
X-SauBit-Signature: sha256=<hex>
Eventos
analysis.completed
Disparado quando uma análise termina (check ou recheck).
Payload:
{
"event": "analysis.completed",
"data": {
"analysis_id": "uuid",
"endpoint": "/interactions/check",
"cached": false,
"recheck_of": "uuid-opcional"
}
}
chat.message
Disparado quando o hospital envia uma mensagem no chat.
Payload:
{
"event": "chat.message",
"data": {
"session_id": "abc123",
"analysis_id": "uuid-opcional"
}
}
Exemplo de verificação (Node.js)
import crypto from "crypto";
// Aceita o secret atual e, durante a rotação, o anterior (X-SauBit-Signature-Previous).
export function verifySauBitWebhook(req, secrets) {
const timestamp = req.headers["x-saubit-timestamp"];
const rawBody = req.rawBody; // corpo bruto (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 lança erro se os tamanhos diferem — verifique antes.
if (sigBuf.length === expectedBuf.length && crypto.timingSafeEqual(sigBuf, expectedBuf)) {
return true;
}
}
}
return false;
}
Exemplo de verificação (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)
Envelope do evento
Cada entrega traz um envelope estável:
{
"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": { "...": "..." }
}
- Entrega at-least-once: o mesmo
event_idpode chegar duplicado — deduplique porevent_id.delivery_id/attemptidentificam a tentativa. - Eventos podem chegar fora de ordem — não assuma ordenação; use
created_at.
Entrega, retentativa e replay
Cada disparo é entregue com até 3 tentativas (retentativa em erro de rede ou resposta não-2xx, com backoff). Toda entrega — sucesso ou falha — é registrada (evento, URL, status HTTP, nº de tentativas, último erro). Responda 2xx para confirmar o recebimento.
O reenvio manual de uma entrega passada (replay), a listagem do log de entregas e o
envio de teste (webhook.test) são operações administrativas, no Swagger Admin
(/docs/admin).
Rotação de secret (dois segredos válidos)
Ao rotacionar o secret (admin), o anterior continua válido durante a janela: cada disparo
inclui X-SauBit-Signature (atual) e X-SauBit-Signature-Previous (anterior). Valide
contra qualquer um dos dois — o exemplo acima já faz isso — para trocar sem perder entregas.