Webhooks
Add an endpoint under Developers in the portal, pick the events you want, and we POST JSON to it when they happen.
Events
question.reservedquestion.completedquestion.underfilledquestion.cancelledquestion.refusedcredits.purchasedcredits.lowcredits.expiringdispute.resolvedconnection.cap_warning
Payload
{
"id": "evt_3kd9",
"type": "question.completed",
"createdAt": "2026-09-24T10:12:00Z",
"data": {
"questionId": "q_8k2f",
"status": "complete",
"summary": {
"winner": "Menu B",
"margin": 36,
"confidence": "high"
},
"creditsSpent": 600
}
}Verifying signatures
Every delivery carries x-50heads-signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of <t>.<raw body> with your endpoint's secret. Check it against the raw bytes before parsing, and reject timestamps more than five minutes old.
import { createHmac, timingSafeEqual } from "node:crypto";
// Header: x-50heads-signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
export function verify(rawBody: string, header: string, secret: string, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=") as [string, string]));
const t = Number(parts.t);
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const given = Buffer.from(parts.v1 ?? "", "hex");
const wanted = Buffer.from(expected, "hex");
return given.length === wanted.length && timingSafeEqual(given, wanted);
}Retries and replay
Answer 2xx within ten seconds. Anything else is retried with backoff, eight times over 24 hours. Deliveries can arrive more than once or out of order: use the event id to ignore repeats. The delivery log and a replay button are under Developers in the portal.