June 19, 2026 · 10 min read

Faire remonter les résultats de sondage live dans notre dashboard interne avec des webhooks

Le guide d'un développeur pour consommer les webhooks PollsLive : vérifier la signature HMAC, réagir en temps réel aux événements vote.created et session, et basculer sur un polling de l'endpoint de résultats.

By Tom Becker · Full-stack developer·DevelopersHow-toTeams & meetings

On a un dashboard mural au bureau qui montre ce qui se passe à travers le produit. Je voulais y afficher les résultats de sondage en live pendant les all-hands de l'entreprise - sans marteler l'API sur une minuterie. Les webhooks PollsLive ont rendu ça affaire de quelques heures. Voici l'intégration, vérification de signature comprise, parce qu'il ne faut jamais faire confiance à un webhook non vérifié.

Tip

You do not need to be technical to follow this guide. Every step uses plain buttons in PollsLive - no coding, no app install for your audience.

Les événements auxquels vous pouvez vous abonner

Vous enregistrez une URL d'endpoint et un ensemble d'événements dans Studio → Developers. Ceux qui m'intéressent pour un dashboard live :

  • `session.started` / `session.ended` - afficher ou démonter la tuile live.
  • `response.created` - un participant a répondu à une slide dans une session live (celui à haute fréquence).
  • `vote.created` - un nouveau vote sur un sondage asynchrone.
  • `poll.published` / `poll.closed` - cycle de vie, pratique pour l'archivage.

Chaque livraison est un `POST` JSON avec la même enveloppe : un nom d'`event`, un timestamp `createdAt`, et un objet `data`.

Example delivery - response.created
POST /webhooks/pollslive  (from PollsLive → your server)
X-PollsLive-Signature: t=1750320000,v1=4f9a…c2

{
  "event": "response.created",
  "createdAt": "2026-06-19T16:00:00.000Z",
  "data": {
    "pollId": "poll_9aZ2kP",
    "sessionId": "ses_4dF1",
    "slideId": "fix",
    "kind": "multiple_choice"
  }
}

Vérifiez la signature en premier - toujours

Chaque requête porte `X-PollsLive-Signature: t=<unix>,v1=<hex>`. La valeur `v1` est un HMAC-SHA256 de `<t>.<rawBody>` utilisant le secret de signature de votre endpoint. Vous devez le calculer sur le corps de requête brut (pas le JSON parsé), comparer en temps constant, et rejeter tout ce dont le timestamp est trop ancien pour bloquer les rejeux.

verify.mjs
import crypto from "node:crypto";

const SECRET = process.env.POLLSLIVE_WEBHOOK_SECRET; // from Studio → Developers

export function verifyPollsLive(rawBody, header, toleranceSec = 300) {
  // header looks like: "t=1750320000,v1=4f9a…c2"
  const parts = Object.fromEntries(
    header.split(",").map((kv) => kv.split("=")),
  );
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;

  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(`${t}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 ?? "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Le seul piège avec Express : il vous faut les octets bruts, donc enregistrez le handler de vérification avec `express.raw()` (ou capturez le buffer) au lieu d'`express.json()` pour cette route. Ensuite, accusez réception rapidement - renvoyez `2xx` immédiatement et faites le vrai travail de façon asynchrone, sinon PollsLive traitera une réponse lente comme une livraison échouée.

server.mjs
import express from "express";
import { verifyPollsLive } from "./verify.mjs";

const app = express();

app.post(
  "/webhooks/pollslive",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const sig = req.get("X-PollsLive-Signature") ?? "";
    if (!verifyPollsLive(req.body.toString("utf8"), sig)) {
      return res.status(400).send("bad signature");
    }
    const evt = JSON.parse(req.body.toString("utf8"));

    // ACK first, then process out of band.
    res.sendStatus(200);
    queue.add(evt); // e.g. push to a worker / pub-sub / websocket fan-out
  },
);

app.listen(3000);

Transformer les événements en tuile live

Je ne mets volontairement pas les *décomptes* de votes dans le payload du webhook - l'événement est un coup de coude qui dit "quelque chose a changé", et mon worker regroupe une rafale d'événements `response.created` (un par réponse, ça peut faire beaucoup pendant un all-hands) en un seul rafraîchissement qui appelle `GET /polls/{id}/results`. Ça me garde bien en dessous de la limite de 120 requêtes/min même avec un sondage très actif.

worker.mjs - debounce + fetch results
const API = process.env.POLLSLIVE_API;
const auth = { Authorization: `Bearer ${process.env.POLLSLIVE_KEY}` };
const pending = new Map(); // pollId → timer

function onEvent(evt) {
  if (evt.event === "session.ended") return teardownTile(evt.data.pollId);
  const id = evt.data.pollId;
  clearTimeout(pending.get(id));
  pending.set(id, setTimeout(() => refresh(id), 750)); // debounce bursts
}

async function refresh(pollId) {
  const res = await fetch(`${API}/polls/${pollId}/results`, { headers: auth });
  if (res.status === 429) {                 // backed-off retry on rate limit
    return setTimeout(() => refresh(pollId), 2000);
  }
  const { data } = await res.json();
  pushToDashboard(pollId, data.voteCounts); // websocket → the wall display
}

Le mur finit par afficher la tuile live ci-dessous - alimentée par les coups de coude des webhooks, pas par une boucle de polling. Lors du dernier all-hands, elle se mettait à jour en moins d'une seconde après chaque vote et n'a jamais fait sauter la limite de débit :

Live poll

Où devrait-on placer la hack week de cette année ?

Fonctionnalités IA pour le produit principal41% · 79
Rembourser la dette technique / fiabilité33% · 64
Outils internes qui nous font gagner du temps19% · 37
Joker - tout est permis7% · 13
La tuile de notre dashboard de bureau, rafraîchie depuis GET /polls/{id}/results à chaque coup de coude de webhook.

Ayez toujours un plan B

Les webhooks peuvent être retardés ou perdus - les réseaux restent des réseaux. Du coup, la même fonction `refresh()` tourne sur un intervalle de filet de sécurité tranquille (toutes les 30 s) tant qu'une session est live. Ceinture et bretelles : les webhooks pour les mises à jour instantanées, un polling doux pour que la tuile ne soit jamais fausse bien longtemps. Si vous ne faites qu'une chose, faites la vérification ; si vous en faites deux, ajoutez le plan B.

Traitez le webhook comme un signal « va voir », pas comme la source de vérité. Vérifiez-le, regroupez-le, puis lisez l'endpoint de résultats - et vous obtenez un dashboard temps réel qui est aussi résilient.

La liste complète des événements et le format de signature vivent dans le guide développeur et la référence d'API interactive. Si vous automatisez aussi le côté *création*, l'article de ma collègue sur comment lancer des rétros depuis l'API en est le pendant.

Tip

Webhook payloads include the full result snapshot - store them, do not poll the API on a timer.

Results panel with stat tiles, breakdown by question, and export buttons.
The Results tab shows every answer and export options.
Session recap with final results, share buttons, and export options.
After a live session, recap shows final results and export options.
The PollsLive integrations page with PowerPoint, Google Slides, Keynote, Zoom, Microsoft Teams, Webex, and Google Meet companions.
PollsLive runs alongside your slides and meeting tools - your audience joins by QR or PIN.

Read results in Studio with read and export poll results. See /integrations.

Construisez sur PollsLive - créez des sondages, pilotez des sessions live, récupérez des résultats et recevez des webhooks depuis votre propre code.

Lire la doc développeur