La documentación para desarrolladores solo está en inglés por ahora. El resto del sitio está en español.

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.reserved
  • question.completed
  • question.underfilled
  • question.cancelled
  • question.refused
  • credits.purchased
  • credits.low
  • credits.expiring
  • dispute.resolved
  • connection.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.