June 19, 2026 · 10 min read

Live-Umfrageergebnisse mit Webhooks in unser internes Dashboard leiten

Ein Entwicklerleitfaden zum Verarbeiten von PollsLive-Webhooks: die HMAC-Signatur verifizieren, in Echtzeit auf vote.created- und Session-Events reagieren und als Fallback den Results-Endpunkt abfragen.

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

Wir betreiben in unserem Büro ein an die Wand montiertes Dashboard, das zeigt, was über das Produkt hinweg passiert. Ich wollte während unserer Firmen-All-Hands Live-Umfrageergebnisse darauf haben - ohne die API per Timer zu bombardieren. PollsLive-Webhooks machten daraus ein paar Stunden Arbeit. Hier ist die Integration, inklusive Signaturprüfung, denn man sollte einem unverifizierten Webhook niemals trauen.

Tip

Du musst kein Technik-Profi sein, um dieser Anleitung zu folgen. Jeder Schritt nutzt normale Buttons in PollsLive - kein Code, keine App-Installation für dein Publikum.

Die Events, die du abonnieren kannst

Du registrierst eine Endpunkt-URL und eine Reihe von Events in Studio → Developers. Die, die mir für ein Live-Dashboard wichtig sind:

  • `session.started` / `session.ended` - die Live-Kachel ein- oder ausblenden.
  • `response.created` - ein Teilnehmer hat in einer Live-Session eine Slide beantwortet (das hochfrequente).
  • `vote.created` - eine neue Stimme bei einer asynchronen Umfrage.
  • `poll.published` / `poll.closed` - Lebenszyklus, praktisch fürs Archivieren.

Jede Zustellung ist ein JSON-`POST` mit derselben Hülle: einem `event`-Namen, einem `createdAt`-Zeitstempel und einem `data`-Objekt.

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"
  }
}

Verifiziere die Signatur zuerst - immer

Jede Anfrage trägt `X-PollsLive-Signature: t=<unix>,v1=<hex>`. Der `v1`-Wert ist ein HMAC-SHA256 von `<t>.<rawBody>` mit dem Signing-Secret deines Endpunkts. Du musst ihn über den rohen Request-Body berechnen (nicht das geparste JSON), in konstanter Zeit vergleichen und alles ablehnen, bei dem der Zeitstempel zu alt ist, um Replays zu blockieren.

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);
}

Der eine Stolperstein in Express: Du brauchst die rohen Bytes, also registriere den Verify-Handler mit `express.raw()` (oder fange den Buffer ab) statt mit `express.json()` für diese Route. Dann bestätige schnell - gib sofort `2xx` zurück und erledige die eigentliche Arbeit asynchron, sonst behandelt PollsLive eine langsame Antwort als fehlgeschlagene Zustellung.

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);

Aus Events eine Live-Kachel machen

Ich packe absichtlich keine Stimmen*zahlen* in die Webhook-Payload - das Event ist ein Anstoß, der sagt "etwas hat sich geändert", und mein Worker entprellt einen Schwall von `response.created`-Events (eines pro Antwort kann während einer All-Hands eine Menge sein) zu einer einzigen Aktualisierung, die `GET /polls/{id}/results` aufruft. So bleibe ich selbst bei einer belebten Umfrage gut unter dem Limit von 120 Anfragen/Min.

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
}

An der Wand erscheint am Ende die Live-Kachel unten - gespeist von Webhook-Anstößen, nicht von einer Polling-Schleife. Bei der letzten All-Hands aktualisierte sie sich innerhalb einer Sekunde nach jeder Stimme und löste das Rate-Limit nie aus:

Live poll

Wohin sollen wir die Hack-Week dieses Jahr legen?

KI-Features fürs Kernprodukt41% · 79
Tech-Schulden / Zuverlässigkeit abbauen33% · 64
Interne Tools, die uns Zeit sparen19% · 37
Wildcard - alles ist erlaubt7% · 13
Unsere Büro-Dashboard-Kachel, bei jedem Webhook-Anstoß aus GET /polls/{id}/results aktualisiert.

Hab immer einen Fallback

Webhooks können verzögert oder verloren gehen - Netzwerke sind Netzwerke. Also läuft dieselbe `refresh()`-Funktion auf einem langsamen Sicherheitsnetz-Intervall (alle 30 Sek.), solange eine Session live ist. Gürtel und Hosenträger: Webhooks für sofortige Updates, ein sanftes Polling, damit die Kachel nie lange falsch ist. Wenn du nur eines machst, mach die Verifizierung; wenn du zwei machst, ergänze den Fallback.

Behandle den Webhook als 'schau mal nach'-Signal, nicht als Quelle der Wahrheit. Verifiziere ihn, entpreller ihn, lies dann den Results-Endpunkt - und du bekommst ein Echtzeit-Dashboard, das obendrein robust ist.

Die vollständige Liste der Events und das Signaturformat stehen im Entwicklerleitfaden und der interaktiven API-Referenz. Wenn du auch die *Erstellungs*-Seite automatisierst, ist der Bericht meines Kollegen über Retros aus der API das passende Begleitstück.

Tip

Webhook-Payloads enthalten den vollen Ergebnis-Snapshot - speichere sie, polle die API nicht per 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.

Lies Ergebnisse im Studio mit Umfrageergebnisse lesen und exportieren. Siehe /integrations.

Entwickle auf PollsLive - erstelle Umfragen, führe Live-Sessions durch, rufe Ergebnisse ab und empfange Webhooks aus deinem eigenen Code.

Lies die Entwicklerdokumentation