Webhook
I webhook sono il modo in cui il tuo backend viene a sapere di cose che accadono in modo asincrono — sessioni che terminano, registrazioni che completano la post-elaborazione, catene di audit che si ancorano al TSA. Registra un URL, verifica la firma su ogni POST, effettua il dispatch in base al tipo di evento.
Tipi di evento
| Evento | Quando |
|---|---|
session.created | Nuova sessione tramite SPA o API pubblica |
session.completed | Sessione terminata (manualmente o via API) |
participant.joined | Utente sul campo (o osservatore) entrato in una sessione |
participant.left | Partecipante disconnesso |
evidence.added | Nuovo snapshot / annotazione / lavagna / clip / documento catturato |
recording.ready | Artefatti della registrazione post-elaborata disponibili |
audit.anchored | Testa della catena ancorata a uno dei TSA configurati |
signature.completed | Un PDF firmato (SES/AES/QES) è pronto per il download |
webhook.test | Attivato dal pulsante "Test fire" nell'admin della SPA, con un payload sintetico minimo |
Forma della envelope
{
"id": "evt_01HGB9...",
"type": "session.completed",
"created_at": "2026-05-23T11:42:15Z",
"org_id": 42,
"data": {
/* event-specific payload — full resource shape */
}
} L'id è stabile tra un retry e l'altro — usalo come chiave di idempotenza sul lato ricevente.
Header della firma
Ogni POST porta un header NB-Signature in formato stile Stripe:
NB-Signature: t=1716461235,v1=5257a8...3e2c1f Il valore v1 è HMAC-SHA256(secret, "{timestamp}.{body}") in esadecimale. Il valore t è un timestamp Unix al momento della firma. Una finestra di scarto di 5 minuti rifiuta i replay di payload più vecchi.
Verifica nel codice
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 dei retry
Un recapito è considerato riuscito quando il tuo endpoint restituisce un 2xx entro 30s. Qualsiasi altra cosa innesca un backoff esponenziale:
30s → 5m → 1h → 6h → 24h → DROPPED Dopo 50 fallimenti consecutivi su tutti gli eventi, l'endpoint si auto-disabilita. L'admin dell'org può riabilitarlo dall'admin della SPA una volta che il ricevente è di nuovo attivo.
Idempotenza dal tuo lato
I recapiti dei webhook possono ripetersi. Tratta event.id come chiave di deduplicazione:
-- 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. Signing secret
Ogni endpoint webhook registrato ha il proprio signing secret whsec_*. Il testo in chiaro viene mostrato esattamente una volta quando registri l'endpoint (o lo ruoti); dopodiché conserviamo una copia cifrata con Fernet e mostriamo solo i primi 6 caratteri per l'identificazione.
Ruota da SPA → Admin → Webhooks → Rotate secret. I receiver esistenti rifiuteranno gli eventi firmati finché non vengono aggiornati con il nuovo secret — pianifica la rotazione con una finestra di deploy.
Test fire
Ogni endpoint registrato ha un pulsante "Test fire" nell'admin SPA che invia un envelope webhook.test così puoi verificare il tuo receiver prima di andare in produzione. L'envelope di test ha la stessa forma di un evento reale ma con un payload sintetico contrassegnato con "test": true in data.
Log dei recapiti
SPA → Admin → Webhooks mostra gli ultimi 100 recapiti per endpoint con stato HTTP, tentativi, ultimo corpo della risposta (troncato) e timestamp. Filtra per stato / endpoint per fare il debug dei problemi del receiver.