Integrate
Webhooks
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
| Event | Sent when |
|---|---|
signal.created | A signal fired. data = the signal object (token, type, label, rule_version, why, scores at fire time, analog statistics). |
token.score.updated | A token's score moved by ≥ 3 points or its active signals changed. data = { token_id, symbol, ts, scores, previous, divergence_state, active_signals }. |
cohort.convergence | A 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.test | Sent only by the “send test” action, to check your endpoint and signature code. |
Payload & headers
Body
| Name | Description |
|---|---|
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. |
idev_…Event id — identical across retries. Use it to deduplicate.object"event"Always event.typeevent typeOne of the events above.created_atISO-8601When the event happened.dataset / livemodestring / booleanlive / true.dataobjectsnake_case payload, serialised exactly like the REST API.
Headers
| Name | Description |
|---|---|
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. |
FomoQuant-EventstringThe 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/jsonUTF-8 JSON.
{
"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
idand usecreated_atfor 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 { … }.
/v1/webhooksWebhooks registered for this key's dataset.
curl "https://fomoquant.app/v1/webhooks" \
-H "Authorization: Bearer $FOMOQUANT_API_KEY"/v1/webhooksRegister an HTTPS endpoint. The signing secret is returned once.
Parameters
| Name | Description |
|---|---|
urlrequiredbody · https URL | Receives signed POSTs. Private and loopback addresses are rejected. |
eventsrequiredbody · event[] | Any of signal.created, token.score.updated, cohort.convergence. |
urlbody · https URLrequiredReceives signed POSTs. Private and loopback addresses are rejected.eventsbody · event[]requiredAny 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"]}'/v1/webhooks/{id}Delete a webhook.
Parameters
| Name | Description |
|---|---|
idrequiredpath · wh_… | Webhook id. |
idpath · wh_…requiredWebhook id.
curl -X DELETE "https://fomoquant.app/v1/webhooks/wh_1d6b5n0h3k8m2q9x4v7c" \
-H "Authorization: Bearer $FOMOQUANT_API_KEY"/v1/webhooks/{id}/testSend a signed webhook.test event now and report the endpoint's HTTP status. Never counts toward auto-disable.
Parameters
| Name | Description |
|---|---|
idrequiredpath · wh_… | Webhook id. |
idpath · wh_…requiredWebhook 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.