> ## Documentation Index
> Fetch the complete documentation index at: https://docs.limitless.exchange/llms.txt
> Use this file to discover all available pages before exploring further.

# WebSocket Streaming

> Real-time market data and position updates with the Go SDK

## Overview

The `WebSocketClient` provides real-time streaming of orderbook updates, prices, order events, transactions, and market lifecycle data over a persistent WebSocket connection. It supports automatic reconnection with exponential backoff and event-driven message handling. The WebSocket client is standalone and does not require an `HttpClient`.

## Setup

```go theme={null}
import limitless "github.com/limitless-labs-group/limitless-exchange-go-sdk/limitless"

ws := limitless.NewWebSocketClient(
    // Required for authenticated channels (positions, order events, transactions).
    // The SDK signs the handshake automatically.
    limitless.WithWebSocketHMACCredentials(limitless.HMACCredentials{
        TokenID: "your_token_id",
        Secret:  "your_token_secret", // base64 secret from token creation
    }),
)
```

| Option                                | Type              | Default                       | Description                                                                                                                                       |
| ------------------------------------- | ----------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `WithWebSocketURL(url)`               | `string`          | `wss://ws.limitless.exchange` | WebSocket server URL                                                                                                                              |
| `WithWebSocketHMACCredentials(creds)` | `HMACCredentials` | --                            | HMAC credentials (`TokenID`, `Secret`) for authenticated channels; the SDK signs the handshake. See [Authentication](/developers/authentication). |
| `WithAutoReconnect(b)`                | `bool`            | `true`                        | Automatically reconnect on disconnection                                                                                                          |
| `WithReconnectDelay(d)`               | `time.Duration`   | `1s`                          | Initial delay between reconnection attempts                                                                                                       |
| `WithMaxReconnectAttempts(n)`         | `int`             | `0` (unlimited)               | Maximum reconnection attempts                                                                                                                     |
| `WithWebSocketTimeout(ms)`            | `int`             | `10000`                       | Connection timeout in milliseconds                                                                                                                |
| `WithWebSocketLogger(l)`              | `Logger`          | `NoOpLogger`                  | Logger for connection events                                                                                                                      |

## Connecting

```go theme={null}
ctx := context.Background()
err := ws.Connect(ctx)
if err != nil {
    log.Fatal(err)
}
defer ws.Disconnect()
```

### Connection States

| Constant            | Description               |
| ------------------- | ------------------------- |
| `StateDisconnected` | Not connected             |
| `StateConnecting`   | Connection in progress    |
| `StateConnected`    | Connected and ready       |
| `StateReconnecting` | Reconnecting after a drop |
| `StateError`        | Connection error          |

Check the current state with `ws.State()` or `ws.IsConnected()`.

## Event Handlers

Register handlers using `On()` for persistent handlers or `Once()` for one-time handlers. Both return a handler ID that can be used with `Off()` to unregister. The raw handler receives the message as `json.RawMessage`.

```go theme={null}
// Persistent handler
handlerID := ws.On("orderbookUpdate", func(data json.RawMessage) {
    fmt.Println("Orderbook update:", string(data))
})

// One-time handler
ws.Once("orderbookUpdate", func(data json.RawMessage) {
    fmt.Println("First orderbook update received")
})

// Remove a handler
ws.Off("orderbookUpdate", handlerID)
```

### Typed Event Handlers

The SDK provides convenience methods that automatically deserialize events into typed structs:

```go theme={null}
ws.OnOrderbookUpdate(func(update limitless.OrderbookUpdate) {
    fmt.Printf("[%s] %d bids, %d asks, midpoint %.3f\n",
        update.MarketSlug, len(update.Orderbook.Bids), len(update.Orderbook.Asks),
        update.Orderbook.AdjustedMidpoint)
})

ws.OnNewPriceData(func(price limitless.NewPriceData) {
    fmt.Printf("AMM price update for %s (block %d)\n", price.MarketAddress, price.BlockNumber)
})

// Order lifecycle (authenticated; subscribe to ChannelSubscribeOrderEvents)
ws.OnMatchedOrderEvent(func(ev limitless.MatchedOrderEvent) {
    fmt.Printf("Order matched (pre-settlement): %+v\n", ev)
})

ws.OnExecutionOrderEvent(func(ev limitless.ExecutionOrderEvent) {
    fmt.Printf("FAK/FOK terminal: %+v\n", ev)
})

ws.OnTransaction(func(tx limitless.TransactionEvent) {
    txHash := ""
    if tx.TxHash != nil {
        txHash = *tx.TxHash
    }
    fmt.Printf("Transaction %s: %s\n", txHash, tx.Status)
})

// Market lifecycle (subscribe to ChannelSubscribeMarketLifecycle)
ws.OnMarketCreated(func(ev limitless.MarketCreatedEvent) {
    fmt.Printf("Market created: %+v\n", ev)
})

ws.OnMarketResolved(func(ev limitless.MarketResolvedEvent) {
    fmt.Printf("Market resolved: %+v\n", ev)
})
```

### Available Events

| Event             | Typed Handler           | Payload               | Delivered by channel              |
| ----------------- | ----------------------- | --------------------- | --------------------------------- |
| `orderbookUpdate` | `OnOrderbookUpdate`     | `OrderbookUpdate`     | `ChannelSubscribeMarketPrices`    |
| `newPriceData`    | `OnNewPriceData`        | `NewPriceData`        | `ChannelSubscribeMarketPrices`    |
| `oraclePriceData` | `OnOraclePriceData`     | `OraclePriceData`     | `ChannelSubscribeMarketPrices`    |
| order matched     | `OnMatchedOrderEvent`   | `MatchedOrderEvent`   | `ChannelSubscribeOrderEvents`     |
| order terminal    | `OnExecutionOrderEvent` | `ExecutionOrderEvent` | `ChannelSubscribeOrderEvents`     |
| `tx`              | `OnTransaction`         | `TransactionEvent`    | `ChannelSubscribeTransactions`    |
| market created    | `OnMarketCreated`       | `MarketCreatedEvent`  | `ChannelSubscribeMarketLifecycle` |
| market resolved   | `OnMarketResolved`      | `MarketResolvedEvent` | `ChannelSubscribeMarketLifecycle` |

Position updates are delivered as a raw `positions` event — register with `ws.On("positions", func(data json.RawMessage) { ... })`.

## Subscribing to Channels

After connecting, subscribe to a channel to receive its events. CLOB orderbook updates are delivered through the market-price subscription:

```go theme={null}
err := ws.Subscribe(ctx, limitless.ChannelSubscribeMarketPrices, limitless.SubscriptionOptions{
    MarketSlugs: []string{"btc-above-100k-march-2025"},
})
if err != nil {
    log.Fatal(err)
}
```

### Public Channels (no authentication required)

| Constant                          | Description                                    |
| --------------------------------- | ---------------------------------------------- |
| `ChannelSubscribeMarketPrices`    | Orderbook + price updates for specific markets |
| `ChannelSubscribeMarketLifecycle` | Market created / resolved events               |
| `ChannelSubscribeLiveSports`      | Live sports updates                            |
| `ChannelSubscribeLiveEsports`     | Live esports updates                           |

### Authenticated Channels (require API key)

| Constant                       | Description                 |
| ------------------------------ | --------------------------- |
| `ChannelSubscribePositions`    | Your position updates       |
| `ChannelSubscribeOrderEvents`  | Your order lifecycle events |
| `ChannelSubscribeTransactions` | Your transaction updates    |

### SubscriptionOptions

| Field             | Type             | Description                  |
| ----------------- | ---------------- | ---------------------------- |
| `MarketSlug`      | `string`         | Single market slug           |
| `MarketSlugs`     | `[]string`       | Multiple market slugs        |
| `MarketAddress`   | `string`         | Single market address        |
| `MarketAddresses` | `[]string`       | Multiple market addresses    |
| `Filters`         | `map[string]any` | Additional filter parameters |

## Unsubscribing

```go theme={null}
err := ws.Unsubscribe(ctx, limitless.ChannelSubscribeMarketPrices, limitless.SubscriptionOptions{
    MarketSlugs: []string{"btc-above-100k-march-2025"},
})
```

## Auto-Reconnect

When `WithAutoReconnect(true)` is set (the default), the client automatically reconnects after a disconnection using exponential backoff with jitter (capped at 60 seconds):

1. The connection drops (network issue, server restart, etc.)
2. The client waits with exponential backoff
3. A new connection is established
4. Subscriptions must be re-established

<Note>
  Re-subscribe to your channels after reconnection. You can detect reconnection by checking `ws.State()`.
</Note>

## Complete Example

```go theme={null}
package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "os/signal"
    "syscall"

    limitless "github.com/limitless-labs-group/limitless-exchange-go-sdk/limitless"
)

func main() {
    ws := limitless.NewWebSocketClient(
        limitless.WithAutoReconnect(true),
        limitless.WithWebSocketLogger(limitless.NewConsoleLogger(limitless.LogLevelInfo)),
    )

    // Register typed event handlers before connecting
    ws.OnOrderbookUpdate(func(update limitless.OrderbookUpdate) {
        fmt.Printf("[%s] Orderbook: %d bids, %d asks, midpoint %.3f\n",
            update.MarketSlug, len(update.Orderbook.Bids), len(update.Orderbook.Asks),
            update.Orderbook.AdjustedMidpoint)
    })

    // Connect
    ctx := context.Background()
    if err := ws.Connect(ctx); err != nil {
        log.Fatal(err)
    }
    defer ws.Disconnect()

    // Orderbook updates are delivered through the market-price subscription
    if err := ws.Subscribe(ctx, limitless.ChannelSubscribeMarketPrices, limitless.SubscriptionOptions{
        MarketSlugs: []string{"btc-above-100k-march-2025"},
    }); err != nil {
        log.Fatal(err)
    }

    fmt.Println("Listening for updates... Press Ctrl+C to exit.")

    // Block until interrupt
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
    <-sigCh
}
```

<Warning>
  The `ws.Disconnect()` call cleans up the connection and clears all subscriptions. Always defer it after a successful `Connect()`.
</Warning>
