Skip to main content

Overview

The WebSocketClient provides real-time streaming of orderbook updates, trades, prices, and position 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

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

ws := limitless.NewWebSocketClient(
    limitless.WithWebSocketAPIKey("lmts_your_key_here"), // or reads LIMITLESS_API_KEY from env
)
OptionTypeDefaultDescription
WithWebSocketURL(url)stringwss://ws.limitless.exchangeWebSocket server URL
WithWebSocketAPIKey(key)stringReads LIMITLESS_API_KEY envAPI key for authenticated channels
WithAutoReconnect(b)booltrueAutomatically reconnect on disconnection
WithReconnectDelay(d)time.Duration1sInitial delay between reconnection attempts
WithMaxReconnectAttempts(n)int0 (unlimited)Maximum reconnection attempts
WithWebSocketTimeout(d)time.Duration10sConnection timeout
WithWebSocketLogger(l)LoggerNoOpLoggerLogger for connection events

Connecting

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

Connection States

ConstantDescription
StateDisconnectedNot connected
StateConnectingConnection in progress
StateConnectedConnected and ready
StateReconnectingReconnecting after a drop
StateErrorConnection 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.
// 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:
ws.OnOrderbookUpdate(func(update limitless.OrderbookUpdate) {
    fmt.Printf("[%s] %d bids, %d asks\n",
        update.MarketSlug, len(update.Orderbook.Bids), len(update.Orderbook.Asks))
})

ws.OnTrade(func(trade limitless.TradeEvent) {
    fmt.Printf("[%s] %s %.2f @ %.3f\n",
        trade.MarketSlug, trade.Side, trade.Size, trade.Price)
})

ws.OnOrder(func(order limitless.OrderUpdate) {
    fmt.Printf("Order %s: %s\n", order.OrderID, order.Status)
})

ws.OnFill(func(fill limitless.FillEvent) {
    fmt.Printf("Fill %s: %.2f @ %.3f\n", fill.FillID, fill.Size, fill.Price)
})

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

ws.OnTransaction(func(tx limitless.TransactionEvent) {
    fmt.Printf("Transaction %s: %s\n", tx.TxHash, tx.Status)
})

ws.OnMarket(func(market limitless.MarketUpdateEvent) {
    fmt.Printf("[%s] Last price: %.3f\n", market.MarketSlug, market.LastPrice)
})

Available Events

EventTyped HandlerPayloadDescription
orderbookUpdateOnOrderbookUpdateOrderbookUpdateOrderbook changes (bids/asks)
tradeOnTradeTradeEventNew trade executed
orderOnOrderOrderUpdateOrder status change
fillOnFillFillEventOrder fill event
newPriceDataOnNewPriceDataNewPriceDataAMM price update
txOnTransactionTransactionEventOn-chain transaction update
marketOnMarketMarketUpdateEventMarket data update

Subscribing to Channels

After connecting, subscribe to specific channels to receive updates:
err := ws.Subscribe(ctx, limitless.ChannelOrderbook, limitless.SubscriptionOptions{
    MarketSlugs: []string{"btc-above-100k-march-2025"},
})
if err != nil {
    log.Fatal(err)
}

Public Channels (no authentication required)

ConstantDescription
ChannelOrderbookOrderbook updates for specific markets
ChannelTradesTrade events for specific markets
ChannelMarketsMarket-level updates
ChannelPricesAMM price data
ChannelSubscribeMarketPricesSubscribe to market price updates

Authenticated Channels (require API key)

ConstantDescription
ChannelOrdersYour order status updates
ChannelFillsYour order fill events
ChannelSubscribePositionsYour position updates
ChannelSubscribeTransactionsYour transaction updates

SubscriptionOptions

FieldTypeDescription
MarketSlugstringSingle market slug
MarketSlugs[]stringMultiple market slugs
MarketAddressstringSingle market address
MarketAddresses[]stringMultiple market addresses
Filtersmap[string]anyAdditional filter parameters

Unsubscribing

err := ws.Unsubscribe(ctx, limitless.ChannelOrderbook, 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
Re-subscribe to your channels after reconnection. You can detect reconnection by checking ws.State() or using a handler on the connection event.

Complete Example

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
    ws.OnOrderbookUpdate(func(update limitless.OrderbookUpdate) {
        fmt.Printf("[%s] Orderbook: %d bids, %d asks\n",
            update.MarketSlug, len(update.Orderbook.Bids), len(update.Orderbook.Asks))
    })

    ws.OnTrade(func(trade limitless.TradeEvent) {
        fmt.Printf("[%s] Trade: %s %.2f @ %.3f\n",
            trade.MarketSlug, trade.Side, trade.Size, trade.Price)
    })

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

    // Subscribe to orderbook and trades
    if err := ws.Subscribe(ctx, limitless.ChannelOrderbook, limitless.SubscriptionOptions{
        MarketSlugs: []string{"btc-above-100k-march-2025"},
    }); err != nil {
        log.Fatal(err)
    }

    if err := ws.Subscribe(ctx, limitless.ChannelTrades, 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
}
The ws.Disconnect() call cleans up the connection and clears all subscriptions. Always defer it after a successful Connect().