Skip to content
Realtime

Streams

Consume events over WebSocket or Server-Sent Events.

Streams push events to you the moment FomoData ingests them. Use the WebSocket for bidirectional subscriptions with filters and cursor resume, or Server-Sent Events when a plain HTTP stream is easier. Both deliver the same Event object you get from REST and webhooks.

Event types

TypeFires whendata
thesis.createdA thesis is ingested from the authorized upstream (including backfill).ThesisCreatedData
profile.updatedPublic profile metadata changed on resync.ProfileUpdatedData
token.activityNew theses on a token. Derived, at most once a minute per token.TokenActivityData
token.thesis_velocityEvery 5 minutes for tokens with theses in the last hour, only when the value changed.ThesisVelocityData

Only event types backed by authorized upstream data exist. Derived events carry source: "fomodata".

The event object

thesis.created · sandbox
{
  "object": "event",
  "id": "evt_2Nf8QpLx0VbT6mRz4kWc1S",
  "type": "thesis.created",
  "created_at": "2026-10-07T10:04:12.000Z",
  "livemode": false,
  "source": "sandbox",
  "data": {
    "thesis_id": "th_7Hq2LmZx9RkT4bVn0sYc3D",
    "profile": {
      "id": "fp_4KZq8mXw2T0aN6rB1cYd9E",
      "handle": "@sbx_milo",
      "display_name": "Milo (sandbox)",
      "avatar_url": null
    },
    "token": {
      "id": "tk_9bF2kLmQ0sVx7RtY3nHc1A",
      "symbol": "$RUN",
      "name": "Run (sandbox)",
      "chain": "sandbox",
      "address": "0x393e5c8b655042f2409351fc00c2714947537a12"
    },
    "text": "Sandbox thesis: course looks fast today.",
    "created_at": "2026-10-07T10:04:12.000Z",
    "public_url": null
  }
}

id is stable: the same event always has the same id across REST, SSE, WebSocket, webhooks and replays, so you can deduplicate on it. Test events you generate carry test: true.

WebSocket

Endpoint
wss://visits-taking-trademark-corner.trycloudflare.com/v1/stream

Authentication

Send Authorization: Bearer fd_… on the upgrade request (server-side clients), or connect and send an auth message within 10 seconds. Keys are never accepted in the URL.

client → server
{ "action": "auth", "api_key": "fd_test_…" }

The server greets you once authenticated:

server → client
{ "type": "welcome", "connection_id": "conn_…", "livemode": false, "project_id": "proj_…", "heartbeat_seconds": 20 }

Subscribe

client → server
{
  "action": "subscribe",
  "id": "race-entries",
  "events": ["thesis.created"],
  "filters": { "token": "$RUN", "handle": "@milo", "chain": "robinhood" },
  "from_cursor": "1042"
}
eventsstring[]
Event types to receive. Omit (or send []) for every type.
filtersobject
token ($SYMBOL, address or tk_ id), handle, chain; each a string or an array (values OR-ed). All optional; dimensions combined with AND.
from_cursorstring
Replay events after this cursor (max 1,000, within your plan's history) before live delivery.
idstring
Optional client reference echoed back.
server → client
{ "type": "subscribed", "subscription_id": "ssub_…", "events": ["thesis.created"], "filters": { "token": "$RUN" } }

Events then arrive as:

server → client
{ "type": "event", "subscription_id": "ssub_…", "cursor": "1043", "event": { "object": "event", "id": "evt_…", "type": "thesis.created", … } }

Other messages

DirectionMessage
client → server{ "action": "unsubscribe", "subscription_id": "ssub_…" }
client → server{ "action": "ping" }
server → client{ "type": "pong" }
server → client{ "type": "heartbeat", "ts": "…" }
server → client{ "type": "error", "error": { "type", "code", "message" } }

Close codes

CodeMeaning
4001Unauthorized: missing, invalid, revoked or expired key.
4003Forbidden: the key lacks streams:read, the project isn't approved for live data, or it is archived.
4008Authentication timeout (no auth within 10 seconds).
4029Connection limit reached for your plan, the key's rate limit or monthly quota (the error frame says which), or too many failed authentications from your address.
1013Slow consumer: more than 1 MB of events was waiting to be sent. Reconnect and subscribe with from_cursor = your last cursor; nothing is lost.
1001Server restarting (deploy). Reconnect and subscribe with from_cursor.
1008Too many control messages (more than 60 in 10 seconds, or more than 256 KB waiting to be handled). Slow down, then reconnect.

4001 and 4003 won't succeed on retry; fix the key or project first. For every other close, reconnect with backoff and resume from your last cursor. Authorization is re-checked while a stream is open: revoking or expiring the key, removing streams:read, archiving the project or losing live approval closes open WebSockets with 4001/4003 and ends SSE streams with an error event, usually within seconds and at most about 20 seconds later. A WebSocket connect, and every subscribe with from_cursor (at most 6 per minute per connection), counts as a request toward your rate limit and monthly quota, like SSE and REST.

Resume after a disconnect

Remember the last cursor you processed. On reconnect, subscribe with from_cursor and FomoData replays what you missed (up to 1,000 events within your plan's history), then continues live. Combined with deduplication on event.id, this gives you at-least-once, gap-free consumption.

With the SDK

The SDK handles auth, heartbeats, reconnects and cursor resume for you: after 1013, 1001 or a dropped connection it reconnects with backoff and re-subscribes with from_cursor set to the last event it delivered. 4001 and 4003 stop it and call onError.

stream.ts
import { FomoData } from "@fomodata/sdk";

const fomo = new FomoData({ apiKey: process.env.FOMODATA_API_KEY });

fomo.streams.subscribe({
  event: "thesis.created",
  token: "$RUN",
  onEvent(event) {
    console.log(event);
  }
});

Server-Sent Events

GET/v1/events/stream
Query parameters
typesstring
Comma-separated event types, e.g. thesis.created,token.activity.
token · handle · chainstring
Same filters as the WebSocket.
from_cursorstring
Resume after this cursor (a frame's id). The Last-Event-ID header takes precedence.
curl -N 'https://fomodata.dev/v1/events/stream?types=thesis.created&token=$RUN' \
  -H "Authorization: Bearer $FOMODATA_API_KEY"

Authenticate with the Authorization header. Each frame's id is the event cursor; reconnect with Last-Event-ID to resume. A : ping comment arrives every 15 seconds to keep proxies from closing the connection.

Event history over REST

Need to backfill or audit? The same events are queryable.

GET/v1/eventsGET/v1/events/{id}

Limits and metering

  • Concurrent connections per project are capped by your plan's stream_connections. Extra connections close with 4029 (WebSocket) or 429 STREAM_CONNECTION_LIMIT (SSE).
  • Each delivered event counts as one stream message, and each connect as one stream connection, in your usage.