Webhooks
Los webhooks son la forma en que su backend se entera de cosas que ocurren de forma asíncrona: sesiones que terminan, grabaciones que finalizan el posprocesado, cadenas de auditoría que se anclan en la TSA. Registre una URL, verifique la firma en cada POST y despache según el tipo de evento.
Tipos de evento
| Evento | Cuándo |
|---|---|
session.created | Nueva sesión mediante la SPA o la API pública |
session.completed | Sesión finalizada (manualmente o mediante la API) |
participant.joined | Un usuario de campo (u observador) se unió a una sesión |
participant.left | Un participante se desconectó |
evidence.added | Nueva captura / anotación / pizarra / clip / documento capturado |
recording.ready | Artefactos de grabación posprocesados disponibles |
audit.anchored | Cabecera de la cadena anclada en una de las TSA configuradas |
signature.completed | Un PDF firmado (SES/AES/QES) está listo para descargar |
webhook.test | Disparado por el botón «Test fire» en el admin de la SPA, con un payload sintético mínimo |
Forma del sobre
{
"id": "evt_01HGB9...",
"type": "session.completed",
"created_at": "2026-05-23T11:42:15Z",
"org_id": 42,
"data": {
/* event-specific payload — full resource shape */
}
} El id es estable entre reintentos: úselo como su clave de idempotencia en el lado del receptor.
Cabecera de firma
Cada POST lleva una cabecera NB-Signature en un formato al estilo de Stripe:
NB-Signature: t=1716461235,v1=5257a8...3e2c1f El valor v1 es HMAC-SHA256(secret, "{timestamp}.{body}") en hexadecimal. El valor t es un sello de tiempo Unix en el momento de la firma. Una ventana de tolerancia de 5 minutos rechaza el reenvío de payloads antiguos.
Verificación en 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) Comportamiento de reintento
Una entrega se considera correcta cuando su endpoint devuelve un 2xx en menos de 30 s. Cualquier otra cosa desencadena un backoff exponencial:
30s → 5m → 1h → 6h → 24h → DROPPED Tras 50 fallos consecutivos en todos los eventos, el endpoint se desactiva automáticamente. El administrador de la organización puede reactivarlo desde el admin de la SPA una vez que el receptor vuelva a estar operativo.
Idempotencia en su lado
Las entregas de webhooks pueden repetirse. Trate event.id como la clave de deduplicación:
-- 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. Secretos de firma
Cada endpoint de webhook registrado tiene su propio secreto de firma whsec_*. El texto plano se muestra exactamente una vez cuando registra el endpoint (o lo rota); a partir de entonces almacenamos una copia cifrada con Fernet y solo mostramos los primeros 6 caracteres para su identificación.
Rótelo desde SPA → Admin → Webhooks → Rotar secreto. Los receptores existentes rechazarán los eventos firmados hasta que se actualicen con el nuevo secreto: programe la rotación con una ventana de despliegue.
Disparo de prueba
Cada endpoint registrado tiene un botón «Test fire» en el admin de la SPA que envía un sobre webhook.test para que pueda verificar su receptor antes de pasar a producción. El sobre de prueba tiene la misma forma que un evento real, pero con un payload sintético etiquetado como "test": true en data.
Registro de entregas
SPA → Admin → Webhooks muestra las últimas 100 entregas por endpoint con el estado HTTP, los intentos, el último cuerpo de respuesta (truncado) y los sellos de tiempo. Filtre por estado / endpoint para depurar problemas del receptor.