{"ok":true,"desk":"keepersignal","at":"2026-11-11T15:12:11.101Z","routes":{"one":"/api/keeper/session/:token?maxAge=seconds","many":"/api/keeper/session?tokens=NVDA,TSLA&maxAge=seconds","subscribe":"POST /api/keeper/webhooks { url, tokens, events }","read":"GET /api/keeper/webhooks/:id with Authorization: Bearer <secret>","remove":"DELETE /api/keeper/webhooks/:id with Authorization: Bearer <secret>"},"defaults":{"maxAge":900,"maxMaxAge":86400,"batch":25,"tokensPerSubscription":20,"events":["halt","reopen","close","weekend-mark"],"defaultEvents":["halt","reopen","weekend-mark"]},"webhooks":{"active":1,"disabled":0,"queued":0,"delivered":139,"failed":0,"lastHour":{"tries":0,"ok":0}},"chat":{"waiting":0,"sent":0},"cycle":{"at":"2026-11-11T15:10:20.073Z","ok":true,"read":203,"counts":{"open":203,"closed":0,"halted":0,"weekend":0,"unknown":0},"reason":null},"recent":[{"at":"2026-11-10T02:31:58.858Z","id":"ev_ec16c6a5cbf6d31fd061","type":"reopen","block":110545924,"symbol":"IBM","verdict":"open","previous":"halted"},{"at":"2026-11-10T02:31:58.858Z","id":"ev_f8673fa5f318723732d8","type":"reopen","block":110545924,"symbol":"F","verdict":"open","previous":"halted"},{"at":"2026-11-10T02:31:58.858Z","id":"ev_37ce122b7f03b739aacb","type":"reopen","block":110545924,"symbol":"V","verdict":"open","previous":"halted"},{"at":"2026-11-10T02:13:08.185Z","id":"ev_90faf86d8dacf0388354","type":"halt","block":110535098,"symbol":"F","verdict":"halted","previous":"open"},{"at":"2026-11-10T02:13:08.185Z","id":"ev_f8b4f64a8b666d6dfe9b","type":"halt","block":110535098,"symbol":"IBM","verdict":"halted","previous":"open"},{"at":"2026-11-10T02:13:08.185Z","id":"ev_e6906068c5dd6c4ec2ef","type":"halt","block":110535098,"symbol":"V","verdict":"halted","previous":"open"},{"at":"2026-11-10T01:31:27.616Z","id":"ev_4b2e9be2bd302da5ee86","type":"reopen","block":110511285,"symbol":"F","verdict":"open","previous":"halted"},{"at":"2026-11-10T01:31:27.616Z","id":"ev_b6d9dd8a35b016310841","type":"reopen","block":110511285,"symbol":"IBM","verdict":"open","previous":"halted"},{"at":"2026-11-10T01:31:27.616Z","id":"ev_316cd07b2778d8916112","type":"reopen","block":110511285,"symbol":"V","verdict":"open","previous":"halted"},{"at":"2026-11-10T01:05:27.607Z","id":"ev_8108cc2b1304c53cf148","type":"halt","block":110493459,"symbol":"F","verdict":"halted","previous":"open"},{"at":"2026-11-10T01:05:27.607Z","id":"ev_e5cb1a7efca1d195365e","type":"halt","block":110493459,"symbol":"IBM","verdict":"halted","previous":"open"},{"at":"2026-11-10T01:05:27.607Z","id":"ev_bcbe88cde6f6ef0bb93f","type":"halt","block":110493459,"symbol":"V","verdict":"halted","previous":"open"},{"at":"2026-11-09T01:29:07.858Z","id":"ev_6a96d6d58945d21534d6","type":"weekend-mark","block":null,"symbol":"MRNA","verdict":null,"previous":null},{"at":"2026-11-09T01:29:07.858Z","id":"ev_4f1dfc47efb55fccda5e","type":"weekend-mark","block":null,"symbol":"BABA","verdict":null,"previous":null},{"at":"2026-11-09T01:29:07.858Z","id":"ev_16c9813e5567560f334a","type":"weekend-mark","block":null,"symbol":"GLXY","verdict":null,"previous":null},{"at":"2026-11-09T01:29:07.858Z","id":"ev_391c5bf6b22f80f160f5","type":"weekend-mark","block":null,"symbol":"DELL","verdict":null,"previous":null},{"at":"2026-11-09T01:29:07.858Z","id":"ev_05aed995b126ac324d0d","type":"weekend-mark","block":null,"symbol":"BA","verdict":null,"previous":null},{"at":"2026-11-09T01:29:07.858Z","id":"ev_4ace79e0b5f79b6b0c88","type":"weekend-mark","block":null,"symbol":"NBIS","verdict":null,"previous":null},{"at":"2026-11-09T01:14:07.855Z","id":"ev_d292d0b71a58132a6a05","type":"reopen","block":109666520,"symbol":"ZM","verdict":"open","previous":"weekend"},{"at":"2026-11-09T01:14:07.855Z","id":"ev_35f101953b8a728cd4bb","type":"reopen","block":109666520,"symbol":"ZS","verdict":"open","previous":"weekend"}],"rules":["canExec is true only when every row the answer stands on was read, is no older than maxAge (900 seconds unless you ask for another, at most 86400), and reads open. On an open answer the age counts from the issuer's last print behind the live row whenever that is older than the read, so maxAge bounds the data a yes stands on; quoteAgeSec gives the print's age on every answer. A row that did not read, a stale row, a row dated in the future and a row that reads anything but open each answer false, with the reason.","Two readings of one row stand behind each answer: the session oracle's live row and HarmonicOracle's row on chain as the writer last read it. A halt is said the moment either reads it; open only once both do. While the contract is not in config, or its writer is switched off and its rows on chain are not kept, the answer stands on the live row alone and says so: source reads session-oracle and the reason says which.","halted is the issuer's halt flag or the token's own pause flag; weekend is the weekend hold; closed is a stale print on an exchange holiday; unknown is anything that did not read or is not known. A verdict is a measurement, never advice and never a price.","A webhook names a token and an event (halt, reopen, close, weekend-mark) and is signed with its own secret: HMAC-SHA256 over the timestamp, a dot and the body, which names the delivery's id, its event and its subscription; the headers that repeat them are not signed, so a receiver drops a repeat by the body's id. Only https on port 443 or 8443 is called, a name is resolved again before every call and refused if any address is private, loopback, link-local or metadata, and a redirect is never followed.","A call that fails is tried again 5 times over about 163 minutes; 8 failed tries in a row switch the subscription off. The secret is shown once; reading or deleting a subscription needs it."],"signing":{"algorithm":"HMAC-SHA256","over":"`${timestamp}.${body}`","header":"x-harmonic-signature","timestamp":"x-harmonic-timestamp","secret":"each subscription's own, shown once","signed":"only the timestamp and the body are signed. the body names the delivery (id, the same on every retry), the event (type) and the subscription; the x-harmonic-delivery, x-harmonic-event and x-harmonic-subscription headers repeat them unsigned, so drop a repeat by the body's id and believe the body over any header"},"verify":"import crypto from \"node:crypto\";\n// raw: the request body exactly as received; ts and sig: the x-harmonic-timestamp and x-harmonic-signature headers\nfunction fromHarmonic(raw, ts, sig, secret) {\n  if (typeof ts !== \"string\" || !/^\\d{1,12}$/.test(ts) || typeof sig !== \"string\") return false;\n  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // replayed or late\n  const want = Buffer.from(\"v1=\" + crypto.createHmac(\"sha256\", secret).update(ts + \".\" + raw).digest(\"hex\"));\n  const got = Buffer.from(sig);\n  return got.length === want.length && crypto.timingSafeEqual(got, want);\n}\n// only the body is signed: drop a repeat by JSON.parse(raw).id, never by a header","cost":"free: the route, the webhooks and the chat alerts. a contract that reads the oracle inside its own transaction is the oracle's paid reader, as before"}