WebSocket
Glowstack exposes a single multiplexed WebSocket that streams every token event we ingest — creations, trades, migrations, holder deltas, and AI signals — with sub-second latency.
wss://stream.glowstack.dev/v1
Connection
Authenticate by passing your key in the Authorization header on the
upgrade request. Browsers that cannot set headers may use the
?api_key= query param — server-side only, never ship keys to browsers.
const ws = new WebSocket("wss://stream.glowstack.dev/v1", {
headers: { Authorization: `Bearer ${process.env.ST_KEY}` },
});
ws.on("open", () => ws.send(JSON.stringify({
type: "subscribe",
id: "sub_1",
event: "token.created",
filter: { source: "pumpfun" },
})));
| Property | Value |
|---|---|
| Heartbeat | Server pings every 25s; reply {"type":"pong"} within 15s |
| Idle timeout | 60s without a client message |
| Max subscriptions | 10 per connection (Pro), 1 (Free) |
| Compression | permessage-deflate auto-negotiated; opt-in msgpack |
Subscribe
Every subscription has a client-chosen id that is echoed on every event,
so you can multiplex many subscriptions on one socket.
{
"type": "subscribe",
"id": "sub_1",
"event": "trade.filled",
"columns": ["mint", "price", "size_usd", "side", "signer"],
"filter": { "mint": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263" }
}
The server acknowledges with a subscribed frame. Any validation failure
closes the subscription (not the socket) with a typed error frame.
{ "type": "subscribed", "id": "sub_1", "cursor": "c_01HW9K…" }
Unsubscribe
{ "type": "unsubscribe", "id": "sub_1" }
Columns
columns is optional. When omitted, the server sends a sensible default
set per event. When present, the server projects to exactly those fields —
saving you bandwidth and parse time.
| Column | Type | Notes |
|---|---|---|
mint | string | Base58 mint address |
symbol | string | Token symbol, nullable |
price | number | USD, 8 decimals |
size_usd | number | Trade size in USD |
side | "buy" | "sell" | Classified against pool reserves |
signer | string | Tx signer wallet |
signal | string | Current AI signal, nullable |
updated_at | string | ISO 8601 UTC |
Filters
Filters use a columnar expression language. Every filter key is a column
name; values are literals, ranges, or $in arrays.
{
"filter": {
"source": "pumpfun",
"mcap": { "$gte": 100000, "$lte": 5000000 },
"signal": { "$in": ["accumulation", "pre_migration"] }
}
}
Compound filters use $and / $or:
{
"filter": {
"$or": [
{ "signal": "insider_spike" },
{ "$and": [{ "holders": { "$gte": 500 } }, { "age_min": { "$lte": 60 } }] }
]
}
}
Events
All event frames share the envelope:
{ "type": "event", "id": "sub_1", "event": "token.created", "data": { /* ... */ } }
| Event | Emitted when |
|---|---|
token.created | A new mint is detected on PumpFun, Meteora, or Raydium launchpad |
token.migrated | Bonding curve completes and liquidity moves to AMM |
trade.filled | Swap classified against a tracked pool |
holder.delta | Top-100 holder set changes (debounced 5s) |
signal.emitted | An AI model emits a new or upgraded signal |
Each event has a typed data payload — see the
event schemas for the full contract.