AO VIVO · AUDIT CHAIN · UE
SISTEMA · 99,99% DISPONIBILIDADE
v 1.0 ↗ FEITO NA UE

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

EventoQuando
session.createdNova sessão via SPA ou API pública
session.completedSessão terminada (manualmente ou via API)
participant.joinedUtilizador no terreno (ou observador) entrou numa sessão
participant.leftParticipante desligou-se
evidence.addedNova captura / anotação / quadro branco / clip / documento capturado
recording.readyArtefactos de gravação pós-processados disponíveis
audit.anchoredTopo da cadeia ancorado numa das TSAs configuradas
signature.completedUm PDF assinado (SES/AES/QES) está pronto para transferência
webhook.testDisparado 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.