Webhooks

Recevez les événements de vos sessions dans votre backend : chaque livraison est signée HMAC-SHA256, réessayée avec backoff exponentiel et porte un identifiant unique pour que votre récepteur soit idempotent.

Quand utiliser les webhooks

Pour réagir à ce qui se passe dans la salle sans sonder : la plateforme vous appelle quand une session démarre ou se termine, qu'une question ferme ou que ses résultats sont consolidés. La livraison est au-moins-une-fois — elle peut se répéter, jamais se perdre en silence — d'où un récepteur idempotent.

Catalogue d'événements

Servi par l'API sur /public/v1/limits — cette page n'en garde pas de copie.

  • presentation.import.completed
  • presentation.import.failed
  • question.activated
  • question.closed
  • question.results.finalized
  • report.ready
  • session.ended
  • session.paused
  • session.resumed
  • session.started

En-têtes de chaque livraison

  • X-Votinova-EventLe type d'événement, tel qu'au catalogue.
  • X-Votinova-Delivery-IdUnique par livraison — votre clé de déduplication.
  • X-Votinova-TimestampÉpoque en secondes ; signée, donc une livraison rejouée ne peut pas être re-datée.
  • X-Votinova-SignatureHMAC-SHA256 en hexadécimal, sans préfixe.

Le corps est un JSON compact avec l'événement et tous les identifiants concernés — voici, octet pour octet, celui de la livraison signée de l'exemple :

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

Vérifier la signature

Chaque livraison est signée en HMAC-SHA256 sur l'horodatage, un point et le corps, avec le secret de l'endpoint. Comparez le résultat à l'en-tête X-Votinova-Signature.

  • Signez le corps BRUT. Le parser puis le re-sérialiser change les espaces et la signature ne correspond plus.
  • Comparez en temps constant, pas avec ==.
Vérifié contre une livraison réelle
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

Réessais

Livraisons de webhook : jusqu'à 8 tentatives, avec des attentes croissantes de 20, 41, 80, 185, 333, 669, 1413 secondes. Épuisées, la livraison passe en file morte et peut être renvoyée à la main.

Idempotence

Utilisez X-Votinova-Delivery-Id comme clé de déduplication : une livraison peut arriver plus d'une fois et votre effet ne doit s'appliquer qu'une seule fois.

X-Votinova-Delivery-Id

Bonnes pratiques

  • Répondez 200 dès que la livraison est à l'abri et traitez dans votre file — un récepteur lent accumule les réessais.
  • Vérifiez TOUJOURS la signature avant de parser : sans elle, quiconque connaît votre URL peut inventer des événements.
  • Dédupliquez par X-Votinova-Delivery-Id : au-moins-une-fois signifie que la même livraison peut appeler deux fois.
  • Après 10 échecs consécutifs, l'endpoint se met en pause ; réactivez-le depuis l'espace ou par l'API quand votre récepteur revient.