# WebSockets

> Stream market data and your account over one socket.

Source: https://docs.stxapp.io/sdks/typescript/websockets/

The client streams the channels documented under [WebSockets](/websockets/): what each channel sends, its payloads and its limits are covered there. This page shows the SDK calls.

## Connect and join

```ts
import { STX } from "@stxapp/stx-typescript";

const client = new STX();
const ws = await client.websocket().connect();

const ticker = await ws.ticker();
console.log(ticker.reply);

await ws.close();
```

`client.websocket()` uses the client's host and key, and signs the handshake with the key. Connecting joins nothing; each join method returns a `Channel`. A client with no API key connects unsigned: the market data channels accept that, and the account channels refuse the join.

## Receive messages

Each message has `topic`, `event` and `payload`. Take them with a callback, one at a time, or with `for await`:

```ts
const ids = (await client.markets({ status: "open", limit: 5 })).items.map((m) => m.market_id!);

// A callback
await ws.trades({ marketIds: ids, onMessage: (msg) => console.log(msg.event, msg.payload) });

// One message, with a timeout in milliseconds
const book = await ws.orderbook(ids);
try {
  const msg = await book.next(5_000);
  console.log(msg.payload.market_id);
} catch {
  console.log("nothing in 5 s");
}

// Every message, until the channel closes
for await (const msg of book) {
  console.log(msg.payload.market_id, msg.payload.bids[0]);
}
```

Money fields are dollar strings on every channel, including the ones that send cents on the wire.

## Snapshots

Account channels send your current state right after the join. `waitSnapshot()` returns it, keyed by event name:

```ts
const positions = await ws.positions();
const snap = await positions.waitSnapshot(10_000);
console.log(snap.all_positions.positions);
```

## Channels

| SDK call | Channel |
|---|---|
| `ws.orderbook(marketIds)` | [Order book, ticker and trades](/websockets/channels/market-data/) |
| `ws.ticker({ sports, competitions })` | [Order book, ticker and trades](/websockets/channels/market-data/) |
| `ws.trades({ marketIds, eventIds })` | [Order book, ticker and trades](/websockets/channels/market-data/) |
| `ws.market(marketId)` | [Market order book](/websockets/order-book/) |
| `ws.markets({ ruleFilters, messageTypes })` | [Markets](/websockets/channels/markets/) |
| `ws.marketStats(marketIds, { range })` | [Market stats](/websockets/channels/market-stats/) |
| `ws.marketUpdates({ watch })` | [Market updates](/websockets/channels/market-updates/) |
| `ws.orders({ marketIds, cancelOnDisconnect, pingTimeout })` | [Orders](/websockets/channels/orders/) |
| `ws.fills({ marketIds })` | [Fills](/websockets/channels/fills/) |
| `ws.positions({ marketIds })` | [Positions](/websockets/channels/positions/) |
| `ws.settlements({ marketIds })` | [Settlements](/websockets/channels/settlements/) |
| `ws.balances({ accountId })` | [Balances](/websockets/channels/balances/) |
| `ws.account({ marketIds })` | [Account](/websockets/channels/account/) |
| `ws.userInfo()` | [User info](/websockets/channels/user-info/) |

Every join method also takes `onMessage`. To change filters after joining, call `channel.selectMarketIds([...])` (and `selectFilters`, `selectRuleFilters`, `selectMessageTypes`); on `market_updates`, `channel.watch([...])`. `ws.join(topic, payload)` joins any other topic.

## Live account view

`ws.accountView()` keeps your balance, open orders, fills and positions current. It joins the `balances`, `orders`, `fills` and `positions` channels, starts from their snapshots, applies every update, and starts over from fresh snapshots after a reconnect. It resolves once all four snapshots are in.

```ts
const ws = await client.websocket().connect();

const view = await ws.accountView({
  onChange: (change) => render(change.view.state()), // change.kind: "balance" | "orders" | "fills" | "positions" | "payment"
});

console.log(view.balance?.available_balance, view.openOrders.length, view.fills.length, view.positions.length);

// No refetch needed: the new order and the balance change arrive on the socket.
await client.placeOrder(marketId, "buy", "limit", { price: "0.01", quantity: "1" });
```

`view.state()` returns plain data you can send to a browser. Options: `marketIds`, `accountId`, `maxFills` (default 1000), `keepFlatPositions`, `snapshotTimeoutMs` (default 15000). `view.close()` leaves the four channels and keeps the socket open.

## Reconnects

The client sends the heartbeat and channel pings for you. After a drop it reconnects with backoff, signs again, and rejoins every channel with its current filters. It cannot know what you missed while offline, so read again anything you loaded with a client method (open orders, history) in `onReconnect`:

```ts
import { ReconnectPolicy, STX } from "@stxapp/stx-typescript";

const client = new STX();
const ws = await client
  .websocket({
    reconnectPolicy: new ReconnectPolicy({ initialBackoffMs: 500, maxBackoffMs: 30_000, maxAttempts: 20 }),
    onReconnect: async () => {
      const open = await client.orders({ status: ["open", "delayed"] });
      console.log("reconnected, open orders:", open.length);
    },
  })
  .connect();
```

`reconnect: false` turns reconnecting off. `ws.connected` and `ws.reconnects` report the socket's state. [After a reconnect](/websockets/#after-a-reconnect) explains what the exchange does and does not replay.

## Errors

A refused join throws `STXChannelException`, with the server's reason in `err.reply`. No reply within `joinTimeoutMs` (default 10000) throws `STXTimeoutException`.

## Keep a worker running

`runForever()` resolves when you call `close()` or the socket gives up:

```ts
const ws = await client.websocket().connect();
await ws.fills({ onMessage: (m) => console.log("fill", m.payload) });
process.on("SIGINT", () => void ws.close());
await ws.runForever();
```
