Skip to main content

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

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
    }),
)
OptionTypeDefaultDescription
WithWebSocketURL(url)stringwss://ws.limitless.exchangeWebSocket server URL
WithWebSocketHMACCredentials(creds)HMACCredentialsHMAC credentials (TokenID, Secret) for authenticated channels; the SDK signs the handshake. See Authentication.
WithAutoReconnect(b)booltrueAutomatically reconnect on disconnection
WithReconnectDelay(d)time.Duration1sInitial delay between reconnection attempts
WithMaxReconnectAttempts(n)int0 (unlimited)Maximum reconnection attempts
WithWebSocketTimeout(ms)int10000Connection timeout in milliseconds
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. The raw handler receives the message as json.RawMessage.
// 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, 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

EventTyped HandlerPayloadDelivered by channel
orderbookUpdateOnOrderbookUpdateOrderbookUpdateChannelSubscribeMarketPrices
newPriceDataOnNewPriceDataNewPriceDataChannelSubscribeMarketPrices
oraclePriceDataOnOraclePriceDataOraclePriceDataChannelSubscribeMarketPrices
order matchedOnMatchedOrderEventMatchedOrderEventChannelSubscribeOrderEvents
order terminalOnExecutionOrderEventExecutionOrderEventChannelSubscribeOrderEvents
txOnTransactionTransactionEventChannelSubscribeTransactions
market createdOnMarketCreatedMarketCreatedEventChannelSubscribeMarketLifecycle
market resolvedOnMarketResolvedMarketResolvedEventChannelSubscribeMarketLifecycle
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:
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)

ConstantDescription
ChannelSubscribeMarketPricesOrderbook + price updates for specific markets
ChannelSubscribeMarketLifecycleMarket created / resolved events
ChannelSubscribeLiveSportsLive sports updates
ChannelSubscribeLiveEsportsLive esports updates

Authenticated Channels (require API key)

ConstantDescription
ChannelSubscribePositionsYour position updates
ChannelSubscribeOrderEventsYour order lifecycle events
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.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
Re-subscribe to your channels after reconnection. You can detect reconnection by checking ws.State().

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 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
}
The ws.Disconnect() call cleans up the connection and clears all subscriptions. Always defer it after a successful Connect().