Webhooks
Receba notificações HTTP assinadas quando eventos da plataforma forem concluídos — anatomia do payload, verificação HMAC e garantias de entrega.
Webhooks
Webhooks enviam eventos da plataforma para seus sistemas conforme acontecem, então você nunca precisa fazer polling de status. Cada entrega é uma requisição POST com um corpo JSON, assinada com HMAC-SHA256 para que você possa verificar que veio da VIDTREO.
Eventos Disponíveis
| Evento | Dispara quando |
|---|---|
transcriptions.transcription.completed | Transcrição por IA termina de processar um vídeo |
O catálogo de eventos está crescendo. O dashboard sempre mostra a lista atual de eventos aos quais você pode se inscrever ao criar ou editar um endpoint.
Criando um Endpoint
Endpoints de webhook são gerenciados por ambiente no dashboard:
Faça login no VIDTREO Dashboard
Abra seu ambiente e vá para Configurações → Webhooks
Clique em Adicionar Endpoint, insira sua URL https:// e selecione os eventos aos quais deseja se inscrever
Copie o segredo de assinatura (whsec_...) exibido após a criação
O segredo de assinatura é exibido uma vez. Ele é armazenado de forma criptografada e nunca pode ser recuperado novamente — se você perdê-lo, rotacione-o nas configurações do endpoint para obter um novo.
Cabeçalhos de Entrega
Toda entrega inclui cabeçalhos de identificação:
| Cabeçalho | Conteúdo |
|---|---|
X-Webhook-Signature | sha256=<hex> — HMAC-SHA256 do corpo bruto da requisição |
X-Vidtreo-Event-Id | ID único do evento — use-o para deduplicar o processamento |
X-Vidtreo-Event-Type | O nome do evento, ex.: transcriptions.transcription.completed |
X-Vidtreo-Event-Version | Versão do schema do payload |
X-Vidtreo-Webhook-Endpoint-Id | O ID do endpoint que recebe o evento |
X-Vidtreo-Webhook-Delivery-Id | Esta tentativa de entrega específica — rastreável no 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" }
]
}
}Dois campos merecem atenção especial:
data.video.userMetadata— qualquer JSON que você anexou ao vídeo no momento do upload, retornado literalmente. Anexe seus próprios identificadores (ID do candidato, ID do curso, número do ticket) e seu handler nunca precisará de uma tabela de referência para saber a qual registro o vídeo pertence.data.files[].url— URLs pré-assinadas que expiram em 5 minutos após a entrega ser gerada. Baixe o que você precisar assim que o evento chegar; busque novas URLs depois viaGET /api/v1/videos/{videoId}/transcription.
Verificando Assinaturas
Sempre verifique o cabeçalho X-Webhook-Signature antes de confiar em um payload. A assinatura é um HMAC-SHA256 do corpo bruto da requisição, codificado em hex, calculado com o segredo de assinatura do seu 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)
}Duas regras que evitam os erros clássicos:
- Calcule o HMAC sobre a string bruta do corpo, antes que qualquer parsing ou re-serialização de JSON a toque.
- Compare usando uma função de tempo constante (
timingSafeEqual), nunca===.
Um 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()
})Responda com um status 2xx rapidamente — faça o processamento pesado depois de confirmar o recebimento, ou de forma assíncrona.
Ciclo de Vida do Segredo
- Os segredos usam o prefixo
whsec_e são gerados no servidor; você também pode fornecer o seu próprio ao criar o endpoint - Armazenados de forma criptografada em repouso, exibidos em texto plano apenas na criação e na rotação
- Rotacione a qualquer momento nas configurações do endpoint no dashboard — atualize o segredo do seu handler quando fizer isso
Garantias de Entrega
- Retentativas automáticas — o processamento com falha é retentado com um atraso antes de desistir
- Captura dead-letter — eventos que esgotam as retentativas são preservados, não descartados
- Histórico completo de entregas — toda tentativa, código de resposta e payload é inspecionável por endpoint no dashboard
- Reenvio manual — reenvie (redrive) um único evento, uma única entrega, ou todas as entregas com falha em uma janela de tempo, a partir do dashboard
Use X-Vidtreo-Event-Id para tornar seu handler idempotente — um evento reenviado carrega o mesmo ID de evento que o original.