Integración · Webhooks
Recibí eventos en tu servidor #
Los webhooks avisan a tu aplicación cuando cambia una sala, una sesión, una grabación o una cuota. Configurá la URL, los eventos y el secreto desde el Panel.
Elegí eventos #
La API publica la lista vigente en GET https://api.confirio.app/api/v1/webhooks/event-types. Los tipos disponibles son:
room.created,room.updated,room.endedsession.started,session.endedparticipant.joined,participant.leftrecording.started,recording.ready,recording.failed,recording.delivered,recording.delivery_failedquota.warning,quota.exceeded,quota.suspended
Consultá el endpoint para ver la descripción vigente de cada evento.
El secreto se muestra una sola vez al crear o rotar el webhook. Guardalo como variable de entorno en tu servidor. También podés configurar encabezados propios desde el Panel.
Verificá la firma antes de procesar #
Cada envío incluye X-Webhook-Timestamp (segundos Unix) y X-Webhook-Signature (HMAC-SHA256 codificado en Base64). La firma se calcula sobre el texto timestamp.cuerpo, usando el cuerpo JSON original sin volver a serializarlo.
El ejemplo del SDK toma el cuerpo crudo, comprueba que el timestamp no tenga más de cinco minutos y compara la firma en tiempo constante. Instalá Express y configurá el secreto en la terminal:
npm install express
export CONFIRIO_WEBHOOK_SECRET=TU_SECRETO_DEL_PANEL
Guardá el código como webhook.mjs y ejecutalo con node webhook.mjs:
import express from 'express';
import { createHmac, timingSafeEqual } from 'node:crypto';
const secret = process.env.CONFIRIO_WEBHOOK_SECRET;
if (!secret) {
console.error('Falta CONFIRIO_WEBHOOK_SECRET en el entorno.');
process.exit(1);
}
const toleranceSeconds = 5 * 60;
const processed = new Set(); // En producción, una tabla con event_id como clave única.
const app = express();
app.post('/webhooks/confirio', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('X-Webhook-Timestamp');
const signature = req.get('X-Webhook-Signature');
if (!timestamp || !signature || Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) {
return res.status(401).end();
}
const expected = createHmac('sha256', secret)
.update(`${timestamp}.${req.body.toString('utf8')}`)
.digest();
const received = Buffer.from(signature, 'base64');
if (received.length !== expected.length || !timingSafeEqual(received, expected)) {
return res.status(401).end();
}
const event = JSON.parse(req.body.toString('utf8'));
if (processed.has(event.event_id)) {
return res.status(200).end(); // reintento o repetición de algo ya procesado
}
processed.add(event.event_id);
if (event.event_type === 'session.ended') {
const data = event.data;
console.log(`Sala ${data.room_id}: ${data.total_participant_seconds} s, ${data.connection_count} conexiones, datos ${data.data_status}`);
} else {
console.log(`Evento ${event.event_type}`, event.data);
}
// Responder rápido: si el proceso tarda, encolalo y contestá 2xx igual.
res.status(200).end();
});
app.listen(3001, () => console.log('Receptor de webhooks en http://localhost:3001/webhooks/confirio'));
En producción, reemplazá el Set en memoria por un registro persistente con una restricción única sobre event_id, y procesá el evento una sola vez.
Identificadores y cuerpo #
Los headers incluyen X-Webhook-Event, X-Webhook-Event-Id y X-Webhook-Delivery-Id. El event_id identifica el evento y sirve para deduplicar; un reintento puede volver a entregar ese mismo evento. En session.ended, los datos incluyen segundos de participante, cantidad de conexiones y estado de los datos.
Reintentos y respuestas #
El servicio hace hasta cinco intentos en total. Reintenta errores de red, respuestas 5xx, 408 y 429; otros errores 4xx terminan esa entrega. Las esperas base que se usan con este límite son 30 segundos, 2 minutos, 10 minutos y 30 minutos, con una variación aleatoria.
Respondé con un código 2xx después de aceptar el evento. Si el procesamiento puede demorar, guardalo en una cola y contestá rápido. Revisá entregas y errores en el historial de webhooks del Panel.