Client Classes
Auth, Data, Trading, Stream and the Trading WebSocket — initialization and usage
The SDK provides 4 specialized clients (Auth, Data, Trading, Stream), plus a Trading WebSocket client. Go uses goroutines natively for concurrency — no separate async variants needed.
The module path is github.com/SSI-Securities-Inc/ssi-sdk-go/v3 and its root package is named ssisdk (it holds the error types, see Error Handling). The clients are created through the ssi package. Go 1.22 or later is required.
Every duration in the SDK is an integer in milliseconds with an Ms suffix.
Auth
Root client — manages REST client, authentication, and tokens. All other clients receive *Auth as a parameter.
import (
"log"
"os"
"github.com/SSI-Securities-Inc/ssi-sdk-go/v3/ssi"
)
config := ssi.NewConfig("YOUR_CLIENT_ID")
config.APIKey = os.Getenv("SSI_API_KEY")
config.APISecret = os.Getenv("SSI_API_SECRET")
config.PrivateKey = os.Getenv("SSI_PRIVATE_KEY")
auth := ssi.NewAuth(config)
defer auth.Close()
token, err := auth.Authenticate("YOUR_OTP")
if err != nil {
log.Fatal(err)
}
// Do not log token.AccessToken.
_ = tokenAuthentication methods
// Request OTP (raw map)
result, err := auth.RequestOTP()
// Request OTP (typed: *auth.OTPResponse with Message and TransactionID)
otpRes, err := auth.RequestOTPTyped()
// Authenticate with OTP
token, err := auth.Authenticate("YOUR_OTP")
// Authenticate without OTP (market data only)
token, err := auth.Authenticate("")
// Refresh token
token, err := auth.Refresh()
// Authenticate or refresh as needed; returns the access token string
accessToken, err := auth.EnsureAuthenticated("YOUR_OTP")EnsureAuthenticated(otp, transactionID...) returns the access token as a string (not a *Token). EnsureAuthenticatedWith(ctx, auth.EnsureOptions{...}) takes a context and lets you set OTP, TransactionID, PollIntervalMs and PollMaxRetries (both are *int).
Approve OTP (Smart OTP push-approval)
Accounts with Smart OTP enabled can approve directly in the app instead of entering a code. Call RequestOTPTyped() to send the request and get a TransactionID, then pass it to EnsureAuthenticated() — the SDK polls automatically until the user approves.
// Step 1: send the approval request, get the transaction id
otpRes, err := auth.RequestOTPTyped()
if err != nil {
log.Fatal(err)
}
// Step 2: the SDK polls until the user approves in the Smart OTP app
// (otp is "" since we're using the transaction id instead)
accessToken, err := auth.EnsureAuthenticated("", otpRes.TransactionID)While waiting, the server returns HTTP 202 + code 401114 ("Push-approval is pending") — the SDK handles this automatically. The poll interval and maximum wait come from Config.OTPPollIntervalMs (5000) and Config.OTPPollMaxWaitMs (60000); EnsureAuthenticatedWith can override them per call. If the attempts run out without approval, EnsureAuthenticated returns an authentication error.
Token status
auth.TokenManager.IsTokenExpired() // true/false (treats a token within 30 s of expiry as expired)
auth.AccessToken() // access token string
auth.Token() // *auth.Token, or nil
auth.HasRefreshToken() // a refresh token is held
auth.CanRefresh() // the refresh token is still validData
Market data client. No OTP needed — just auth.Authenticate("").
import "github.com/SSI-Securities-Inc/ssi-sdk-go/v3/ssi"
auth := ssi.NewAuth(config)
defer auth.Close()
if _, err := auth.Authenticate(""); err != nil {
log.Fatal(err)
}
data := ssi.NewData(auth)
ohlc, err := data.MarketData.GetOHLC1Minute("SSI")
indexes, err := data.MarketData.GetIndexes()
info, err := data.MarketData.GetSecuritiesInfo("SSI")
master, err := data.MarketData.GetMasterData() // ceiling, floor and reference price
actions, err := data.MarketData.GetCorporateActions() // corporate actions of todayService: data.MarketData (*market.Service) — OHLC, indexes, index/board summaries, securities, securities summary, master data, corporate actions. See API Services.
Trading
Trading, account, and portfolio client. OTP required.
import (
"github.com/SSI-Securities-Inc/ssi-sdk-go/v3/ssi"
"github.com/SSI-Securities-Inc/ssi-sdk-go/v3/trading"
)
auth := ssi.NewAuth(config)
defer auth.Close()
if _, err := auth.Authenticate("YOUR_OTP"); err != nil {
log.Fatal(err)
}
t := ssi.NewTrading(auth)
// Account
accounts, err := t.Account.GetAccountInfo()
// Portfolio (a getter returns nil when there is no data: check before use)
balance, err := t.Portfolio.GetEquityBalance("YOUR_ACCOUNT_NO")
positions, err := t.Portfolio.GetEquityPositions("YOUR_ACCOUNT_NO")
orders, err := t.Portfolio.GetTodayOrders("YOUR_ACCOUNT_NO")
// Trading
result, err := t.Trading.PlaceLimitOrder(
"YOUR_ACCOUNT_NO", "SSI", trading.OrderSideBuy, 100, 66000,
)Services:
t.Account(*account.Service) — account information.t.Portfolio(*portfolio.Service) — balances, positions, order book, PPMMR.t.Trading(*trading.Service) — place/modify/cancel orders, batch orders, max buy/sell, conditional orders (FCO).
Trading client method groups (full parameters in API Services):
| Group | Methods |
|---|---|
| Place | PlaceOrder, PlaceLimitOrder, PlaceMarketOrder, PlaceATOOrder, PlaceATCOrder |
| Modify / cancel | ModifyOrderPrice, ModifyOrderQuantity, ModifyOrderPriceByOrderID, ModifyOrderQuantityByOrderID, CancelOrder, CancelOrderByOrderID |
| Batch | PlaceBatchOrders, CancelBatchOrders — see Batch Orders |
| Max buy/sell | GetMaxBuySell, GetMaxBuySellAtMarketPrice |
| Conditional (FCO) | PlaceFcoGtd, PlaceFcoStop, PlaceFcoStopLimit, PlaceFcoTrailingStop, PlaceFcoTrailingStopLimit, PlaceFcoOco, PlaceFcoBullBear, CancelFco, GetFcoByAccountNo, GetFcoBySymbol, GetFcoByStatus, GetFcoByStatuses, GetFcoByDate, GetFcoById, GetFcoOrderBook, GetFcoStatusHistory |
Order and FCO methods take optional trailing options: trading.WithClientRequestID(id) for orders and trading.WithOTPCode(code) for FCO place/cancel.
Stream
Realtime streaming client via WebSocket. OTP required.
import (
"fmt"
"log"
"github.com/SSI-Securities-Inc/ssi-sdk-go/v3/ssi"
"github.com/SSI-Securities-Inc/ssi-sdk-go/v3/stream"
)
auth := ssi.NewAuth(config)
defer auth.Close()
if _, err := auth.Authenticate("YOUR_OTP"); err != nil {
log.Fatal(err)
}
s := ssi.NewStream(auth)
defer s.Disconnect()
// Register callbacks
s.Streaming.SetOnData(func(msg interface{}) {
switch m := msg.(type) {
case *stream.TradeMessage:
fmt.Printf("[TRADE] %s | %.0f | %d\n", m.Symbol, m.Price, m.Quantity)
case *stream.QuoteMessage:
fmt.Printf("[QUOTE] %s\n", m.Symbol)
case *stream.IndexTickMessage:
if m.Value != nil {
fmt.Printf("[INDEX] %s | %.2f\n", m.Symbol, *m.Value)
}
}
})
s.Streaming.SetOnTrading(func(msg interface{}) {
switch m := msg.(type) {
case *stream.OrderStatusMessage:
fmt.Printf("[ORDER] %s %s | %s\n", m.Symbol, m.Side, m.Status)
case *stream.OrderMatchMessage:
fmt.Printf("[MATCH] %s | %d @ %.0f\n", m.Symbol, m.MatchQty, m.MatchPrice)
}
})
// Connect and subscribe
if err := s.Connect(); err != nil {
log.Fatal(err)
}
s.Streaming.SubscribeSymbol([]string{"SSI", "HPG"}, nil)
s.Streaming.SubscribeIndexTrade([]string{"VNINDEX"}, stream.IntervalTick, nil)
s.Streaming.SubscribeOrderStatus("", nil) // "" = all accounts of the token
s.Wait(nil) // nil = wait until disconnectedService: s.Streaming (*stream.Service) — subscribe/unsubscribe realtime data. The facade *ssi.Stream has Connect(), Disconnect(), IsConnected() and Wait(timeoutMs *int).
The stream reconnects and resubscribes by itself when Config.AutoReconnect is true (the default). Watch the state with SetOnConnection, see Connection events.
Callbacks:
| Method | Callback Signature | Description |
|---|---|---|
SetOnData | func(interface{}) | Every DATA-channel message, parsed to a typed value (use a type switch) |
SetOnTrading | func(interface{}) | Every trading-channel event (order status, fills, portfolio, FCO) |
SetOnHeartbeat | func(*stream.HeartbeatMessage) | Receives heartbeat |
SetOnMarketFlag | func(*stream.MarketFlagMessage) | Only market session flags |
SetOnIndexSummary | func(*stream.IndexSummaryMessage) | Only index summaries |
SetOnOrder | func(*stream.OrderStatusMessage) | Only order status events |
SetOnOrderMatch | func(*stream.OrderMatchMessage) | Only fill events |
SetOnPortfolio | func(*stream.PortfolioMessage) | Only portfolio events (derivative accounts) |
SetOnConnection | func(transport.ConnectionEvent) | Connection state changes |
Typed callbacks only receive their own type and do not subscribe to anything; you still call the matching Subscribe* method. A callback that panics is recovered and does not affect the others. Index ticks have no dedicated callback: use SetOnData and a type switch. The subscribe methods, topic and interval rules are in API Services, section 6.
Trading WebSocket client
The tradingws package is not wired into the Trading / Stream facades; create the client from an *ssi.Auth.
tradingws.TradingWS connects to the Trading WebSocket (/ws/v3/trading) and returns a *tradingws.Service that sends orders, runs queries and receives order events on one socket. Unlike the four clients above, it is created from an *ssi.Auth and a context.Context, and connects immediately.
Until 3.3.1 this package was called
experimental. If you upgrade, change the import to.../v3/tradingwsandexperimental.totradingws., see the migration note.
import (
"context"
"log"
"github.com/SSI-Securities-Inc/ssi-sdk-go/v3/ssi"
"github.com/SSI-Securities-Inc/ssi-sdk-go/v3/tradingws"
)
auth := ssi.NewAuth(config) // config.PrivateKey is required to sign orders
defer auth.Close()
if _, err := auth.Authenticate("YOUR_OTP"); err != nil {
log.Fatal(err)
}
ctx := context.Background()
ws, err := tradingws.TradingWS(ctx, auth)
if err != nil {
log.Fatal(err)
}
defer ws.Close() // required: closes the socket and wakes every waiting requestPoints to know:
- It takes its defaults from
Config(TradingWSDomain,TimeoutMs,UserAgent,PrivateKey); onlywss://URLs are accepted. - There is no automatic reconnect: call
ws.Client().Connect(ctx)and subscribe again. - Methods, options, behaviour and examples: Trading WebSocket. Settings: Configuration.
Full example
package main
import (
"fmt"
"log"
"os"
"github.com/SSI-Securities-Inc/ssi-sdk-go/v3/ssi"
"github.com/SSI-Securities-Inc/ssi-sdk-go/v3/stream"
)
func main() {
config := ssi.NewConfig(os.Getenv("SSI_CLIENT_ID"))
config.APIKey = os.Getenv("SSI_API_KEY")
config.APISecret = os.Getenv("SSI_API_SECRET")
config.PrivateKey = os.Getenv("SSI_PRIVATE_KEY")
auth := ssi.NewAuth(config)
defer auth.Close()
if _, err := auth.Authenticate(os.Getenv("SSI_OTP")); err != nil {
log.Fatal(err)
}
// Market data
data := ssi.NewData(auth)
ohlc, err := data.MarketData.GetOHLC1Minute("SSI")
if err != nil {
log.Fatal(err)
}
fmt.Println(ohlc)
// Trading
t := ssi.NewTrading(auth)
accounts, err := t.Account.GetAccountInfo()
if err != nil {
log.Fatal(err)
}
fmt.Println(accounts)
// Streaming
s := ssi.NewStream(auth)
defer s.Disconnect()
s.Streaming.SetOnData(func(msg interface{}) {
switch m := msg.(type) {
case *stream.TradeMessage:
fmt.Printf("[TRADE] %s | %.0f | %d\n", m.Symbol, m.Price, m.Quantity)
}
})
s.Streaming.SetOnTrading(func(msg interface{}) {
fmt.Printf("[TRADING] %v\n", msg)
})
if err := s.Connect(); err != nil {
log.Fatal(err)
}
s.Streaming.SubscribeSymbol([]string{"SSI", "HPG"}, nil)
s.Streaming.SubscribeOrderStatus("", nil)
timeoutMs := 30000 // 30 seconds
s.Wait(&timeoutMs)
}Notes
- Go SDK does not have separate async variants — use goroutines for concurrency.
- All clients share the HTTP connection via
Auth. - Always use
defer auth.Close()anddefer s.Disconnect()for cleanup (anddefer ws.Close()for the Trading WebSocket client). ssi.NewClientis deprecated: usessi.NewAuthplusNewData/NewTrading/NewStream.- Read secrets and the OTP from environment variables or a secret store; do not hard-code them or log tokens.
- Classify errors with
errors.Asagainst the types in thessisdkpackage:*ssisdk.AuthenticationError(auth),*ssisdk.WebSocketError(stream),*ssisdk.TradingWSError(Trading WebSocket),*ssisdk.APIError(HTTP business errors). See Error Handling.