WebSocket API
mackinac exposes a single WebSocket endpoint that serves all real-time market data — the same feeds that power the browser interface. Professional subscribers access feeds through their authenticated browser session. API-tier subscribers can additionally authenticate with an API key for headless, programmatic access.
Architecture
┌──────────────────────────────────────────────────────────────┐ │ mackinac data platform │ │ │ │ Hyperliquid L2 ──► HL feed processor ──►┐ │ │ Arbitrum One ──► AMM feed processor ──►┤ Fanout │ │ (on-chain swaps, (Uniswap V3/V4, │ server ────┼──► wss://app.mackinac.io/ws │ LP events, SushiSwap, │ │ │ pool state) PancakeSwap) ──►┘ │ └──────────────────────────────────────────────────────────────┘
Client authentication ───────────────────── Browser session ──► JWT (automatic when logged in to the app) API key ──► { "action": "auth", "key": "mk_live_..." } (API tier only)
Subscription model ────────────────── Client sends: { "action": "subscribe", "exchange": "hl", "symbol": "BTC" } Server sends: subscribed → snapshot → quote / print / depth / spread / ... (streaming)Connecting
Endpoint: wss://app.mackinac.io/ws
Connections are accepted anonymously. Anonymous and browser-session connections receive
data gated to the subscriber’s tier. API-tier users must send an auth action immediately
after connecting to identify themselves before subscribing.
Authentication (API Tier)
Send immediately after the connection opens:
{ "action": "auth", "key": "mk_live_..." }Server response on success:
{ "type": "authed", "tier": "api", "symbols": 100 }Server response on failure:
{ "type": "error", "code": "auth_failed", "message": "Invalid or revoked key" }Generate and revoke API keys from the Developer section of Account settings in the app. Up to 3 active keys per account. See Subscription Tiers for API tier details.
Subscribing to Symbols
{ "action": "subscribe", "exchange": "hl", "symbol": "BTC" }{ "action": "subscribe", "exchange": "univ3", "symbol": "WETH/USDC" }{ "action": "subscribe", "exchange": "univ4", "symbol": "WETH/USDC" }{ "action": "subscribe", "exchange": "sushiswap", "symbol": "WETH/USDC" }{ "action": "subscribe", "exchange": "pancake", "symbol": "WETH/USDC" }Virtual / aggregated streams — require the underlying product to be subscribed first:
{ "action": "subscribe", "exchange": "ammbook", "symbol": "WETH/USDC" }ammbook aggregates the best bid and offer across all four AMM venues for the pair.
Unsubscribing:
{ "action": "unsubscribe", "exchange": "hl", "symbol": "BTC" }Symbol Formats
| Exchange value | Format | Example |
|---|---|---|
hl | Standard perp | BTC, ETH |
hl | Micro contract | kPEPE, kSHIB |
hl | Builder DEX perp | xyz:CL, xyz:NG |
hl | Spot pair | PURR/USDC |
univ3 / univ4 / sushiswap / pancake | Pool pair | WETH/USDC, WBTC/USDC |
Tier Limits
| Free | Professional | API | |
|---|---|---|---|
| Max simultaneous symbols | 2 | 50 | 100 |
quote / print / snapshot | ✓ | ✓ | ✓ |
depth | — | ✓ | ✓ |
spread | — | ✓ | ✓ |
ammbook | — | ✓ | ✓ |
liquidity | — | ✓ | ✓ |
| API key authentication | — | — | ✓ |
Data Streams
subscribed
Sent when the server accepts a subscription request.
{ "type": "subscribed", "exchange": "hl", "symbol": "BTC" }snapshot
Sent immediately after subscribed. Contains a replay of recent prints (up to 1,000) so
charts and metrics are populated immediately rather than starting empty. Each item has the
same schema as a print message.
{ "type": "snapshot", "exchange": "hl", "symbol": "BTC", "prints": [ { ...print }, { ...print }, ... ]}quote
L2 book update. Sent whenever the order book changes.
{ "type": "quote", "exchange": "hl", "symbol": "BTC", "bids": [ { "price": "97500.0", "size": "2.4" }, { "price": "97498.0", "size": "5.1" } ], "asks": [ { "price": "97501.0", "size": "1.8" }, { "price": "97503.0", "size": "3.2" } ], "ts": 1710000000000}For AMM products, each level corresponds to one fee-tier pool. Two additional fields are included per level:
| Field | Description |
|---|---|
feeTier | Pool fee rate as a decimal string, e.g. "0.0005" for 0.05% |
lastSwapMs | Milliseconds since the last swap was observed in this pool |
print
A single trade or swap event, annotated with live microstructure metrics computed server-side.
{ "type": "print", "exchange": "hl", "symbol": "BTC", "price": "97500.5", "size": "0.42", "side": "A", "ts": 1710000000123, "d_tickrate": 0.84, "d_volumerate": 41200, "d_lobimb": 0.12, "d_bidqty": 18.3, "d_askqty": 15.1, "d_hawkes": 1.43, "d_hawkes_bid": 0.61, "d_hawkes_ask": 0.82, "d_volumeimb": 0.09, "d_quoterate": 2.1}| Field | Type | Description |
|---|---|---|
side | string | "B" buyer-initiated · "A" seller-initiated |
d_tickrate | number | Trade arrivals per second at time of print |
d_volumerate | number | Notional volume per second at time of print |
d_lobimb | number | LOB imbalance (bid_qty − ask_qty) / total — range [−1, +1] |
d_bidqty | number | Total bid-side quantity in the book at time of print |
d_askqty | number | Total ask-side quantity in the book at time of print |
d_hawkes | number | Hawkes process arrival intensity — all sides |
d_hawkes_bid | number | Hawkes intensity — bid-hit (seller-initiated) side |
d_hawkes_ask | number | Hawkes intensity — ask-lift (buyer-initiated) side |
d_volumeimb | number | Signed volume imbalance (buy − sell volume) in the current window |
d_quoterate | number | Quote update rate per second |
depth
Uniswap liquidity histogram and market-impact estimates. One message per fee-tier pool. Sent when tick liquidity changes (mint/burn events). Professional+ only.
{ "type": "depth", "exchange": "univ3", "symbol": "WETH/USDC", "feeTier": "0.0005", "ticks": [ { "tick": -200760, "liquidity": "18500000000" }, { "tick": -200820, "liquidity": "12100000000" } ], "impact": { "buy": { "1k": 0.04, "10k": 0.18, "100k": 1.42, "1m": 9.3 }, "sell": { "1k": 0.03, "10k": 0.17, "100k": 1.38, "1m": 8.9 } }, "currentTick": -200761, "ts": 1710000000000}| Field | Description |
|---|---|
ticks | Array of { tick, liquidity } — active tick boundaries with their liquidity values |
currentTick | The pool’s current active tick (where swaps are executing) |
impact.buy / impact.sell | Price-impact estimates in basis points for notional sizes of $1k, $10k, $100k, $1M |
spread
Cross-pool spread within a single AMM venue — the best bid and best ask available across all fee-tier pools for a pair on that venue. Professional+ only.
{ "type": "spread", "exchange": "univ3", "symbol": "WETH/USDC", "bestBid": { "price": "3499.91", "feeTier": "0.0005" }, "bestAsk": { "price": "3500.09", "feeTier": "0.0001" }, "spreadBps": 5.2, "ts": 1710000000000}ammbook
Consolidated best bid/offer aggregated across all four AMM venues simultaneously. Sent whenever the cross-venue book changes. Professional+ only.
Subscribe with exchange: "ammbook". Requires at least one AMM product for the same pair
to already be subscribed.
{ "type": "ammbook", "symbol": "WETH/USDC", "bestBid": { "price": "3499.91", "exchange": "univ3", "feeTier": "0.0005" }, "bestAsk": { "price": "3500.07", "exchange": "univ4", "feeTier": "0.0001" }, "spreadBps": 4.6, "gapGrossBps": 0.0, "gapNetBps": -5.2, "venues": [ { "exchange": "univ3", "bid": "3499.91", "ask": "3500.09", "ageSec": 1 }, { "exchange": "univ4", "bid": "3499.95", "ask": "3500.07", "ageSec": 0 }, { "exchange": "sushiswap", "bid": "3499.88", "ask": "3500.12", "ageSec": 4 }, { "exchange": "pancake", "bid": "3499.82", "ask": "3500.18", "ageSec": 9 } ], "ts": 1710000000000}| Field | Description |
|---|---|
bestBid / bestAsk | The single best price available across all venues, with source exchange and fee tier |
spreadBps | Spread between bestBid and bestAsk in basis points |
gapGrossBps | Cross-venue spread gap (best bid − best ask across venues) before fees; positive = gap exists |
gapNetBps | Same gap after taker fees on both legs; negative means fees exceed the gross gap |
venues | Per-venue best bid/ask and seconds since the last swap on that venue |
liquidity
LP mint or burn event. Sent when a liquidity provider adds or removes a position. Professional+ only.
{ "type": "liquidity", "exchange": "univ3", "symbol": "WETH/USDC", "event": "mint", "feeTier": "0.0005", "amount0": "4.21", "amount1": "14750.00", "tickLower": -201000, "tickUpper": -200400, "ts": 1710000000000}| Field | Description |
|---|---|
event | "mint" (LP position added) · "burn" (LP position removed) |
amount0 / amount1 | Token quantities added or removed |
tickLower / tickUpper | The price range of the position (in Uniswap tick units) |
Connection Management Messages
These messages are sent by the server without a client request.
feed_stale
The upstream data feed for a symbol has gone stale. Subsequent quote and print messages
for this symbol may be delayed or absent until the feed recovers.
{ "type": "feed_stale", "exchange": "hl", "symbol": "BTC" }feed_live
The upstream feed has recovered. Normal message flow will resume.
{ "type": "feed_live", "exchange": "hl", "symbol": "BTC" }server_closing
The server is shutting down gracefully. Reconnect after a short delay.
{ "type": "server_closing", "message": "Planned restart in 10s" }error
An action was rejected. Sent in response to a specific client action.
{ "type": "error", "code": "symbol_limit", "message": "Subscription limit reached for your tier"}| Code | Cause |
|---|---|
auth_failed | API key is invalid, revoked, or expired |
symbol_limit | Active subscriptions are at the tier maximum |
unknown_symbol | The requested symbol was not found |
invalid_action | Unrecognized action type or malformed message |
Example Session (API Tier)
const ws = new WebSocket('wss://app.mackinac.io/ws');
ws.onopen = () => { // Step 1 — authenticate with API key ws.send(JSON.stringify({ action: 'auth', key: 'mk_live_...' }));};
ws.onmessage = ({ data }) => { const msg = JSON.parse(data);
if (msg.type === 'authed') { // Step 2 — subscribe to HL perp and AMM spot ws.send(JSON.stringify({ action: 'subscribe', exchange: 'hl', symbol: 'ETH' })); ws.send(JSON.stringify({ action: 'subscribe', exchange: 'univ4', symbol: 'WETH/USDC' })); // Step 3 — subscribe to aggregated streams ws.send(JSON.stringify({ action: 'subscribe', exchange: 'ammbook', symbol: 'WETH/USDC' })); }
if (msg.type === 'print' && msg.exchange === 'hl') { console.log( `HL ETH price=${msg.price} hawkes=${msg.d_hawkes.toFixed(3)}` + ` lobImb=${msg.d_lobimb.toFixed(3)} volImb=${msg.d_volumeimb.toFixed(3)}` ); }
if (msg.type === 'ammbook') { console.log( `WETH/USDC best bid=${msg.bestBid.price} (${msg.bestBid.exchange})` + ` best ask=${msg.bestAsk.price} (${msg.bestAsk.exchange})` ); }};
ws.onerror = (err) => console.error('WebSocket error', err);ws.onclose = () => console.log('Disconnected — reconnecting...');