Skip to main content

Fast Order Response SBE

MMWS only

This channel is available only via your dedicated Market Maker WebSocket (MMWS) host. It is not accessible from standard WebSocket endpoints.

Overview

The Fast Order SBE channel provides ultra-low-latency order push updates for HFT clients through the Market Maker WebSocket (MMWS). It delivers binary-encoded SBE messages directly from the matching engine for fast order submission, amendment, and cancellation acknowledgements.

This channel is designed for speed and efficiency — prioritizing the latest updates and omitting passive events such as fills or system-initiated cancellations.

Release Schedule

ProductTestnetMainnet
SpotMay 27, 2026June 23, 2026
Futures (linear & inverse)June 9, 2026July 9, 2026
OptionsJune 16, 2026July 21, 2026

Connection

EnvironmentURL
Testnetwss://stream-testnet.bybits.org/v5/private-sbe
Mainnetwss://<your-dedicated-MMWS-host>.bybit-aws.com/v5/private-sbe
  • SBE messages are sent as binary frames (opcode = 2).
  • Control frames (auth, ping/pong, subscribe/unsubscribe) use the standard Bybit V5 API JSON format.

Authentication

Authentication is required immediately after establishing a connection.

Auth Request

{
"req_id": "10001",
"op": "auth",
"args": [
"api_key",
1662350400000,
"signature"
]
}
FieldDescription
req_idOptional client identifier
args[1]Timestamp — must be greater than current time
args[2]Generated using the Bybit API signing algorithm

Auth Success Response

{
"success": true,
"ret_msg": "",
"op": "auth",
"conn_id": "cejreaspqfh3sjdnldmg-p"
}

Heartbeat

Send Ping

{"req_id": "100001", "op": "ping"}

Receive Pong

{
"success": true,
"ret_msg": "pong",
"conn_id": "465772b1-7630-4fdc-a492-e003e6f0f260",
"req_id": "100001",
"op": "ping"
}

Subscription

Available Topics

TopicDescription
order.sbe.resp.spotSpot fast order response
order.sbe.resp.linearLinear (USDT/USDC) fast order response
order.sbe.resp.inverseInverse fast order response
order.sbe.resp.optionOptions fast order response

Subscribe Example

{
"op": "subscribe",
"args": ["order.sbe.resp.linear", "order.sbe.resp.spot", "order.sbe.resp.option"]
}

Subscription Acknowledgment

{
"success": true,
"ret_msg": "",
"conn_id": "d30fdpbboasp1pjbe7r0",
"req_id": "abc123",
"op": "subscribe"
}

Push Logic

fast.resp.order messages are actively pushed to clients when the order is initiated by the user (active trading actions: place, amend, cancel).

info

Upon channel restart or re-subscription, pushes start from the latest matching event — the focus is speed, not backfill.

Scenario / EventPush via Fast Order ChannelNotes
Maker order new (accepted / ack)✅ YesAll active actions initiated by client (place / amend / cancel / reject).
Maker order filled / partial filled✅ YesAll active actions initiated by client (place / amend / cancel / reject).
Taker order (active side)✅ YesAll active actions initiated by client (place / amend / cancel / reject).
COT (CloseOnTrigger) order✅ Yes (for triggered order)Triggered COT order acts like a new taker order; if it opens opposite side, orderLinkId="".
RO / ReduceOnly order✅ YesNormal push; if rejected due to cost or position box, rejectReason populated.
Condition / TP-SL triggered order✅ YesOnce condition triggers and order becomes active, it's pushed. orderLinkId="" (empty).
DCP (Disconnect All Protection)✅ YesPushes when DCP forcibly cancels orders on disconnect.
SMP cancel-taker / Cancel Both (Self Match Protection)✅ YesBoth taker/maker side cancellations will be pushed.
SMP cancel-maker✅ YesBoth taker/maker side cancellations will be pushed.
MMP (Market Maker Protection)✅ YesMMP trigger cancels also pushed in fast order channel.
Delist / Contract expiry / Option delivery❌ NoSystem-initiated close; no fast order push.
Order reject (matching / validation reject)✅ YesPushed immediately with rejectReason.
Amend success / reject✅ YesActive amend ack / reject are pushed.
Cancel success / reject✅ YesActive cancel ack / reject are pushed.

Differences from Standard WebSocket Order Channel

The table below highlights behavioral differences between the Fast Order SBE channel (order.sbe.resp.*) and the standard private WebSocket Order channel.

ScenarioFast Order ChannelStandard WS Order ChannelNotes
Conditional order — place / amend / cancel (before trigger)❌ No push✅ PushConditional orders are not sent to the matching engine before being triggered; the matching engine has no pre-trigger order state to push.
TP/SL order — place / amend / cancel (before trigger)❌ No push✅ PushSame reason as above.
Trailing stop order — place / amend / cancel (before trigger)❌ No push✅ PushSame reason as above.
Position liquidation order❌ No push✅ PushNote: liquidation-triggered cancellations are consistent between both channels.
Contract delist cancellation❌ No push✅ PushDelist cancellations are handled internally and not forwarded to the matching engine.
Amend / cancel an order that was fully filled before the request arrivedFast Order: orderStatus=RejectedStandard WS: orderStatus=Fillede.g. qty=10, amend to 12, but 10 lots already filled — Fast Order returns the amend/cancel as Rejected; Standard WS returns Filled.
Normal place / amend / cancel — leavesValue fieldNo value (0)Has valueFast Order always returns 0 for leavesValue on non-spot-market-buy-order-by-value orders.
Pre-market call auction — cancel rejectedorderStatus=RejectedorderStatus=NewDuring the pre-market call auction phase, cancel requests are rejected; Fast Order reflects Rejected while Standard WS reflects New.

OrderLinkId Behavior by Version

Scenario2026 Testnet / MainnetNotes
Active new order (user-initiated)✅ PresentClient-initiated place includes user's orderLinkId.
Amend / Cancel (user-initiated)✅ PresentClient-initiated place includes user's orderLinkId.
Maker→Taker transition (e.g. price amend crosses book)✅ PresentClient-initiated place includes user's orderLinkId.
Active new conditional order (user-initiated)✅ PresentClient-initiated place includes user's orderLinkId.
Position set trading stop order❌ EmptySystem-created, no orderLinkId.

Message Structure (SBE)

templateId = 21000 (FastOrderResp)

Message Header (8 bytes)

FieldTypeSize (bytes)Description
blockLengthuint162Message body length
templateIduint162Fixed = 21000
schemaIduint162Fixed = 1
versionuint162Fixed = 0

Message Body

IDFieldTypeDescription
1categoryuint81=spot, 2=linear, 3=inverse, 4=option
2sideuint81=Buy, 2=Sell
3orderStatusuint8Order state enum. 0=Others, 4=PartiallyFilledAndCancelled, 5=Rejected, 6=New, 7=Cancelled, 8=PartiallyFilled, 9=Filled
4priceExponentint8Decimal places for price. price = mantissa / 10^priceExponent
5sizeExponentint8Decimal places for size
6valueExponentint8Decimal places for value
7rejectReasonuint160 if N/A. See rejectReason mapping
8priceint64Price mantissa (apply priceExponent)
9leavesQtyint64Remaining quantity mantissa (apply sizeExponent)
10leavesValueint64Spot market buy only; otherwise 0 (apply valueExponent)
11creationTimeint64Order creation timestamp in Fast Order channel (microseconds)
12updatedTimeint64Matching timestamp (microseconds)
13seqint64Cross sequence ID
14symbolIDint32Symbol ID
100orderIdvarString8Order ID (UUID)
101orderLinkIdvarString8Optional; present for user-initiated orders

rejectReason Mapping

CodeName
0EC_NoError
1EC_Others
2EC_UnknownMessageType
3EC_MissingClOrdID
4EC_MissingOrigClOrdID
5EC_ClOrdIDOrigClOrdIDAreTheSame
6EC_DuplicatedClOrdID
7EC_OrigClOrdIDDoesNotExist
8EC_TooLateToCancel
9EC_UnknownOrderType
10EC_UnknownSide
11EC_UnknownTimeInForce
12EC_WronglyRouted
13EC_MarketOrderPriceIsNotZero
14EC_LimitOrderInvalidPrice
15EC_NoEnoughQtyToFill
16EC_NoImmediateQtyToFill
17EC_QtyCannotBeZero
18EC_PerCancelRequest
19EC_MarketOrderCannotBePostOnly
20EC_PostOnlyWillTakeLiquidity
21EC_CancelReplaceOrder
22EC_InvalidSymbolStatus
23EC_MarketOrderNoSupportTIF
24EC_ReachMaxTradeNum
25EC_InvalidPriceScale
26EC_BitIndexInvalid
27EC_StopBySelfMatch
28EC_BySelfMatch
29EC_InvalidSmpType
30EC_CancelByMMP
31EC_InCallAuctionStatus
34EC_InvalidUserType
35EC_InvalidMirrorOid
36EC_InvalidMirrorUid
37EC_SymbolNotExist
38EC_CancelNoActiveOrders
39EC_MissingUID
100EC_EcInvalidQty
101EC_InvalidAmount
102EC_LoadOrderCancel
103EC_CancelForNoFullFill
104EC_MarketQuoteNoSuppSell
105EC_DisorderOrderID
106EC_InvalidBaseValue
107EC_LoadOrderCanMatch
108EC_SecurityStatusFail
110EC_ReachRiskPriceLimit
111EC_OrderNotExist
112EC_CancelByOrderValueZero
113EC_CancelByMatchValueZero
200EC_ReachMarketPriceLimit

SBE XML Template (Fast Order Response)

<?xml version="1.0" encoding="UTF-8"?>
<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
xmlns:mbx="https://bybit-exchange.github.io/docs/v5/intro"
package="order.fast.sbe"
id="1"
version="0"
semanticVersion="1.0.0"
description="Bybit fast order response SBE schema"
byteOrder="littleEndian"
headerType="messageHeader">
<types>
<composite name="messageHeader" description="Template ID and length of message root">
<type name="blockLength" primitiveType="uint16"/>
<type name="templateId" primitiveType="uint16"/>
<type name="schemaId" primitiveType="uint16"/>
<type name="version" primitiveType="uint16"/>
</composite>
<composite name="varString8" description="Variable length UTF-8 string">
<type name="length" primitiveType="uint8"/>
<type name="varData" length="0" primitiveType="uint8" semanticType="String" characterEncoding="UTF-8"/>
</composite>
</types>
<!-- Fast order response: active place/cancel/amend acknowledgements -->
<sbe:message name="FastOrderResp" id="21000">
<!-- Routing / classification -->
<field id="1" name="category" type="uint8" description="1=spot, 2=linear, 3=inverse, 4=option"/>
<!-- Side / status / rejection -->
<field id="2" name="side" type="uint8" description="1=Buy, 2=Sell"/>
<field id="3" name="orderStatus" type="uint8" description="Order state enum"/>
<!-- Price / size (mantissas) with exponents -->
<field id="4" name="priceExponent" type="int8" description="Decimal places for price"/>
<field id="5" name="sizeExponent" type="int8" description="Decimal places for size"/>
<field id="6" name="valueExponent" type="int8" description="Decimal places for value"/>
<field id="7" name="rejectReason" type="uint16" description="0 if N/A"/>
<field id="8" name="price" type="int64" mbx:exponent="priceExponent" description="Price mantissa"/>
<field id="9" name="leavesQty" type="int64" mbx:exponent="sizeExponent" description="Remaining quantity mantissa"/>
<field id="10" name="leavesValue" type="int64" mbx:exponent="valueExponent" description="Spot market buy only; otherwise 0"/>
<!-- Timing -->
<field id="11" name="creationTime" type="int64" description="Order creation timestamp in Fast order channel(microseconds)"/>
<field id="12" name="updatedTime" type="int64" description="Matching timestamp (microseconds)"/>
<field id="13" name="seq" type="int64" description="Cross sequence ID"/>
<!-- SymbolID -->
<field id="14" name="symbolID" type="int32" description="Symbol ID"/>
<!-- Order identifiers -->
<data id="100" name="orderId" type="varString8" description="Order ID"/>
<data id="101" name="orderLinkId" type="varString8" description="Optional; present for user-initiated orders"/>
</sbe:message>
</sbe:messageSchema>

Code Example

package main

import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"log"
"math"
"os"
"os/signal"
"time"

"github.com/gorilla/websocket"
)

// ---------- Config ----------

const (
MMWSURLTestnetBybits = "wss://stream-testnet.bybits.org/v5/private-sbe"
MMWSURLTestnetBybit = "wss://stream-testnet.bybit.com/v5/private-sbe"
MMWSURLMainnet = "wss://stream.bybit.com/v5/private-sbe"
)

// TODO: fill in your real keys
const (
APIKey = "YOUR_API_KEY"
APISecret = "YOUR_API_SECRET"
)

var subTopics = []string{
"order.sbe.resp.spot",
}

// ---------- SBE helpers ----------

func readU8(buf []byte, off *int) (uint8, error) {
if *off+1 > len(buf) {
return 0, fmt.Errorf("readU8: out of range")
}
v := buf[*off]
*off++
return v, nil
}

func readI8(buf []byte, off *int) (int8, error) {
if *off+1 > len(buf) {
return 0, fmt.Errorf("readI8: out of range")
}
v := int8(buf[*off])
*off++
return v, nil
}

func readU16LE(buf []byte, off *int) (uint16, error) {
if *off+2 > len(buf) {
return 0, fmt.Errorf("readU16LE: out of range")
}
v := binary.LittleEndian.Uint16(buf[*off : *off+2])
*off += 2
return v, nil
}

func readI32LE(buf []byte, off *int) (int32, error) {
if *off+4 > len(buf) {
return 0, fmt.Errorf("readI32LE: out of range")
}
v := int32(binary.LittleEndian.Uint32(buf[*off : *off+4]))
*off += 4
return v, nil
}

func readI64LE(buf []byte, off *int) (int64, error) {
if *off+8 > len(buf) {
return 0, fmt.Errorf("readI64LE: out of range")
}
v := int64(binary.LittleEndian.Uint64(buf[*off : *off+8]))
*off += 8
return v, nil
}

func readVarString8(buf []byte, off *int) (string, error) {
if *off+1 > len(buf) {
return "", fmt.Errorf("readVarString8: no length byte")
}
ln := int(buf[*off])
*off++
if ln == 0 {
return "", nil
}
if *off+ln > len(buf) {
return "", fmt.Errorf("readVarString8: length out of range")
}
s := string(buf[*off : *off+ln])
*off += ln
return s, nil
}

func applyExp(mantissa int64, exp int8) float64 {
e := int(exp)
if e >= 0 {
return float64(mantissa) / math.Pow10(e)
}
return float64(mantissa) * math.Pow10(-e)
}

// ---------- Fast Order SBE decode ----------

type FastOrderSBEResp struct {
SBEHeader struct {
BlockLength uint16 `json:"blockLength"`
TemplateID uint16 `json:"templateId"`
SchemaID uint16 `json:"schemaId"`
Version uint16 `json:"version"`
} `json:"_sbe_header"`

Category uint8 `json:"category"`
Side uint8 `json:"side"`
OrderStatus uint8 `json:"orderStatus"`
PriceExponent int8 `json:"priceExponent"`
SizeExponent int8 `json:"sizeExponent"`
ValExponent int8 `json:"valueExponent"`
RejectReason uint16 `json:"rejectReason"`

PriceMantissa int64 `json:"priceMantissa"`
LeavesQtyMantissa int64 `json:"leavesQtyMantissa"`
LeavesValueMantissa int64 `json:"leavesValueMantissa"`

CreationTime int64 `json:"creationTime"`
UpdatedTime int64 `json:"updatedTime"`
Seq int64 `json:"seq"`

SymbolID int32 `json:"symbolID"`
OrderID string `json:"orderId"`
OrderLinkID string `json:"orderLinkId"`

Price float64 `json:"price"`
LeavesQty float64 `json:"leavesQty"`
LeavesValue float64 `json:"leavesValue"`

RawOffsetEnd int `json:"_raw_offset_end"`
}

func decodeFastOrderResp(payload []byte, debug bool) (*FastOrderSBEResp, error) {
if len(payload) < 8 {
return nil, fmt.Errorf("payload too short for SBE header")
}
off := 0
blockLen := binary.LittleEndian.Uint16(payload[off : off+2])
templateID := binary.LittleEndian.Uint16(payload[off+2 : off+4])
schemaID := binary.LittleEndian.Uint16(payload[off+4 : off+6])
version := binary.LittleEndian.Uint16(payload[off+6 : off+8])
off += 8

if debug {
log.Printf("HEADER: block_len=%d, template_id=%d, schema_id=%d, version=%d",
blockLen, templateID, schemaID, version)
}

if templateID != 21000 {
return nil, fmt.Errorf("unexpected templateId: %d", templateID)
}

var err error
resp := &FastOrderSBEResp{}
resp.SBEHeader.BlockLength = blockLen
resp.SBEHeader.TemplateID = templateID
resp.SBEHeader.SchemaID = schemaID
resp.SBEHeader.Version = version

if resp.Category, err = readU8(payload, &off); err != nil {
return nil, err
}
if resp.Side, err = readU8(payload, &off); err != nil {
return nil, err
}
if resp.OrderStatus, err = readU8(payload, &off); err != nil {
return nil, err
}
if resp.PriceExponent, err = readI8(payload, &off); err != nil {
return nil, err
}
if resp.SizeExponent, err = readI8(payload, &off); err != nil {
return nil, err
}
if resp.ValExponent, err = readI8(payload, &off); err != nil {
return nil, err
}
if resp.RejectReason, err = readU16LE(payload, &off); err != nil {
return nil, err
}
if resp.PriceMantissa, err = readI64LE(payload, &off); err != nil {
return nil, err
}
if resp.LeavesQtyMantissa, err = readI64LE(payload, &off); err != nil {
return nil, err
}
if resp.LeavesValueMantissa, err = readI64LE(payload, &off); err != nil {
return nil, err
}
if resp.CreationTime, err = readI64LE(payload, &off); err != nil {
return nil, err
}
if resp.UpdatedTime, err = readI64LE(payload, &off); err != nil {
return nil, err
}
if resp.Seq, err = readI64LE(payload, &off); err != nil {
return nil, err
}
if resp.SymbolID, err = readI32LE(payload, &off); err != nil {
return nil, err
}
if resp.OrderID, err = readVarString8(payload, &off); err != nil {
return nil, err
}
if resp.OrderLinkID, err = readVarString8(payload, &off); err != nil {
return nil, err
}

resp.Price = applyExp(resp.PriceMantissa, resp.PriceExponent)
resp.LeavesQty = applyExp(resp.LeavesQtyMantissa, resp.SizeExponent)
resp.LeavesValue = applyExp(resp.LeavesValueMantissa, resp.ValExponent)
resp.RawOffsetEnd = off

return resp, nil
}

// ---------- WebSocket helpers ----------

func sendJSON(conn *websocket.Conn, v any) error {
data, err := json.Marshal(v)
if err != nil {
return err
}
return conn.WriteMessage(websocket.TextMessage, data)
}

func signAuth(secret, value string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(value))
return hex.EncodeToString(h.Sum(nil))
}

func heartbeat(ctx context.Context, conn *websocket.Conn) {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
reqID := fmt.Sprintf("%d", time.Now().UnixMilli())
err := sendJSON(conn, map[string]any{
"req_id": reqID,
"op": "ping",
})
if err != nil {
log.Printf("[heartbeat] error sending ping: %v", err)
return
}
}
}
}

// ---------- Main run ----------

func run(ctx context.Context, url string) error {
dialer := websocket.Dialer{
HandshakeTimeout: 10 * time.Second,
EnableCompression: false,
}

conn, _, err := dialer.Dial(url, nil)
if err != nil {
return fmt.Errorf("dial error: %w", err)
}
defer conn.Close()
log.Printf("Connected to %s", url)

expires := (time.Now().Unix() + 10000) * 1000
val := fmt.Sprintf("GET/realtime%d", expires)
sig := signAuth(APISecret, val)

authMsg := map[string]any{
"req_id": "10001",
"op": "auth",
"args": []any{APIKey, expires, sig},
}
if err := sendJSON(conn, authMsg); err != nil {
return fmt.Errorf("send auth error: %w", err)
}

if _, msg, err := conn.ReadMessage(); err != nil {
return fmt.Errorf("read auth ack error: %w", err)
} else {
log.Printf("auth-ack: %s", string(msg))
}

subMsg := map[string]any{
"op": "subscribe",
"args": subTopics,
}
if err := sendJSON(conn, subMsg); err != nil {
return fmt.Errorf("send subscribe error: %w", err)
}

hbCtx, hbCancel := context.WithCancel(ctx)
defer hbCancel()
go heartbeat(hbCtx, conn)

for {
select {
case <-ctx.Done():
log.Printf("context canceled, exit read loop")
return nil
default:
}

mt, data, err := conn.ReadMessage()
if err != nil {
return fmt.Errorf("read message error: %w", err)
}

switch mt {
case websocket.BinaryMessage:
resp, err := decodeFastOrderResp(data, false)
if err != nil {
log.Printf("binary decode error: %v", err)
} else {
j, _ := json.Marshal(resp)
log.Printf("FAST_ORDER_SBE: %s", string(j))
}
case websocket.TextMessage:
var obj map[string]any
if err := json.Unmarshal(data, &obj); err != nil {
log.Printf("text-nonjson: %s", string(data))
continue
}
if op, ok := obj["op"].(string); ok && op == "pong" {
continue
}
j, _ := json.Marshal(obj)
log.Printf("control: %s", string(j))
default:
log.Printf("unknown message type %d", mt)
}
}
}

// ---------- Entry ----------

func main() {
url := flag.String("url", MMWSURLTestnetBybits, "WebSocket URL")
flag.Parse()

if APIKey == "YOUR_API_KEY" || APISecret == "YOUR_API_SECRET" {
log.Println("⚠️ Please set APIKey and APISecret in the source before running.")
}

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

if err := run(ctx, *url); err != nil {
log.Fatalf("run error: %v", err)
}
}