Every frame on the SBE WebSocket is one message of the trading schema: an 8-byte header, a fixed block, then any repeating groups and variable-length fields. Generate your codec from the schema of the environment you connect to, and let it handle offsets and null values.
The schema
The schema ID is 7. Each environment publishes its own XML and accepts exactly one version of it. Schema version links the
published files and a download script that checks the XML against the manifest.
Version rules:
- Send the XML's
versionin every message header. Any other version is answered with aCommandRejectwith codeBAD_REQUESTand is not processed. - The server encodes its messages with the same version, so decode with the same codec.
- A new version can change any message. Regenerate your codec whenever the environment's manifest names a new schema file, before that version goes live on your environment.
- A field's
sinceVersionrecords when it was added. It does not let you send an older layout.
Generate codecs
Real Logic's Simple Binary Encoding tool
generates codecs from the XML. Download the sbe-all jar of a recent release from Maven Central
(uk.co.real-logic:sbe-all) and run it with Java 17 or later. Pin the release you use so that
regenerated code stays stable.
java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED
-Dsbe.output.dir=generated/java
-jar sbe-all-<release>.jar tradews.xmlJava is the default target. The codecs land in the tradews package and use the Agrona library for
their buffers.
java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED
-Dsbe.target.language=Cpp
-Dsbe.output.dir=generated/cpp
-jar sbe-all-<release>.jar tradews.xmlThe C++ codecs are header-only.
java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED
-Dsbe.target.language=Golang
-Dsbe.go.generate.generate.flyweights=true
-Dsbe.output.dir=generated/go
-jar sbe-all-<release>.jar tradews.xmlFlyweights encode and decode in place without allocating per message. Without the flag, the Go generator emits struct codecs instead.
java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED
-Dsbe.target.language=Rust
-Dsbe.output.dir=generated/rust
-jar sbe-all-<release>.jar tradews.xmlThe output is a crate you can add to your workspace as a path dependency.
java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED
-Dsbe.target.language=uk.co.real_logic.sbe.generation.csharp.CSharp
-Dsbe.output.dir=generated/csharp
-jar sbe-all-<release>.jar tradews.xmlThe generated code needs the SBE C# runtime library; see the C# user guide.
Python, TypeScript, and other languages have no generator from Real Logic. The layout is small and
fixed, so a hand-written codec over struct, DataView, or your language's byte buffer works well.
Take offsets and null values from the Message reference, and test your encoder
against the worked examples below. The Getting started quickstart shows a
complete Python client written this way.
Message layout
+----------------------+--------------------+----------------------+--------------------------+
| messageHeader 8 B | fixed block | repeating groups | variable-length fields |
| blockLength | blockLength bytes | 4 B size header + | 4 B length + bytes, |
| templateId | | entries, in schema | in schema order |
| schemaId = 7 | | order | |
| version | | | |
+----------------------+--------------------+----------------------+--------------------------+The rules every message follows:
- Byte order: every integer is little-endian.
- Header: four
uint16values,blockLength,templateId,schemaId, andversion. ThetemplateIdselects the message; see the template index. - Fixed block: fields sit at fixed offsets with no padding.
blockLengthmust equal the block length of that message in your environment's schema. A different value is rejected. - Groups: a 4-byte header of
uint16entry block length anduint16entry count, then the entries. Each entry is its own fixed block followed by its own variable-length fields. - Variable-length fields: a
uint32length, then that many bytes. An empty value is a length of0. Note the 4-byte length: many SBE examples use a 2-byte length. - Order: write and read the parts in schema order. Generated codecs enforce this.
- Trailing bytes: a frame must end exactly at its last field. Extra bytes are rejected.
When you decode server messages, read blockLength from the header and skip to the end of the fixed
block by that value, as generated codecs do.
Field encodings
Null values
Optional fields carry a null value when they are not set. The schema uses two conventions:
- Optional
int64· null-9223372036854775808(INT64_MIN)
For examplebaseQtyScaled,priceTicks,expireAtTsNs. - Optional
int32· null-2147483648(INT32_MIN)
For examplemarketMaxSlippageBps. - Optional
uint64· null18446744073709551615(UINT64_MAX)
For examplerateLimitRemaining. - IDs and enums of a
ZeroNulltype · null0
For exampleorderId,symbolIdin a cancel,side,stpMode,RejectCode.
Optional IDs and enums use 0 as their null value. For example, a cancel by client order ID sets orderId to 0, and a symbolId of 0 lets the service resolve the Pair from the order.
A required enum whose type is zero-null, such as side, rejects 0. A required enum with a uint8 encoding, such as orderType, has no value 0 either.
int64 field must hold INT64_MIN, not 0. A zero-initialized buffer turns every unset
price or quantity into an explicit 0: a MARKET order with priceTicks 0 is rejected, and so
is a MAX_QUOTE_DEBIT order whose baseQtyScaled is 0 instead of null. Set every optional field
explicitly, or start from a buffer your codec has filled with null values.Prices, quantities, and timestamps
| Field pattern | Encoding |
|---|---|
*PriceTicks, priceTicks | Price in quote units scaled by 109, for every Pair |
marketMaxSlippageTicks | Price delta in the same 10-9 quote-unit ticks |
*QtyScaled, baseQtyScaled | Base quantity scaled by the Pair's base_quantity_scale |
maxQuoteDebitScaled | Quote amount scaled by the Pair's quote_quantity_scale |
*Bps | Basis points: 1 is 0.01% |
*TsNs | Nanoseconds since the Unix epoch, UTC |
*Ns durations | Nanoseconds |
Read each Pair's symbol_id, base_quantity_scale, quote_quantity_scale, tick size, and step
size from GET /v1/spot/config (ConnectRPC GetSpotConfig). Price
ticks never use the quote Asset's quantity scale. See Scaled
integers for conversion rules and common mistakes.
Identifiers
orderId fields carry the public order ID as an unsigned 64-bit integer, the same value
ConnectRPC carries as fixed64. REST shows the same ID as a base58 string. See ConnectRPC identifiers and Public IDs. symbolId is the Pair's numeric symbol_id.
clientOrderId and newClientOrderId are ASCII: 1 to 36 letters, digits, ., _, :, /, or -. See Client order IDs. The detail text of a CommandReject is UTF-8.
Enums and bitsets
Enums are one or two bytes; their values are listed in the Message reference. The
two bitsets are uint32: in EventChannels, bit 0 is executions, bit 1 orders, and bit 2 marketResync; in OrderFlags, bit 0 is postOnly. Every other bit must be 0.
Boolean fields such as dryRun and cancelledOrdersTruncated are uint8 values 0 or 1.
Worked examples
The examples are real encodings, checked against codecs generated from the schema. vv vv stands
for your environment's schema version as a little-endian uint16. Offsets count from the start of
the frame.
A Ping with seq 1, correlationId 100, and payload hello is 33 bytes:
0 10 00 blockLength = 16
2 01 00 templateId = 1 (Ping)
4 07 00 schemaId = 7
6 vv vv version
8 01 00 00 00 00 00 00 00 seq = 1
16 64 00 00 00 00 00 00 00 correlationId = 100
24 05 00 00 00 payload length = 5
28 68 65 6c 6c 6f payload = "hello"The server answers with a Pong of the same shape, template ID 22, echoing seq, correlationId, and payload.
Example values: Pair symbolId 7, a quantity of 0.01 on a Pair with base_quantity_scale 8,
a price of 65000.5, and client order ID quickstart-1.
0 52 00 blockLength = 82
2 02 00 templateId = 2 (CreateOrder)
4 07 00 schemaId = 7
6 vv vv version
8 03 00 00 00 00 00 00 00 seq = 3
16 66 00 00 00 00 00 00 00 correlationId = 102
24 07 00 00 00 symbolId = 7
28 02 side = 2 (SELL)
29 01 orderType = 1 (LIMIT)
30 01 timeInForce = 1 (GTC)
31 40 42 0f 00 00 00 00 00 baseQtyScaled = 1000000
39 00 75 b6 1b 1e 3b 00 00 priceTicks = 65000500000000
47 01 00 00 00 flags = 0x1 (postOnly)
51 01 feeAsset = 1 (QUOTE)
52 01 stpMode = 1 (EXPIRE_MAKER)
53 00 00 00 00 00 00 00 80 marketClientRefPriceTicks = null
61 00 00 00 00 00 00 00 80 marketMaxSlippageTicks = null
69 00 00 00 80 marketMaxSlippageBps = null
73 01 sizingKind = 1 (BASE_QUANTITY)
74 00 00 00 00 00 00 00 80 maxQuoteDebitScaled = null
82 00 00 00 00 00 00 00 80 expireAtTsNs = null
90 0c 00 00 00 clientOrderId length = 12
94 71 75 69 63 6b 73 74 61 72 74 2d 31
clientOrderId = "quickstart-1" 0 1c 00 blockLength = 28
2 03 00 templateId = 3 (CancelOrder)
4 07 00 schemaId = 7
6 vv vv version
8 04 00 00 00 00 00 00 00 seq = 4
16 67 00 00 00 00 00 00 00 correlationId = 103
24 00 00 00 00 00 00 00 00 orderId = 0 (null: cancel by client order ID)
32 07 00 00 00 symbolId = 7
36 0c 00 00 00 clientOrderId length = 12
40 71 75 69 63 6b 73 74 61 72 74 2d 31
clientOrderId = "quickstart-1" 0 14 00 blockLength = 20
2 06 00 templateId = 6 (Subscribe)
4 07 00 schemaId = 7
6 vv vv version
8 02 00 00 00 00 00 00 00 seq = 2
16 65 00 00 00 00 00 00 00 correlationId = 101
24 03 00 00 00 channels = 0x3 (executions, orders)Test your codec
- Encode a
Pingand compare it byte for byte with the example above. - Send the
Pingon Testnet and check thatPongechoes all three values. - Send a command with a deliberately wrong version and check that you decode the
CommandReject, including itsdetailtext. - Decode every server template, even ones you do not act on, so that an unexpected message never stalls your reader.