Skip to content

WebSocket Streaming

Igor Sazonov edited this page Aug 9, 2026 · 1 revision

WSClient provides a reconnecting WebSocket transport for Bitget UTA public and private streams. It maintains subscriptions, sends periodic pings, reconnects unexpected disconnects with exponential backoff, and resubscribes previously active channels after reconnecting. 1

Choose a client

Constructor Default endpoint Authentication Typical use
NewPublicWSClient(opts...) wss://ws.bitget.com/v3/ws/public None Public tickers, market data, and other public channels.
NewPrivateWSClient(apiKey, secretKey, passphrase, opts...) wss://ws.bitget.com/v3/ws/private Automatic during Connect Private fills, positions, orders, and other private channels.

For Demo WebSocket access, supply WithWSURL(bitget.DemoPublicWSURL) or WithWSURL(bitget.DemoPrivateWSURL). These constants resolve to Bitget's documented wss://wspap.bitget.com/v3/ws/public and /private demo endpoints. Demo WebSocket authentication still requires Demo API credentials. 1 2

Public subscription

The normal lifecycle is: construct the client, Connect, defer Close, call Subscribe, then read the returned channel until the parent context ends. 1 3

package main

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

    bitget "github.com/tigusigalpa/bitget-go"
    "github.com/tigusigalpa/bitget-go/models"
)

func main() {
    ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
    defer stop()

    ws := bitget.NewPublicWSClient()
    if err := ws.Connect(ctx); err != nil {
        log.Fatal(err)
    }
    defer ws.Close()

    pushes, err := ws.Subscribe(ctx, models.WSArg{
        InstType: "SPOT",
        Topic:    "ticker",
        Symbol:   "BTCUSDT",
    })
    if err != nil {
        log.Fatal(err)
    }

    for {
        select {
        case <-ctx.Done():
            return
        case push, ok := <-pushes:
            if !ok {
                return
            }
            fmt.Printf("action=%s data=%s\n", push.Action, push.Data)
        }
    }
}

Subscribe returns a receive-only channel of models.WSPush. A push includes the subscribed Arg, an Action, a raw JSON Data payload, and timestamp Ts. The raw payload shape depends on the selected Bitget channel. 1 4

Private subscription

Private clients log in automatically inside Connect. The login signature is computed over timestamp + "GET" + "/user/verify", matching Bitget's UTA WebSocket login procedure. Connect waits until login succeeds, the login times out, or the supplied context ends. 1 2

ws := bitget.NewPrivateWSClient(
    os.Getenv("BITGET_API_KEY"),
    os.Getenv("BITGET_SECRET_KEY"),
    os.Getenv("BITGET_PASSPHRASE"),
)

if err := ws.Connect(ctx); err != nil {
    return err
}
defer ws.Close()

fills, err := ws.Subscribe(ctx, models.WSArg{
    InstType: "UTA",
    Topic:    "fast-fill",
    Symbol:   "default",
})
if err != nil {
    return err
}

The current SDK includes a models.FastFill model for Bitget's fast-fill private channel. Other channels can still be subscribed through the generic transport, but callers should decode WSPush.Data using the relevant Bitget channel's documented payload. 3 4

Decode fast-fill data

Bitget's published fast-fill event example contains a single object in data. Decode the raw payload directly into models.FastFill: 5

for push := range fills {
    var fill models.FastFill
    if err := json.Unmarshal(push.Data, &fill); err != nil {
        log.Printf("decode fast-fill: %v", err)
        continue
    }

    fmt.Printf(
        "fill order=%s symbol=%s qty=%s price=%s\n",
        fill.OrderID,
        fill.Symbol,
        fill.ExecQty,
        fill.ExecPrice,
    )
}

Add the import:

import "encoding/json"
models.FastFill field Meaning
OrderID, ClientOid, ExecID Exchange order, client order, and execution identifiers.
Symbol, Category, Side, HoldSide Instrument and trade/position context.
ExecPrice, ExecQty, TradeScope Fill price, executed quantity, and maker/taker scope.
ExecTime, UpdatedTime Millisecond timestamp strings.

Subscription controls

A subscription is identified by the InstType, Topic, Symbol, and Coin fields of models.WSArg. Reusing the same argument returns the existing local buffered channel while still sending the subscribe operation. Unsubscribe(arg) removes the local subscription and closes its data channel; calling it for an unknown subscription returns nil. 1

arg := models.WSArg{InstType: "SPOT", Topic: "ticker", Symbol: "BTCUSDT"}

pushes, err := ws.Subscribe(ctx, arg)
if err != nil {
    return err
}

// Later, when the stream is no longer required:
if err := ws.Unsubscribe(arg); err != nil {
    return err
}
_ = pushes

Connection behavior and backpressure

Behavior SDK implementation Application implication
Ping Sends text ping every 25 seconds. Keep Connect active and do not assume an idle stream is disconnected.
Auto reconnect Enabled by default; retry starts at 1 second and caps at 60 seconds. Retain long-lived clients rather than rebuilding them for every transient failure.
Resubscription Sends all tracked WSArg values after a successful reconnect. Design consumers to tolerate duplicate or resumed data.
Subscription buffer 100 WSPush messages per subscription. Consume promptly; when full, the SDK drops new push messages and logs a warning.
Graceful close Close stops loops and is safe to call more than once. Always defer ws.Close() after a successful connection.

Bitget also enforces its own connection, subscription, and message-rate limits and expects a ping/pong keepalive exchange. The SDK implements its own periodic ping, but callers remain responsible for a subscription design that respects exchange limits. 1 2

WebSocket options

Option Purpose
WithWSURL(url) Override the endpoint; use this for Demo or approved Bitget alternative URLs.
WithWSLogger(logger) Receive structured connection, reconnection, decoding, and backpressure diagnostics.
WithWSAutoReconnect(false) Disable automatic reconnecting when the application must own reconnection policy.

References

Clone this wiki locally