June 19, 2026 · 10 min read
webhooks के ज़रिए live poll results को हमारे internal dashboard में पाइप करना
PollsLive webhooks इस्तेमाल करने की एक डेवलपर गाइड: HMAC signature verify करें, real time में vote.created और session events पर react करें, और results endpoint को poll करते हुए fallback रखें।
हमारे ऑफ़िस में दीवार पर लगा एक dashboard है जो दिखाता है कि पूरे product में क्या हो रहा है। मैं company all-hands के दौरान उस पर live poll results चाहता था - बिना timer पर API को बार-बार ठोके। PollsLive webhooks ने इसे कुछ घंटों का काम बना दिया। यहाँ पूरा integration है, signature verification समेत, क्योंकि एक unverified webhook पर कभी भरोसा नहीं करना चाहिए।
Tip
इस guide को follow करने के लिए technical होना ज़रूरी नहीं। हर step PollsLive में साधारण buttons use करता है - कोई coding नहीं, आपकी audience के लिए कोई app install नहीं।
जिन events को आप subscribe कर सकते हैं
आप Studio → Developers में एक endpoint URL और events का एक सेट register करते हैं। एक live dashboard के लिए जिन की मुझे परवाह है:
- `session.started` / `session.ended` - live tile दिखाएँ या हटाएँ।
- `response.created` - किसी प्रतिभागी ने एक live session में slide का जवाब दिया (high-frequency वाला)।
- `vote.created` - किसी async poll पर एक नया vote।
- `poll.published` / `poll.closed` - lifecycle, archiving के लिए काम का।
हर delivery एक JSON `POST` है जिसका envelope एक जैसा होता है: एक `event` नाम, एक `createdAt` timestamp, और एक `data` object।
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"
}
}पहले signature verify करें - हमेशा
हर request `X-PollsLive-Signature: t=<unix>,v1=<hex>` ले जाती है। `v1` value आपके endpoint के signing secret का इस्तेमाल करते हुए `<t>.<rawBody>` का एक HMAC-SHA256 है। आपको इसे raw request body पर compute करना चाहिए (parsed JSON पर नहीं), constant time में compare करना चाहिए, और जहाँ timestamp replays रोकने के लिए बहुत पुराना हो उसे reject करना चाहिए।
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);
}Express में एक ही पेच: आपको raw bytes चाहिए, इसलिए उस route के लिए verify-handler को `express.json()` के बजाय `express.raw()` के साथ register करें (या buffer कैप्चर करें)। फिर जल्दी acknowledge करें - तुरंत `2xx` लौटाएँ और असली काम asynchronously करें, वरना PollsLive एक धीमे response को failed delivery मान लेगा।
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);events को एक live tile में बदलना
मैं जानबूझकर webhook payload में vote *counts* नहीं डालता - event एक इशारा है जो कहता है "कुछ बदला है", और मेरा worker `response.created` events की झड़ी को (all-hands के दौरान हर जवाब के लिए एक बहुत हो सकता है) एक ही refresh में debounce कर देता है जो `GET /polls/{id}/results` को कॉल करता है। इससे एक busy poll के साथ भी मैं 120 requests/min की लिमिट से अच्छा-खासा नीचे रहता हूँ।
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
}दीवार पर आख़िरकार नीचे दिखाया गया live tile दिखता है - webhook इशारों से चलता हुआ, polling loop से नहीं। पिछले all-hands के दौरान यह हर vote के एक सेकंड के भीतर अपडेट हुआ और rate limit को कभी नहीं छुआ:
इस साल का hack week हमें कहाँ रखना चाहिए?
हमेशा एक fallback रखें
webhooks में देरी हो सकती है या वे ड्रॉप हो सकते हैं - नेटवर्क आख़िर नेटवर्क हैं। तो वही `refresh()` function session live रहने के दौरान एक धीमे सेफ़्टी-नेट अंतराल (हर 30s) पर भी चलता है। पूरी ऐहतियात: तुरंत अपडेट के लिए webhooks, और एक हल्का poll ताकि tile कभी ज़्यादा देर ग़लत न रहे। अगर सिर्फ़ एक चीज़ करें, तो verification करें; अगर दो करें, तो fallback जोड़ें।
webhook को 'जाकर देखो' संकेत मानें, सच का स्रोत नहीं। इसे verify करें, debounce करें, फिर results endpoint पढ़ें - और आपको एक real-time dashboard मिलता है जो टिकाऊ भी है।
events की पूरी सूची और signature फ़ॉर्मैट developer guide और इंटरैक्टिव API reference में हैं। अगर आप *बनाने* वाला हिस्सा भी ऑटोमेट कर रहे हैं, तो मेरे साथी का API से retros चलाने वाला लेख साथी टुकड़ा है।
Tip
Webhook payloads mein full result snapshot hota hai - store karein, timer par API poll na karein.



Studio mein results poll results padhna aur export se dekhein. Dekhein /integrations.
PollsLive पर बनाएँ - अपने ही कोड से polls बनाएँ, live sessions चलाएँ, results खींचें, और webhooks पाएँ।
developer docs पढ़ें