Ballot data changes unpredictably. Webhooks turn 'poll every endpoint every hour just in case' into one HTTP POST per actual change — HMAC-signed, replay-protected, exponentially retried.
POST /your/webhook HTTP/1.1
Content-Type: application/json
PoliAPI-Event: candidate.filed
PoliAPI-Delivery: 7f3c9e2a-... // unique per attempt (idempotency key)
PoliAPI-Signature: t=1739564800,v1=... // HMAC-SHA256 of timestamp + body
{
"event": "candidate.filed",
"occurred_at": "2026-02-14T18:22:09Z",
"jurisdiction": "ocd-division/.../place:asheville",
"candidate": { "id": "cnd_9k2", "name": "Jordan Reyes" },
"source_url": "https://buncombecounty.gov/elections/..."
}import { createHmac, timingSafeEqual } from 'crypto';
const sig = req.headers['poliapi-signature'];
const [tPart, v1Part] = sig.split(',');
const t = tPart.split('=')[1];
const v1 = v1Part.split('=')[1];
const expected = createHmac('sha256', WEBHOOK_SECRET)
.update(`${t}.${rawBody}`)
.digest('hex');
if (!timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) {
return res.status(401).send('bad sig');
}
if (Date.now() / 1000 - Number(t) > 300) {
return res.status(401).send('replay');
}