Webhooks
Receba no seu backend os eventos das suas sessões: cada entrega é assinada com HMAC-SHA256, repetida com backoff exponencial e traz um id único para o seu receptor ser idempotente.
Quando usar webhooks
Para reagir ao que acontece na sala sem ficar consultando: a plataforma chama você quando uma sessão começa ou termina, quando uma pergunta fecha ou quando os resultados dela são finalizados. A entrega é at-least-once — pode repetir, nunca falha em silêncio — e é por isso que o seu receptor precisa ser idempotente.
Catálogo de eventos
Servido pela API em /public/v1/limits — esta página não mantém cópia.
- presentation.import.completed
- presentation.import.failed
- question.activated
- question.closed
- question.results.finalized
- report.ready
- session.ended
- session.paused
- session.resumed
- session.started
Cabeçalhos em cada entrega
X-Votinova-EventO tipo do evento, exatamente como aparece no catálogo.X-Votinova-Delivery-IdÚnico por entrega — a sua chave de deduplicação.X-Votinova-TimestampSegundos desde a epoch; faz parte do que é assinado, então uma entrega repetida não pode ganhar outra data.X-Votinova-SignatureHMAC-SHA256 em hexadecimal, sem prefixo.
O corpo é JSON compacto com o evento e todos os ids envolvidos — este é, byte a byte, o exemplo de entrega assinado:
{"event":"session.ended","session_id":"68b2e3d4c5f607182930a5bc"}Verificando a assinatura
Cada entrega é assinada com HMAC-SHA256 sobre o timestamp, um ponto e o corpo, usando o segredo do endpoint como chave. Compare o resultado com o cabeçalho X-Votinova-Signature.
- Assine o corpo BRUTO. Fazer o parse e serializar de novo muda os espaços em branco, e a assinatura para de bater.
- Compare em tempo constante, não com ==.
const crypto = require("node:crypto");
function verify(headers, rawBody, secret) {
const timestamp = headers["x-votinova-timestamp"];
const signature = headers["x-votinova-signature"];
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}secret whsec_3f8a1c92d4e5b6a7
timestamp 1785321600
body {"event":"session.ended","session_id":"68b2e3d4c5f607182930a5bc"}
HMAC-SHA256(secret, timestamp + "." + body)
= 10a86171bd92f5fcf8c28b873978e0214302cc2291c54cc016d18347f3ee7c38Novas tentativas
Entregas de webhook: até 8 tentativas, com esperas crescentes de 21, 45, 95, 171, 369, 732, 1480 segundos. Esgotadas as tentativas, a entrega vai para a fila de falhas e pode ser reenviada à mão.
Idempotência
Use o X-Votinova-Delivery-Id como chave de deduplicação: uma entrega pode chegar mais de uma vez e o seu efeito tem que valer só uma.
X-Votinova-Delivery-Id
Boas práticas
- Responda 200 assim que a entrega estiver segura e processe na sua própria fila — um receptor lento acumula novas tentativas.
- SEMPRE verifique a assinatura antes de fazer o parse: sem isso, qualquer um que saiba a sua URL pode inventar eventos.
- Deduplique pelo X-Votinova-Delivery-Id: at-least-once significa que a mesma entrega pode chamar duas vezes.
- Depois de 10 falhas seguidas o endpoint se pausa sozinho; reative pelo painel ou pela API quando o seu receptor voltar.