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
}),
)
| 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. |
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
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.
// 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
| 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:
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
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):
- The connection drops (network issue, server restart, etc.)
- The client waits with exponential backoff
- A new connection is established
- 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().