WebSocket API
Superhero pushes live token data over Socket.IO. Two events carry token updates and trades on the default namespace, and a separate authenticated namespace carries notifications.
Connecting
| Network | URL |
|---|---|
| Mainnet | https://api.superhero.com |
| Testnet | Not publicly documented |
Same origin as the REST API, default namespace, default /socket.io/ path. No authentication
and no handshake beyond Socket.IO's own.
import { io } from 'socket.io-client';
const socket = io('https://api.superhero.com', { autoConnect: false });
socket.connect();The CORS origin is the same value the REST API uses: every origin by default, an allow-list
when the operator sets ALLOWED_ORIGINS. See Conventions.
A plain WebSocket client cannot connect: Socket.IO is a protocol layered on Engine.IO, so use a Socket.IO client for your language.
Events
| Event | Fires when |
|---|---|
token-updated | A token's live price, supply, market cap or DAO balance has been re-read from chain and written back |
token-history | A buy, sell or token creation has been indexed live |
Both are broadcast to every connected client, so getting started takes two steps: connect and
listen. Each payload carries the token's sale_address, so your process can route it to the views
that show that token. For newly created tokens, read
GET /api/tokens?order_by=created_at&order_direction=DESC or GET /api/feed.
All emissions originate server-side, from services that have already validated the payload.
Payloads
Both events are an envelope with the token's sale_address at the top level and the payload
under data.
token-updated
{ "sale_address": "ct_...", "data": {} }data is the live-price read that was just written to the token row, a fixed subset of
TokenDto:
| Key | Type | Unit |
|---|---|---|
price | string | AE |
price_data | PriceDto | AE |
sell_price | string | AE |
sell_price_data | PriceDto | AE |
market_cap | string | aettos |
market_cap_data | PriceDto | aettos |
total_supply | string | base units |
circulating_supply | string | base units |
dao_balance | string | aettos |
address | string | the AEX-9 contract |
name, symbol | string | from the sale contract's metaInfo |
beneficiary_address, bonding_curve_address, owner_address | string | from metaInfo |
Nothing else is on it. rank, trending_score, holders_count, tx_count,
collection_info, performance and created_at are never pushed. Refetch
/api/tokens/{address}/rankings and the holders endpoint for those, throttled to at most once
every 15 seconds per sale_address so a burst of trades collapses into one refetch.
A field the chain read failed to return is absent rather than null. Merge, do not replace:
const newTokenData = { ...currentTokenData, ...message.data };token-history
{ "sale_address": "ct_...", "data": {}, "token": {} }token is the full token row the trade belongs to. data is the transaction record that was
just persisted, with the fields of a GET /api/transactions row:
| Field | Type | Notes |
|---|---|---|
sale_address | string | Repeats the envelope's |
tx_type | string | buy, sell or create_community |
tx_hash | string | th_... |
block_height | number | |
address | string | The trader's ak_... |
volume | string | whole tokens |
protocol_reward | string | whole tokens |
amount | PriceDto | AE |
unit_price | PriceDto | AE |
previous_buy_price | PriceDto | AE |
buy_price | PriceDto | AE |
total_supply | string | whole tokens |
market_cap | PriceDto | AE, not aettos |
created_at | string | ISO 8601 |
verified | boolean |
The market_cap in data is in AE, while the market_cap on the same event's token object is
in aettos.
For a candlestick chart, read data.buy_price[convertTo] and data.market_cap[convertTo] using
the same nine currency codes as the REST API: update the current candle when the tick is inside
the active interval and open a new one otherwise. For a live-trades list, spread data and
token into one object and prepend it to your cached GET /api/transactions page.
Trades are pushed as they are indexed live. For history, read GET /api/transactions.
Delivery
Socket.IO reconnects on its own. Events are live signals: use each one as your cue to refetch, with the REST endpoints as the source of record. After a reconnect, re-read the REST endpoints behind the views you have open so they pick up anything that changed in between.
A minimal live-trades client
import { io } from 'socket.io-client';
const SALE_ADDRESS = 'ct_YOUR_SALE_ADDRESS';
const socket = io('https://api.superhero.com', { autoConnect: false });
socket.on('connect', () => console.log('connected'));
socket.on('disconnect', () => console.log('disconnected — refetch on reconnect'));
socket.on('token-history', (message) => {
if (message.sale_address !== SALE_ADDRESS) return;
// data.amount and data.unit_price are already in AE; volume is in whole tokens
console.log('trade', message.data.tx_type, message.data.volume, 'for', message.data.amount.ae, 'AE');
});
socket.on('token-updated', (message) => {
if (message.sale_address !== SALE_ADDRESS) return;
console.log('price now', message.data.price_data?.ae, 'AE');
});
socket.connect();Seed from REST first, then let the socket keep it current:
curl -s "https://api.superhero.com/api/transactions?token_address=ct_YOUR_SALE_ADDRESS&includes=token&limit=50"The notification namespace
Notifications run on the same origin but a different Socket.IO namespace, with a different connection model:
namespace: https://api.superhero.com/notifications
auth: { token } — a bearer session token
transports: ['polling'] with upgrade: false, forceNew: true| Token socket | Notification socket | |
|---|---|---|
| Namespace | Default (/) | /notifications |
| Authentication | None | Socket.IO auth.token, from the feed session flow |
| Transport | Polling, upgrading to WebSocket | Polling only, upgrade disabled |
| Reconnection | Library default | reconnectionAttempts: 5 |
| Events | token-updated, token-history | notification, unread-count |
| Fan-out | Broadcast to everyone | Only to the proven address's own room |
The token comes from the feed session flow in Conventions: challenge, wallet signature, then a bearer token.
Handshake
The server runs four checks in order and disconnects the socket on any failure:
- Per-IP handshake cap. At most 30 handshake attempts per source IP per rolling minute, counted before any session lookup.
- Token extraction.
handshake.auth.tokenfirst, then anAuthorization: Bearerheader. No token means immediate disconnect. - Session resolution. The token is an opaque 32-byte random hex string mapped to its owner
address, minted by
POST /api/notifications/{address}/feed/sessionand valid for 7 days. It is not a JWT and is revocable server-side. An unknown or expired token means disconnect. - Per-address connection cap. At most 10 concurrent sockets per address.
On success the socket joins a room named after the proven address, and every emit is addressed to that room. The namespace accepts no client-sent messages: read history and mark items read over the authenticated REST endpoints.
notification carries a feed item; unread-count carries { count }.
The persisted feed is pruned after 90 days and trimmed to the 500 most recent rows per address; a feed page is capped at 50 items. The REST side of the same subsystem:
POST /api/notifications/{address}/feed/challenge
POST /api/notifications/{address}/feed/session
GET /api/notifications/{address}/feed?limit=50
GET /api/notifications/{address}/feed/unread-count
POST /api/notifications/{address}/feed/read
POST /api/notifications/{address}/web-push/subscription
GET /api/notifications/web-push/vapid-public-keyThe feed page returns { items, nextCursor }; the unread count returns { count }; marking
read takes an optional array of ids (omit it to mark everything) and returns the new count.
What is not on the WebSocket
New posts, comments and tips are not pushed on the token channels, so poll the feed over REST.
Chat runs on Nostr relays (wss://relay.superhero.chat by default), not on this socket. Chain
events are also absent; for those, watch the middleware.
Related
- Transactions API: the REST form of the
token-historypayload - Tokens API: the full
TokenDtothattoken-updatedsends partial updates for - API Conventions: base URLs, the
PriceDtocurrency keys and the units rule - Node & Middleware: for chain-level events rather than Superhero events
- Socket.IO client documentation (opens in a new tab): the protocol this endpoint speaks