Skip to main content
A short, opinionated guide to getting the most out of Struct. The patterns below cover latency, cost, resilience, and security in roughly that order.

Take advantage of pre-computed data

Struct’s defining feature is that every derived value (PnL, leaderboards, positions, market metrics, event metrics, tag metrics, condition metrics) is materialised on write, not computed on read. Rows are updated as on-chain events arrive, never on a periodic cycle, so values stay current with the chain. A query that would otherwise scan hundreds of thousands of trades is a single-row lookup. Every pattern below flows from that:
  • Stream metrics, not trades, when you want aggregates. If your UI shows volume, holders, or trader counts, subscribe to polymarket_market_metrics (or event/position/tag/condition metrics) instead of polymarket_trades and aggregating yourself. One materialised update per change beats N raw trade messages per change, often by 10-100x.
  • Use alerts for “wake me when X” logic. If you only care about probability moves above a threshold, a whale trade, or a volume milestone, subscribe to the matching alert instead of a raw stream. Alerts fire only on the crossing, so you stop paying for the messages your code would have filtered out anyway.
  • Pace the snapshot streams to your UI. polymarket_markets_stream and polymarket_events_stream deliver fresh snapshots at your chosen interval_ms. Tune the interval to what you actually render (1000ms for a live table, 5000ms for a sidebar). The bill scales linearly with the rate.
  • Narrow filters as your UI narrows. A subscription without filters on a busy room delivers every upstream event. After a user picks a market or a watchlist shrinks, send a fresh subscribe to drop everything you no longer care about. Wider filters cost more, full stop.

Stream, don’t poll

Anything you’d refresh more than once a minute should be a websocket subscription, not a REST poll. Server-side filtering means you only pay for the messages you actually consume, and push latency is in single-digit milliseconds.
If you’re polling…Subscribe to…
Markets list every few secondspolymarket_markets_stream
Trader PnL every minutepolymarket_trader_pnl
Order book snapshotspolymarket_order_book
Trade history for a marketpolymarket_trades (filter on condition_ids)
Crypto prices for an assetpolymarket_asset_prices
See WebSockets for the full room catalogue.

Pool subscriptions on one connection

A single websocket can subscribe to every room. The 1-credit connection hold is per-connect, so:
  • One socket per process is usually enough for backend workloads.
  • One socket per logged-in user is enough for in-app alerts and per-user dashboards.
  • Avoid opening, closing, and reopening sockets in a tight loop. Every reconnect re-incurs the hold.

Filter at subscribe time

Every room takes filter fields on subscribe. Server-side filtering is free; once a message arrives at your client you’ve already been billed for it.
await ws.subscribe("polymarket_trades", {
  condition_ids: ["0xabc..."],
  trade_types: ["OrderFilled"],
});
If you need to widen a subscription, send a fresh subscribe message; the new filters replace the previous set.

Reconnect with exponential backoff

Subscriptions are bound to the connection. When the socket drops, you must reconnect and resubscribe.
  • Use exponential backoff with jitter. Start around 500ms, double on each attempt, cap at 30s.
  • Never retry on auth failures (close codes 1008 and 4001). Fix credentials first.
  • Reset the backoff counter on a successful connect.
The TypeScript SDK does all of this for you, including replaying every active subscription on reconnect. See SDK WebSockets.

Use JWT public keys in the browser

sk_live_* keys grant full org access. Never ship them in a frontend bundle, mobile app, or anywhere a user can read them. For client-side calls, mint a pk_jwt_* public key in the dashboard, point it at your auth provider’s JWKS URL, and pass each user’s JWT alongside the key. The key is safe to embed in your bundle, and per-user rate limits keyed on the JWT sub claim prevent one user from burning your whole quota. See Authentication.

Handle pagination cleanly

List endpoints are cursor-paginated with pagination_key. Iterate until pagination.has_more is false.
for await (const market of client.markets.list({ limit: 200 })) {
  // ...
}
The TypeScript SDK exposes an async iterator on every paginated endpoint. See Pagination.

Cache long-lived data

Market metadata (slug, image, outcomes), event metadata, and tag lists rarely change. Cache them in your application layer for at least a few minutes; you can refresh on a websocket event or a TTL whichever you prefer. Volatile values (price, PnL, positions, volume) should not be cached; subscribe to the matching room instead.

Pick the right surface for the access pattern

Most cost regressions come from using the wrong product, not the wrong filter. Match the surface to how often you need the data and how you react to it.
You need to…UseWhy
Read a single value on demandREST1 credit, single-row lookup.
Render a live view of changing valuesWS metrics roomsPre-computed deltas, no client-side aggregation.
React to a threshold crossingAlerts or webhooksFires only on the event you care about.
Backfill or paginate historyREST with pagination_keyCursor pagination, batch-friendly.
Receive an event when you’re offlineWebhooksQueued and retried server-side; no socket required.
Per-message rates also vary by room. A higher per-message rate often correlates with lower frequency: Trader PnL costs 0.1 credits per message but only fires when a tracked trader actually trades, while Order Book at 0.001 credits per message can produce thousands of updates per minute on an active market. Don’t pick by sticker price alone, model the expected message rate first. See WebSocket Pricing for the rate card and worked examples.

Verify webhook signatures

Every webhook delivery is signed with HMAC SHA-256 using the secret shown when you create the webhook. Always verify the signature on receipt before trusting the payload. Rotate the secret when staff leave or if you suspect a leak. See Webhooks for the verification snippet.
Last modified on May 27, 2026