Skip to content

Integrate

Webhooks

Receive signals, score updates and cohort convergence on your own server. Every delivery is a signed JSON POST, retried with backoff, and carries a stable event id for idempotency.

Create webhooks from the API page or with the API (scope webhooks:write). A webhook only receives events from the dataset of the key it was created with: live Fomo activity.

Events

EventSent when
signal.createdA signal fired. data = the signal object (token, type, label, rule_version, why, scores at fire time, analog statistics).
token.score.updatedA token's score moved by ≥ 3 points or its active signals changed. data = { token_id, symbol, ts, scores, previous, divergence_state, active_signals }.
cohort.convergenceA cohort converged on a token. data = the cohort event (cohort, token, member_count / cohort_size, window_minutes, members, score). Auto cohorts reach every subscriber; a manual cohort only its owner (or its team, when shared).
webhook.testSent only by the “send test” action, to check your endpoint and signature code.

Payload & headers

Body

NameDescription
id
ev_…
Event id — identical across retries. Use it to deduplicate.
object
"event"
Always event.
type
event type
One of the events above.
created_at
ISO-8601
When the event happened.
dataset / livemode
string / boolean
live / true.
data
object
snake_case payload, serialised exactly like the REST API.
  • idev_…
    Event id — identical across retries. Use it to deduplicate.
  • object"event"
    Always event.
  • typeevent type
    One of the events above.
  • created_atISO-8601
    When the event happened.
  • dataset / livemodestring / boolean
    live / true.
  • dataobject
    snake_case payload, serialised exactly like the REST API.

Headers

NameDescription
FomoQuant-Event
string
The event type.
FomoQuant-Delivery
whd_…
This delivery attempt's id.
FomoQuant-Signature
t=<unix>,v1=<hex>
HMAC-SHA256 of "<t>.<raw body>" with your signing secret.
Content-Type
application/json
UTF-8 JSON.
  • FomoQuant-Eventstring
    The event type.
  • FomoQuant-Deliverywhd_…
    This delivery attempt's id.
  • FomoQuant-Signaturet=<unix>,v1=<hex>
    HMAC-SHA256 of "<t>.<raw body>" with your signing secret.
  • Content-Typeapplication/json
    UTF-8 JSON.
Example · signal.created (illustrative)
{
  "id": "ev_7c1d6b5n0h3k8m2q9x4v",
  "object": "event",
  "type": "signal.created",
  "created_at": "2026-10-10T11:54:00.000Z",
  "dataset": "live",
  "livemode": true,
  "data": {
    "object": "signal",
    "dataset": "live",
    "livemode": true,
    "id": "sig_8d3k2m9q4x7v1c6b5n0t",
    "type": "EARLY_ACCELERATION",
    "label": "Early acceleration",
    "rule_version": "early_acceleration@1.0",
    "status": "active",
    "fired_at": "2026-10-10T11:54:00.000Z",
    "ended_at": null,
    "strength": 0.214,
    "scores": { … },
    "why": [
      "thesis velocity +218%",
      "unique authors +71% (12 authors this hour)",
      "crowding low (24)",
      …
    ],
    "analogs": {
      "n": 27,
      "median_24h": 0.168,
      "worst_drawdown_24h": -0.284,
      "positive_share": 0.59,
      "confidence": "MEDIUM"
    },
    "token": {
      "object": "token",
      "id": "tk_4f8k2m9q1x7c3v5b6n0d",
      "dataset": "live",
      "symbol": "XYZ",
      "name": "Xyzzy Agents",
      "chain": "base",
      "address": "0x1111111111111111111111111111111111111111",
      "logo_url": null,
      "status": "active",
      "narrative": { … },
      "fomo_url": null,
      "livemode": true
    }
  }
}

Verify signatures

Compute HMAC-SHA256(secret, "<t>.<raw body>"), compare it to v1 in constant time, and reject timestamps older than a few minutes to stop replays. Always use the raw request body: parsing and re-serialising JSON changes the bytes.

import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

const SECRET = process.env.FOMOQUANT_WEBHOOK_SECRET!; // whsec_…, shown once at creation
const TOLERANCE_SEC = 300;

function verify(rawBody: Buffer, header: string | undefined) {
  if (!header) throw new Error("missing FomoQuant-Signature");
  const parts = new Map(header.split(",").map((p) => p.split("=", 2) as [string, string]));
  const t = Number(parts.get("t"));
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > TOLERANCE_SEC) throw new Error("stale timestamp");
  const expected = createHmac("sha256", SECRET).update(`${t}.`).update(rawBody).digest();
  const given = Buffer.from(parts.get("v1") ?? "", "hex");
  if (given.length !== expected.length || !timingSafeEqual(given, expected)) throw new Error("bad signature");
  return JSON.parse(rawBody.toString("utf8"));
}

const app = express();
// Verify against the RAW body — never a re-serialised JSON object.
app.post("/fomoquant", express.raw({ type: "application/json" }), (req, res) => {
  let event;
  try {
    event = verify(req.body, req.get("FomoQuant-Signature"));
  } catch {
    return res.status(400).send("invalid signature");
  }
  if (alreadyProcessed(event.id)) return res.sendStatus(200); // deliveries can repeat
  queue(event);                                               // do the work async
  res.sendStatus(200);                                        // answer within 10 s
});

Delivery & retries

  • A 2xx response within 10 seconds counts as delivered. Redirects are not followed.
  • Failed deliveries are retried after 30 s, 2 m, 10 m, 30 m, 2 h, 6 h.
  • After 20 consecutive failures the webhook is disabled, with the reason shown on the API page. Test deliveries never count toward this.
  • Events can arrive more than once and out of order — deduplicate on id and use created_at for ordering.
  • URLs must be public HTTPS endpoints; private, loopback and link-local addresses are rejected (including after DNS resolution).

Endpoints

Example responses use illustrative values ($XYZ, example_rhea, a placeholder address) — not real tokens or authors; every field name and shape is exactly what the API returns. Long sections are collapsed as { … }.

GET/v1/webhooks
scope webhooks:write

Webhooks registered for this key's dataset.

curl "https://fomoquant.app/v1/webhooks" \
  -H "Authorization: Bearer $FOMOQUANT_API_KEY"
POST/v1/webhooks
scope webhooks:write

Register an HTTPS endpoint. The signing secret is returned once.

Parameters

NameDescription
urlrequired
body · https URL
Receives signed POSTs. Private and loopback addresses are rejected.
eventsrequired
body · event[]
Any of signal.created, token.score.updated, cohort.convergence.
  • urlbody · https URLrequired
    Receives signed POSTs. Private and loopback addresses are rejected.
  • eventsbody · event[]required
    Any of signal.created, token.score.updated, cohort.convergence.
curl -X POST "https://fomoquant.app/v1/webhooks" \
  -H "Authorization: Bearer $FOMOQUANT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/fomoquant","events":["signal.created","token.score.updated"]}'
DELETE/v1/webhooks/{id}
scope webhooks:write

Delete a webhook.

Parameters

NameDescription
idrequired
path · wh_…
Webhook id.
  • idpath · wh_…required
    Webhook id.
curl -X DELETE "https://fomoquant.app/v1/webhooks/wh_1d6b5n0h3k8m2q9x4v7c" \
  -H "Authorization: Bearer $FOMOQUANT_API_KEY"
POST/v1/webhooks/{id}/test
scope webhooks:write

Send a signed webhook.test event now and report the endpoint's HTTP status. Never counts toward auto-disable.

Parameters

NameDescription
idrequired
path · wh_…
Webhook id.
  • idpath · wh_…required
    Webhook id.
curl -X POST "https://fomoquant.app/v1/webhooks/wh_1d6b5n0h3k8m2q9x4v7c/test" \
  -H "Authorization: Bearer $FOMOQUANT_API_KEY"

FomoQuant provides analytics and historical/statistical context, not financial advice or guaranteed predictions.