CRISP partners

Live charts and order books

Load initial market data and keep it updated with one SDK store.

Use createPolymarketMarketData() from SDK 0.13.0 to load an outcome's order book and chart history, then maintain its public WebSocket subscription. You render the state using your existing components. The SDK handles HTTP reads, stream snapshots and deltas, heartbeats, reconnects, refreshes and cleanup.

This is a public market-data stream directly from Polymarket. It does not use CRISP credentials, a managed account, or a realtime session. The origins configured in the Partner Dashboard apply to CRISP account realtime, not this public stream. Use account realtime separately for balances and orders.

One store per selected outcome

import { createPolymarketMarketData } from '@crisp-trade/partner-sdk/market-data';

const market = createPolymarketMarketData({
  tokenId, // String outcome token ID from market discovery.
  history: { interval: '1d', fidelity: 5 },
});

const unsubscribe = market.subscribe(() => {
  const state = market.getSnapshot();
  console.log(state.book.data, state.chart.points, state.connection);
});
market.start();

// On page unmount or before replacing this store with another outcome:
unsubscribe();
market.stop();

Construction performs no network requests. start() is idempotent while running; stop() closes the socket, aborts pending reads and cancels timers. A stopped store can be started again for the same outcome unless it observed market resolution. Create a new store when changing the outcome or history range. Unsubscribing a listener does not stop the store; the owner of the store must call stop().

The entry point is browser-safe and also works in Node.js 24+. Import /market-data in browser code, not the main server SDK entry point. Keep the outcome token ID as a string. A Gamma event ID or condition ID is not a token ID.

React hook

'use client';

import { useEffect, useMemo, useSyncExternalStore } from 'react';
import { createPolymarketMarketData } from '@crisp-trade/partner-sdk/market-data';

export function useMarketData(tokenId: string) {
  const store = useMemo(() => createPolymarketMarketData({
    tokenId,
    history: { interval: '1d', fidelity: 5 },
  }), [tokenId]);

  const state = useSyncExternalStore(
    store.subscribe,
    store.getSnapshot,
    store.getSnapshot,
  );

  useEffect(() => {
    store.start();
    return () => store.stop();
  }, [store]);

  return state;
}

Use state.book.data?.bids and asks in your order-book component, and state.chart.points in your chart. Snapshots are stable between notifications and deeply immutable. Notification bursts are coalesced over 50ms by default. One broken listener cannot stop updates for other listeners.

What the state means

FieldMeaning
connectionidle, connecting, synchronizing, live, reconnecting, or stopped
book.dataCurrent display book or null; bid prices descend and ask prices ascend
book.sourcerest for an HTTP snapshot, stream after a full WebSocket book
book.staleKeep the last display if useful, but indicate that it may be outdated
book.errorRead failure; a failure is not an empty order book
chart.pointsOrdered { timestamp, price, source } display points, bounded by maxPoints
chart.stale / chart.errorHistory refresh failed or recovery is in progress
chart.refreshingCanonical history is being loaded in the background
chart.truncatedEarlier points were omitted to keep the configured memory bound
lastTradeLatest observed trade price/size and upstream timestamp in milliseconds
resolvedA market-resolution event was observed; the store stops and retains stale display data
streamErrorThe public stream was interrupted and is recovering

Book prices and sizes remain decimal strings. Chart timestamps are Unix seconds and prices are numbers for display. updatedAt is a local read/update time, not a venue execution guarantee. A live connection does not imply that history loaded successfully; check each resource independently.

Historical samples and the live tail

Points with source: 'history' come from Polymarket's /prices-history API. Points with source: 'live_trade' are provisional last-trade observations newer than the most recent historical sample. They update at most once per timestamp second and do not represent OHLC candles or the venue's historical sampling method. Use a different style for the provisional tail if your chart needs to distinguish it; filter to history when displaying canonical historical data only.

The SDK refreshes canonical history every 60 seconds and during reconnects. New history replaces provisional points it covers. It does not fabricate points for periods without trades or fill gaps with invented prices. A fixed startTs/endTs range does not receive live points outside those bounds.

Recovery behavior

The initial HTTP book lets you show a snapshot while the stream connects. The SDK waits for a full book from that WebSocket connection before applying its price-level updates. A delta replaces the quantity at a price; zero removes it. Late HTTP responses cannot overwrite live levels, and old sockets cannot update a new connection. Tick-size changes update book metadata.

After an interruption, the SDK marks the retained display stale, reconnects with bounded exponential backoff and jitter, reloads history, and requests a new full stream book. Heartbeats run every ten seconds. Missing initial snapshots, stalled connections and malformed relevant data trigger recovery. Reads have a ten-second default deadline, including custom loaders that ignore cancellation.

The upstream channel does not provide a durable replay cursor. The SDK cannot promise gap-free trading records or cryptographically verify every book delta. Use this store for display. Trading still requires current CRISP order requirements and authoritative order/fill responses.

Optional backend HTTP loaders

If direct browser HTTP requests are unavailable because of CORS, network policy or your preferred caching architecture, provide loaders for your own backend. The SDK still owns the stream and state lifecycle:

const market = createPolymarketMarketData({
  tokenId,
  loadBook: async (tokenId, signal) => {
    const response = await fetch(`/api/public/book?tokenId=${encodeURIComponent(tokenId)}`, { signal });
    if (!response.ok) throw new Error('Book unavailable');
    return response.json();
  },
  loadHistory: async (query, signal) => {
    const response = await fetch('/api/public/history', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(query),
      signal,
    });
    if (!response.ok) throw new Error('History unavailable');
    return response.json();
  },
});

These are example routes you implement. Return the unchanged result envelope from getPolymarketOrderBook(tokenId) or getPolymarketPriceHistory(query) in @crisp-trade/partner-sdk/discovery. Pass cancellation through to the upstream read and apply bounded public-data caching as appropriate. No CRISP credentials are required. Do not attach them to these requests.

Options and boundaries

  • history: false runs the book and last-trade stream without chart reads.
  • refreshIntervalMs defaults to 60,000. It refreshes history and provides an HTTP book fallback while the stream is not synchronized; it does not poll live books.
  • maxPoints defaults to 2,000 and caps retained chart data.
  • notifyIntervalMs defaults to 50; timeoutMs defaults to 10,000.
  • signal stops the lifecycle when aborted. refresh() requests background reads without overlapping an already-running read of the same resource.
  • fetch and createSocket allow transport injection and deterministic tests.

Shared application client (SDK 0.13.0)

Download SDK 0.13.0.

For multiple outcomes, create one app-scoped client. Construction makes no requests. Matching views share a store and HTTP reads; all views and quote subscriptions share one physical socket. The standalone store above still supports explicit start/stop.

import { createPolymarketMarketDataClient } from '@crisp-trade/partner-sdk/market-data';

const client = createPolymarketMarketDataClient();
const market = client.market({ tokenId, history: false });
const unsubscribe = market.subscribe(() => render(market.getSnapshot()));
// React: memoize market by token/history, then pass its subscribe,
// getSnapshot and getServerSnapshot to useSyncExternalStore.
unsubscribe(); // final consumer releases the store; StrictMode safe

Specialized chart/quote reducers can share this same connection without creating an HTTP book/history store per card:

const quotes = client.subscribe(tokenIds, {
  onMessage: frames => applyOwnPriceRules(frames),
  onReset: () => markQuotesStale(),
});
quotes.update(nextTokenIds);
quotes.close();

Callbacks receive arrays of public upstream frames. Filter multi-token price changes to the tokens your reducer owns. Keep midpoint-based chart tails distinct from live_trade samples. History views are keyed by token/range/fidelity; distinct ranges are not resampled into one another. Use history: false for depth-only views.

The connection owns heartbeat, reference-counted subscriptions and reconnect/backoff. Late depth subscribers renew only their token subscriptions on the same connection to obtain a fresh snapshot; deltas are gated until it arrives. Market resolution stops only that outcome's store. Call client.dispose() only when destroying the owning application, never when one component unmounts. Optional backend loaders follow the same unsigned envelope and AbortSignal contract described above.

Underlying sources are https://clob.polymarket.com/book, https://clob.polymarket.com/prices-history, and wss://ws-subscriptions-clob.polymarket.com/ws/market. Polymarket availability, limits and data terms apply. See its market stream documentation.

CRISP Partner Documentation