Sub/Unsub Market Data
Subscribe and unsubscribe to market data via FastConnect WebSocket.
Overview
The DATA channel returns 8 groups of market information:
| Topic | Description |
|---|---|
trade | Matched trade information |
trade.index | Realtime index value |
indexsummary | Index summary information |
quote | Bid/ask information |
room | Foreign room information |
put | Put-through (negotiated) trades |
oddlot | Odd-lot trades |
market | Start-of-day and session-flag information |
Topic pattern
trade.[index/exchange/symbol-symbol/*]@[tick/1m/5m]
trade.index.[index_code]@[tick/1m/5m]
indexsummary.[index_code/*]
quote.[index/exchange/symbol-symbol/*]@[tick/1m/5m]
room.[index/exchange/symbol-symbol/*]@[tick/1m/5m]
put.[index/exchange/symbol-symbol/*]@[tick/1m/5m]
oddlot.[index/exchange/symbol-symbol/*]@[tick/1m/5m]
market.[index/exchange/symbol-symbol/*]- Multiple symbols are joined with
-(e.g.ACB-SSI-GVR-KDH);*covers all symbols within scope. - Omitting
@intervaldefaults totick. Themarketandindexsummarytopics have no interval. trade.indexandindexsummaryonly accept an index code (e.g.VN30,VNINDEX,HNXINDEX,UPCOMINDEX).
Note: valid intervals are currently
tick,1m,5m(there is no3m).
Subscribe market data
{
"method": "SUBSCRIBE",
"channel": "DATA",
"topics": [
"trade.SSI",
"trade.ACB-SSI-GVR-KDH",
"trade.vn30",
"trade.hose"
]
}trade.SSI— subscribe to matched-trade messages for symbol SSItrade.ACB-SSI-GVR-KDH— subscribe to matched-trade messages for symbols ACB, SSI, GVR, KDHtrade.vn30— subscribe to matched-trade messages for all symbols in the VN30 baskettrade.hose— subscribe to matched-trade messages for all symbols on the HOSE exchange
The same syntax applies to quote, room, put, oddlot, market — just change the topic prefix (e.g. quote.SSI, room.vn30, market.hose).
Unsubscribe market data
{
"method": "UNSUBSCRIBE",
"channel": "DATA",
"topics": ["trade.SSI", "quote.SSI"]
}I. Trade — matched trade information
| Field | Name | Type | Description |
|---|---|---|---|
s | symbol | String | Securities symbol |
t | tradingTime | String | YYYY/MM/DD HH:mi:ss |
p | price | String | Matched price (unit: VND) |
q | quantity | String | Matched quantity |
a | avgPrice | String | Average matched price for the day (unit: VND) = total matched value / total matched volume. "0" when there are no matches yet |
si | side | String | B: Buy Up / S: Sell Down |
o | openPrice | String | Opening price (unit: VND) |
h | highPrice | String | Highest price (unit: VND) |
l | lowPrice | String | Lowest price (unit: VND) |
v | totalVol | String | Total matched volume for the day |
Sample output:
{
"s": "SSI",
"t": "2026/07/29 09:15:32",
"p": "45500",
"q": "1000",
"a": "45350",
"si": "B",
"o": "45000",
"h": "45800",
"l": "44900",
"v": "1250000"
}I.1. Trade.index — realtime index value
Some symbols on the trade topic (e.g. s: "VN30") are not stocks but the realtime value of the index itself. Subscribing to trade.vn30 as above only returns the stocks belonging to the VN30 basket, not the index's own value. To receive the index's own realtime tick, use the dedicated trade.index topic.
Subscribe syntax: trade.index.[index_code]@[tick/1m/5m]
{
"method": "SUBSCRIBE",
"channel": "DATA",
"topics": [
"trade.index.VN30",
"trade.index.VNINDEX",
"trade.index.HNXINDEX",
"trade.index.UPCOMINDEX"
]
}trade.index.VN30— subscribe to the realtime VN30 index valuetrade.index.UPCOMINDEX— subscribe to the realtime UPCOM index value. The HNX source publishes the raw message ass: "HNXUPCOMINDEX"; the server renames it toUPCOMINDEXbefore forwarding to clients, so subscribe using this new name.trade.index.VN30@1m,trade.index.VN30@5m— subscribe to the 1-minute/5-minute candles of the index. A candle message'sdatacontainss(index code),st(candle start time),t(last tick time),o,h,l,c(open/high/low/close) andv(volume).
The tick data structure (no interval) is the same as the trade topic above (s, t, p, q, v, ...), where p is the index's current point value.
Sample output:
{
"s": "VN30",
"t": "2026/09/14 13:39:10",
"p": "1928.81",
"q": "75412",
"a": "0",
"si": "",
"o": "0",
"h": "0",
"l": "0",
"v": "129161843"
}I.2. Indexsummary — index summary information
Returns realtime summary information of an index: index value and change, number of advancing/declining/unchanged/ceiling/floor stocks, and total matched and put-through volume and value. Unlike trade.index (which only carries the index value tick), indexsummary also carries market breadth and liquidity information for the index.
Subscribe syntax: indexsummary.[index_code/*]
This topic requires the stream:DATA:indexsummary scope.
{
"method": "SUBSCRIBE",
"channel": "DATA",
"topics": [
"indexsummary.VN30",
"indexsummary.VNINDEX",
"indexsummary.HNXINDEX",
"indexsummary.UPCOMINDEX"
]
}indexsummary.VN30— subscribe to the VN30 index summaryindexsummary.UPCOMINDEX— subscribe to the UPCOM index summary. The HNX source publishes the raw message ass: "HNXUPCOMINDEX"; clients subscribe usingUPCOMINDEXand the server maps it to the source code automatically.indexsummary.*— subscribe to the summary of all indices
Note:
indexsummaryonly accepts an index code. Do not subscribe by exchange code (hose,hnx,upcom) or by stock symbol as with thetradetopic.
| Field | Name | Type | Description |
|---|---|---|---|
s | indexName | String | Index code |
b | exchange | String | Exchange the index belongs to |
ss | tradingSession | String | Current trading session (e.g. ATC) |
t | tradingTime | String | Trading time of the message, format YYYY/MM/DD HH:mi:ss |
v | indexValue | String | Current index value (points) |
c | indexChange | String | Index change versus the previous session (points). Negative: down, positive: up |
cr | indexRatioChange | String | Index change percentage versus the previous session (%). Negative: down, positive: up |
adv | advances | String | Total number of advancing stocks |
dec | declines | String | Total number of declining stocks |
nc | noChange | String | Total number of unchanged stocks |
ce | ceilings | String | Total number of stocks at ceiling price |
fl | floors | String | Total number of stocks at floor price |
tmv | totalMatchedValue | String | Total matched trading value (unit: VND) |
tmq | totalMatchedQty | String | Total matched trading quantity (unit: shares) |
tvp | totalValuePt | String | Total put-through trading value (unit: VND) |
tqp | totalQtyPt | String | Total put-through trading quantity (unit: shares) |
tq | totalQty | String | Total matched and put-through trading quantity (unit: shares) = tmq + tqp |
tv | totalValue | String | Total matched and put-through trading value (unit: VND) = tmv + tvp |
Sample output:
{
"s": "VNIND",
"b": "HOSE",
"ss": "ATC",
"t": "2026/10/13 14:30:55",
"v": "793.02",
"c": "-20.44",
"cr": "-2.51",
"adv": "15",
"dec": "47",
"nc": "11",
"ce": "1",
"fl": "0",
"tmv": "792414028510",
"tmq": "37448788",
"tvp": "22700831600",
"tqp": "1344858",
"tq": "38793646",
"tv": "815114860110"
}II. Room — foreign room information
| Field | Name | Type | Description |
|---|---|---|---|
s | symbol | String | Securities symbol |
t | tradingtime | String | YYYY/MM/DD HH:mi:ss |
tr | totalRoom | String | Total foreign room |
cr | currentRoom | String | Remaining foreign room |
bq | buyQuantity | String | Foreign buy quantity |
bv | buyValue | String | Foreign buy value |
sq | sellQuantity | String | Foreign sell quantity |
sv | sellValue | String | Foreign sell value |
Sample output:
{
"s": "SSI",
"t": "2026/07/29 09:15:32",
"tr": "1500000",
"cr": "980000",
"bq": "5000",
"bv": "227500000",
"sq": "3000",
"sv": "136500000"
}III. Quote — bid/ask information
| Field | Type | Description |
|---|---|---|
s | String | Securities symbol |
t | String | tradingtime, YYYY/MM/DD HH:mi:ss |
bids | List<String[2]> | List of bid levels [price, quantity]. A future upgrade will only return changed values. |
asks | List<String[2]> | List of ask levels [price, quantity]. A future upgrade will only return changed values. |
Sample output:
{
"s": "SSI",
"t": "2026/07/29 09:15:32",
"bids": [
["45.45", "3200"],
["45.40", "1500"],
["45.35", "4200"],
["45.30", "2100"],
["45.25", "3300"]
],
"asks": [
["45.50", "2800"],
["45.55", "1900"],
["45.60", "3600"],
["45.65", "2200"],
["45.70", "4100"]
]
}IV. Put — put-through (negotiated) trades
| Field | Name | Type | Description |
|---|---|---|---|
s | symbol | String | Securities symbol |
t | tradingtime | String | YYYY/MM/DD HH:mi:ss |
p | price | String | Put-through matched price |
q | quantity | String | Put-through matched quantity |
tq | TotalQuantity | String | Total put-through quantity for the day |
tv | TotalValue | String | Total put-through value for the day |
Sample output:
{
"s": "SSI",
"t": "2026/07/29 10:05:12",
"p": "45.00",
"q": "50000",
"tq": "350000",
"tv": "15750000000"
}V. Oddlot — odd-lot trades
| Field | Name | Type | Description |
|---|---|---|---|
s | symbol | String | Securities symbol |
t | tradingtime | String | YYYY/MM/DD HH:mi:ss |
p | price | String | Odd-lot matched price |
q | quantity | String | Odd-lot matched quantity |
bids | List<String[2]> | List of odd-lot bid levels [price, quantity] | |
asks | List<String[2]> | List of odd-lot ask levels [price, quantity] |
Sample output:
{
"s": "SSI",
"t": "2026/07/29 09:16:05",
"p": "45.50",
"q": "80",
"bids": [
["45.45", "50"],
["45.40", "30"]
],
"asks": [
["45.55", "40"],
["45.60", "20"]
]
}VI. Market — start-of-day and session information
Subscribe syntax has no interval: market.[index/exchange/symbol-symbol/*].
Market streaming data:
| Field | Name | Type | Description |
|---|---|---|---|
s | symbol | String | Securities symbol |
b | board | String | HOSE, HNX, UPCOM |
t | tradingDate | String | YYYY/MM/DD |
ce | ceilingPrice | String | Ceiling price |
fl | floorPrice | String | Floor price |
ref | refPrice | String | Reference price |
Sample output:
{
"s": "SSI",
"b": "HOSE",
"t": "2026/07/29",
"ce": "48.50",
"fl": "42.10",
"ref": "45.30"
}Market flag (session-flag message):
| Field | Name | Type | Description |
|---|---|---|---|
b | board | String | HOSE, HNX, UPCOM, DERIVATIVES |
t | tradingTime | String | YYYY/MM/DD HH:mm:ss |
f | flag | String | Session flag: ATO, LO, ATC, etc. |
Sample output:
{
"b": "HOSE",
"t": "2026/07/29 09:00:00",
"f": "ATO"
}See also
- Connect WebSocket — connection setup
- Heartbeat — ping/pong keep-alive mechanism
- Sub/Unsub Trading Data — subscribe to trading data
Sub/Unsub Trading DataWS
Subscribe and unsubscribe to trading data via FastConnect WebSocket — including regular orders, order matches, portfolio updates, and conditional orders (FCO).
Connect WebSocket APIWS
Establish a WebSocket connection via an HTTP Upgrade request to `/ws/v3/trading`. The server returns `HTTP 101 Switching Protocols` when the connection succeeds. **Authentication:** pass the JWT access token one of two ways (checked in this order): - Query string: `wss://.../ws/v3/trading?token=<jwt>` - Header: `Authorization: Bearer <jwt>` The token must be valid (not expired, not revoked) and the API Key must be granted the `tradingws:*:*` scope — the `trading:*:*` scope used by the REST Trading API **does not work** for this WS connection; it must be granted separately. **Connection limits:** at most 10 concurrent connections per API Key; exceeding the limit is rejected with reason `Connection limit exceeded`. **⚠️ Client implementation note:** except when the request is not a valid WebSocket upgrade (a real HTTP `400` returned before connecting), every other rejection reason (401/403/429) happens AFTER the connection has already succeeded (HTTP 101) — the server accepts the connection first, sends a message describing the reason, then closes it. Reason: most WebSocket client libraries cannot reliably surface the HTTP status code of the connection step. Clients **should read the first message** after connecting to know whether the connection is actually ready to send commands. **Sample rejection message:** ```json { "code": 401, "msg": "Invalid or expired token" } ``` Possible rejection reasons: `401` missing/invalid/expired/revoked token, `403` missing `tradingws:*:*` scope, `429` concurrent-connection limit exceeded, `500` system error.