Webhooks
Os webhooks são a forma de o seu backend tomar conhecimento de coisas que acontecem de forma assíncrona — sessões a terminar, gravações a concluir o pós-processamento, cadeias de auditoria a ancorar na TSA. Registe um URL, verifique a assinatura em cada POST, despache por tipo de evento.
Tipos de evento
| Evento | Quando |
|---|---|
session.created | Nova sessão via SPA ou API pública |
session.completed | Sessão terminada (manualmente ou via API) |
participant.joined | Utilizador no terreno (ou observador) entrou numa sessão |
participant.left | Participante desligou-se |
evidence.added | Nova captura / anotação / quadro branco / clip / documento capturado |
recording.ready | Artefactos de gravação pós-processados disponíveis |
audit.anchored | Topo da cadeia ancorado numa das TSAs configuradas |
signature.completed | Um PDF assinado (SES/AES/QES) está pronto para transferência |
webhook.test | Disparado pelo botão "Disparo de teste" na administração da SPA, com um payload sintético mínimo |
Forma do envelope
{
"id": "evt_01HGB9...",
"type": "session.completed",
"created_at": "2026-05-23T11:42:15Z",
"org_id": 42,
"data": {
/* event-specific payload — full resource shape */
}
} O id é estável entre retentativas — use-o como chave de idempotência do lado do recetor.
Cabeçalho de assinatura
Cada POST transporta um cabeçalho NB-Signature num formato ao estilo Stripe:
NB-Signature: t=1716461235,v1=5257a8...3e2c1f O valor v1 é HMAC-SHA256(secret, "{timestamp}.{body}") em hex. O valor t é um timestamp Unix no momento da assinatura. Uma janela de desvio de 5 minutos rejeita replays de payloads mais antigos.
Verificação em código
Node
import { NexBasira, InvalidSignatureError } from "@nexbasira/node";
const nb = new NexBasira({ apiKey: "...", apiSecret: "..." });
// Express handler — important: use express.raw() so the body is the
// untouched bytes the signature was computed over.
app.post("/nb-webhook", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.header("NB-Signature")!;
const secret = process.env.NB_WEBHOOK_SECRET!;
let event;
try {
event = nb.webhooks.constructEvent(req.body, sig, secret);
} catch (err) {
if (err instanceof InvalidSignatureError) {
return res.status(401).send("bad signature");
}
throw err;
}
// event is now type-narrowed by event.type
return handleEvent(event, res);
}); Python
from nexbasira import NexBasira, InvalidSignatureError
nb = NexBasira(api_key="...", api_secret="...")
# Flask handler — get the raw body, not request.get_json()
@app.post("/nb-webhook")
def webhook():
sig = request.headers["NB-Signature"]
secret = os.environ["NB_WEBHOOK_SECRET"]
try:
event = nb.webhooks.construct_event(request.data, sig, secret)
except InvalidSignatureError:
return ("bad signature", 401)
return handle_event(event) Comportamento de retentativa
Uma entrega é considerada bem-sucedida quando o seu endpoint devolve um 2xx em menos de 30s. Qualquer outra coisa aciona backoff exponencial:
30s → 5m → 1h → 6h → 24h → DROPPED Após 50 falhas consecutivas em todos os eventos, o endpoint desativa-se automaticamente. O administrador da organização pode reativá-lo a partir da administração da SPA assim que o recetor voltar a funcionar.
Idempotência do seu lado
As entregas de webhook podem repetir-se. Trate o event.id como chave de desduplicação:
-- Postgres example
INSERT INTO webhook_deliveries (event_id, processed_at)
VALUES ($1, NOW())
ON CONFLICT (event_id) DO NOTHING
RETURNING event_id;
-- If RETURNING is empty, this is a duplicate — skip the side-effect. Segredos de assinatura
Cada endpoint de webhook registado tem o seu próprio segredo de assinatura whsec_*. O texto simples é mostrado uma única vez quando regista o endpoint (ou o roda); depois disso guardamos uma cópia cifrada com Fernet e mostramos apenas os primeiros 6 caracteres para identificação.
Rode a partir de SPA → Admin → Webhooks → Rodar segredo. Os recetores existentes rejeitarão os eventos assinados até serem atualizados com o novo segredo — agende a rotação com uma janela de deploy.
Disparo de teste
Cada endpoint registado tem um botão "Disparo de teste" na administração da SPA que envia um envelope webhook.test para que possa verificar o seu recetor antes de entrar em produção. O envelope de teste tem a mesma forma de um evento real, mas com um payload sintético etiquetado "test": true em data.
Registo de entregas
SPA → Admin → Webhooks mostra as últimas 100 entregas por endpoint com o estado HTTP, tentativas, último corpo de resposta (truncado) e timestamps. Filtre por estado / endpoint para depurar problemas do recetor.