Webhooks

Receba os eventos das suas sessões no seu backend: cada entrega vai assinada com HMAC-SHA256, repete-se com backoff exponencial e traz um id único para que o seu recetor possa ser idempotente.

Quando usar webhooks

Para reagir ao que acontece na sala sem andar a sondar: a plataforma chama-o quando uma sessão começa ou termina, quando uma pergunta fecha ou quando os seus resultados ficam definitivos. A entrega é pelo menos uma vez — pode repetir-se, nunca falha em silêncio —, e é por isso que o seu recetor tem de ser idempotente.

Catálogo de eventos

Servido pela API em /public/v1/limits — esta página não guarda 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 de cada entrega

  • X-Votinova-EventO tipo de evento, exatamente como aparece no catálogo.
  • X-Votinova-Delivery-IdÚnico por entrega — a sua chave de desduplicação.
  • X-Votinova-TimestampSegundos epoch; faz parte do que se assina, para que uma entrega repetida não possa ser redatada.
  • X-Votinova-SignatureHMAC-SHA256 em hexadecimal, sem prefixo.

O corpo é JSON compacto com o evento e todos os ids envolvidos — esta é, byte a byte, a entrega de exemplo assinada:

{"event":"session.ended","session_id":"68b2e3d4c5f607182930a5bc"}

Verificar a assinatura

Cada entrega é assinada com HMAC-SHA256 sobre o timestamp, um ponto e o corpo, com a chave do segredo do endpoint. Compare o resultado com o cabeçalho X-Votinova-Signature.

  • Assine o corpo EM BRUTO. Analisá-lo e voltar a serializá-lo muda os espaços e a assinatura deixa de coincidir.
  • Compare em tempo constante, não com ==.
Verificado contra uma entrega real
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)
= 10a86171bd92f5fcf8c28b873978e0214302cc2291c54cc016d18347f3ee7c38

Repetições

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 falhadas e pode ser reenviada à mão.

Idempotência

Use o X-Votinova-Delivery-Id como chave de desduplicação: uma entrega pode chegar mais do que uma vez e o seu efeito só se pode aplicar uma.

X-Votinova-Delivery-Id

Boas práticas

  • Responda 200 assim que a entrega esteja em segurança e processe na sua própria fila — um recetor lento acumula repetições.
  • Verifique SEMPRE a assinatura antes de analisar o corpo: sem isso, quem souber o seu URL pode inventar eventos.
  • Desduplique por X-Votinova-Delivery-Id: «pelo menos uma vez» significa que a mesma entrega pode chamar duas vezes.
  • Ao fim de 10 falhas seguidas o endpoint suspende-se sozinho; reative-o no painel ou pela API quando o seu recetor voltar.