Webhooks
Recibe notificaciones HTTP firmadas cuando se completan eventos de la plataforma — anatomía del payload, verificación HMAC y garantías de entrega.
Webhooks
Los webhooks envían eventos de la plataforma a tus sistemas en el momento en que ocurren, así nunca necesitas hacer polling para conocer el estado. Cada entrega es una solicitud POST con un cuerpo JSON, firmada con HMAC-SHA256 para que puedas verificar que proviene de VIDTREO.
Eventos Disponibles
| Evento | Se dispara cuando |
|---|---|
transcriptions.transcription.completed | Transcripción con IA termina de procesar un video |
El catálogo de eventos sigue creciendo. El dashboard siempre muestra la lista actual de eventos a los que puedes suscribirte al crear o editar un endpoint.
Crear un Endpoint
Los endpoints de webhook se gestionan por entorno en el dashboard:
Inicia sesión en el Dashboard de VIDTREO
Abre tu entorno y ve a Configuración → Webhooks
Haz clic en Agregar Endpoint, ingresa tu URL https:// y selecciona los eventos a los que quieres suscribirte
Copia el secreto de firma (whsec_...) que se muestra después de la creación
El secreto de firma se muestra una sola vez. Se almacena cifrado y nunca puede recuperarse de nuevo: si lo pierdes, rótalo desde la configuración del endpoint para obtener uno nuevo.
Encabezados de Entrega
Cada entrega incluye encabezados de identificación:
| Encabezado | Contenido |
|---|---|
X-Webhook-Signature | sha256=<hex> — HMAC-SHA256 del cuerpo de la solicitud sin procesar |
X-Vidtreo-Event-Id | ID único del evento: úsalo para deduplicar el procesamiento |
X-Vidtreo-Event-Type | El nombre del evento, por ejemplo transcriptions.transcription.completed |
X-Vidtreo-Event-Version | Versión del esquema del payload |
X-Vidtreo-Webhook-Endpoint-Id | El ID del endpoint receptor |
X-Vidtreo-Webhook-Delivery-Id | Este intento de entrega específico: rastreable en el dashboard |
Payload: transcriptions.transcription.completed
{
"id": "9f4c1a2e-8d3b-4f6a-b1c9-2e7d5a8f0c3b",
"event": "transcriptions.transcription.completed",
"eventVersion": "2",
"occurredAt": "2026-08-03T14:22:07.000Z",
"data": {
"transcription": {
"id": "b7e2...",
"videoId": "vid_...",
"environmentId": "env_...",
"language": "en",
"wordCount": 812,
"duration": 294.5
},
"video": {
"id": "vid_...",
"publicId": "pub_...",
"filename": "interview-final.mp4",
"duration": 295,
"status": "COMPLETED",
"metadata": { },
"userMetadata": {
"candidateId": "cand_8231",
"jobId": "job_114"
}
},
"files": [
{ "format": "vtt", "url": "https://...", "expiresAt": "...", "contentType": "text/vtt" },
{ "format": "json", "url": "https://...", "expiresAt": "...", "contentType": "application/json" },
{ "format": "txt", "url": "https://...", "expiresAt": "...", "contentType": "text/plain" }
]
}
}Dos campos merecen atención especial:
data.video.userMetadata— cualquier JSON que hayas adjuntado al video en el momento de subirlo, devuelto tal cual. Adjunta tus propios identificadores (ID de candidato, ID de curso, número de ticket) y tu handler nunca necesitará una tabla de referencia para saber a qué registro pertenece el video.data.files[].url— URLs prefirmadas que expiran 5 minutos después de generarse la entrega. Descarga lo que necesites en cuanto llegue el evento; obtén URLs nuevas más adelante medianteGET /api/v1/videos/{videoId}/transcription.
Verificar Firmas
Verifica siempre el encabezado X-Webhook-Signature antes de confiar en un payload. La firma es un HMAC-SHA256 del cuerpo de la solicitud sin procesar, codificado en hexadecimal, calculado con el secreto de firma de tu endpoint:
import { createHmac, timingSafeEqual } from 'node:crypto'
function isFromVidtreo(rawBody: string, signature: string, secret: string) {
const expected = `sha256=${createHmac('sha256', secret)
.update(rawBody)
.digest('hex')}`
const a = Buffer.from(signature)
const b = Buffer.from(expected)
return a.length === b.length && timingSafeEqual(a, b)
}Dos reglas que evitan los errores clásicos:
- Calcula el HMAC sobre el string del cuerpo sin procesar, antes de que cualquier parseo o re-serialización de JSON lo modifique.
- Compara con una función de tiempo constante (
timingSafeEqual), nunca con===.
Un handler mínimo:
app.post('/webhooks/vidtreo', async (req) => {
if (!isFromVidtreo(req.rawBody, req.headers['x-webhook-signature'], process.env.VIDTREO_WEBHOOK_SECRET)) {
return res.status(401).end()
}
const { video, files } = req.body.data
const { candidateId } = video.userMetadata
const txt = files.find(f => f.format === 'txt')
const transcript = await fetch(txt.url).then(r => r.text())
await processTranscript(candidateId, transcript)
return res.status(200).end()
})Responde rápido con un estado 2xx: realiza el procesamiento pesado después de confirmar la recepción, o de forma asíncrona.
Ciclo de Vida del Secreto
- Los secretos usan el prefijo
whsec_y se generan del lado del servidor; también puedes proporcionar el tuyo al crear el endpoint - Se almacenan cifrados en reposo y se muestran en texto plano solo durante la creación y la rotación
- Puedes rotarlos en cualquier momento desde la configuración del endpoint en el dashboard: actualiza el secreto de tu handler cuando lo hagas
Garantías de Entrega
- Reintentos automáticos — el procesamiento fallido se reintenta con un retraso antes de desistir
- Captura dead-letter — los eventos que agotan los reintentos se conservan, no se descartan
- Historial completo de entregas — cada intento, código de respuesta y payload se puede inspeccionar por endpoint en el dashboard
- Reenvío manual — puedes volver a enviar un solo evento, una sola entrega o todas las entregas fallidas dentro de una ventana de tiempo, desde el dashboard
Usa X-Vidtreo-Event-Id para que tu handler sea idempotente: un evento reenviado lleva el mismo ID de evento que el original.