{
 "license": "CC BY 4.0",
 "kind": "council-webhook-format",
 "events": [
  "issue",
  "day"
 ],
 "docs": "https://trends.polyhelper.ai/api/v1/#webhook",
 "subscribe": {
  "method": "POST",
  "url": "https://polyhelper-trends-votes.vadymchernets.workers.dev/hook",
  "body": {
   "url": "https://your.site/path",
   "events": [
    "issue",
    "day"
   ]
  },
  "events": "optional; all of issue, day by default",
  "url_rule": "https only, port 443, a host name (no IP literal, no localhost / .local / .internal / .lan / .home / .corp / .intranet / workers.dev / pages.dev), no user:password, up to 300 characters",
  "ping": "before the subscription is kept, the address gets one signed POST {\"event\": \"ping\", \"at\", \"id\"} (header X-Polyhelper-Event: ping) and must answer 2xx",
  "reply": {
   "ok": true,
   "id": "16 hex characters",
   "secret": "shown once, keep it",
   "events": [
    "issue",
    "day"
   ],
   "sign": "X-Polyhelper-Signature: sha256=HMAC_SHA256(secret, timestamp + \".\" + body)"
  },
  "errors": {
   "400": "bad url or events",
   "409": "this url is already subscribed",
   "422": "the ping did not get 2xx",
   "429": "limit"
  },
  "limits": {
   "subscriptions_in_all": 2000,
   "new_per_address_a_day": 3,
   "pings_per_address_a_day": 10
  }
 },
 "unsubscribe": {
  "method": "DELETE",
  "url": "https://polyhelper-trends-votes.vadymchernets.workers.dev/hook",
  "body": {
   "id": "<id>",
   "secret": "<secret>"
  },
  "errors": {
   "400": "bad id",
   "404": "no such id or wrong secret"
  }
 },
 "delivery": {
  "method": "POST",
  "content_type": "application/json",
  "user_agent": "polyhelper-markets-webhook/1",
  "body": {
   "event": "issue | day",
   "key": "<run_id>:<event> - the same key is delivered to a subscriber once",
   "at": "UTC time of the send",
   "data": {
    "issue": "the issue date",
    "run_id": "the run",
    "status": "the issue status",
    "url": "the issue page",
    "api": "https://trends.polyhelper.ai/api/v1/en/council.json",
    "dispute": "the day's dispute (as api/v1/<lang>/dispute.json)",
    "digest": "the digest lines",
    "results": "day only: [{asset, expected, actual_pct, right, of, draw}] of the 1d score"
   }
  },
  "when": "issue: on every published issue; day: on every day's score (both sent by the engine through the Worker)",
  "headers": {
   "X-Polyhelper-Event": "issue | day | ping",
   "X-Polyhelper-Timestamp": "unix seconds",
   "X-Polyhelper-Delivery": "the key (<run_id>:<event>)",
   "X-Polyhelper-Signature": "sha256=<hex HMAC-SHA256 with your secret of: timestamp + \".\" + the raw body>"
  },
  "tries": 3,
  "retry": "after 1 s, then 4 s; a 4xx other than 408 / 429 is not retried; 8 s timeout; redirects are not followed",
  "switch_off": "after 10 deliveries in a row failed (after their retries) the subscription is switched off"
 },
 "verify": "recompute HMAC-SHA256(secret, X-Polyhelper-Timestamp + \".\" + raw body), compare with X-Polyhelper-Signature in constant time, refuse an old timestamp, and ignore a key you have seen",
 "feed": "api/v1/council-feed.json",
 "source": "votes/worker.js (hookSubscribe, hookDeliver, hookSign, hookFire); engine council_hooks.fire"
}