13 KiB
HT Data Engine | TSE & IFB | WebSocket
Real-Time Market Streams (TSE & IFB)
WebSocket • High-Frequency • Low-Latency
1. Overview
This documentation describes the HT Data Engine WebSocket API for subscribing to real-time market data for TSE & IFB symbols.
The API provides multiple channels delivering structured financial data such as order books, aggregate trades, OHLCV, and fund/contract information.
All WebSocket endpoints require JWT token-based authentication and support multiple symbols or ISINs in a single connection.
2. Authentication
To access protected WebSocket endpoints, a valid JWT token is required.
How to get the token
Send a POST request to:
https://core.hedgetech.ir/auth/user/token/issue
Headers:
Content-Type: application/x-www-form-urlencoded
Body:
username=your_username&password=your_password
Response Example
{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Usage
Include the token in the WebSocket connection headers:
Authorization: <your_token>
Important Notes:
- Tokens are bound to your IP and browser fingerprint. A change invalidates the token.
- Ensure your account is registered and approved by an admin.
- Unauthorized connections are closed with WS code 1008.
3. WebSocket Endpoints
| Endpoint | Description |
|---|---|
wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin |
Subscribe using ISIN codes |
wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/name |
Subscribe using symbol names |
Note: The output payload structure is identical for both endpoints, except the symbol identifier field:
symbolIsinfor/symbol/isinsymbolNamefor/symbol/name
🔶 Important Clarification: ISIN vs Symbol-Name WebSocket Endpoints
The data engine provides two separate WebSocket endpoints:
-
Subscribe using ISIN codes
wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin -
Subscribe using Symbol Names
wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/name
Both endpoints deliver identical payload structures, including the same channels and the same data schema.
The only difference is the identifier field inside each message:
| Endpoint | Identifier Field in Payload |
|---|---|
/symbol/isin |
"symbolIsin": "<ISIN>" |
/symbol/name |
"symbolName": "<Symbol>" |
Example for ISIN endpoint:
{
"channel": "best-limit",
"symbolIsin": "IRO1XYZ1234",
"timestamp": "2025-11-14T12:00:00.000000",
"data": { ... }
}
Example for Symbol-Name endpoint:
{
"channel": "best-limit",
"symbolName": "XYZ",
"timestamp": "2025-11-14T12:00:00.000000",
"data": { ... }
}
No other structural difference exists between these two WebSocket services.
Why this clarification matters
- Consumers might assume that subscribing to the symbol-name endpoint returns a different schema — it does not.
- Client implementations should be prepared to handle either identifier field (
symbolIsinorsymbolName) depending on which endpoint they connect to. - This avoids confusing bugs (for example: looking for
symbolIsinin messages coming from the/symbol/nameendpoint).
4. Connection Flow (Updated)
-
Establish WebSocket connection with the proper
Authorizationheader. -
Include query parameters in the URL. Each symbol/ISIN and channel is repeated as a separate query parameter:
For ISIN endpoint:
wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin? channels=<channel1>&channels=<channel2>& symbol_isins=<ISIN1>&symbol_isins=<ISIN2>For symbol names endpoint:
wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/name? channels=<channel1>&channels=<channel2>& symbol_names=<symbol1>&symbol_names=<symbol2> -
If verification passes, the WebSocket connection is accepted.
-
Real-time messages are streamed continuously until the connection is closed.
Important: Unauthorized connections are closed immediately with code 1008.
5. Query Parameters
| Parameter | Type | Description |
|---|---|---|
symbol_isins |
list of strings | List of ISIN codes to subscribe (for /symbol/isin) |
symbol_names |
list of strings | List of symbol names to subscribe (for /symbol/name) |
channels |
list of strings | List of channels to subscribe (see Section 6) |
Example URL:
wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin?channels=order-book&channels=best-limit&symbol_isins=IRT3SATF0001&symbol_isins=IRTKMOFD0001
6. Channel List & Payload Schemas
All messages are delivered in the following JSON structure:
{
"channel": "best-limit",
"symbolIsin": "IRO1XYZ1234",
"timestamp": "2025-11-14T12:00:00.000000",
"data": { ... channel-specific payload ... }
}
NOTE: For the
/symbol/nameendpoint thesymbolIsinfield above is replaced bysymbolName. Everything else remains the same.
| Channel | Payload Model | Description |
|---|---|---|
best-limit |
BestLimit |
Top buy/sell orders at each level |
order-book |
OrderBook |
Full order book with aggregated price levels |
ohlcv-last-1m |
OHLCV |
1-minute candlestick data |
aggregate |
Aggregate |
Aggregate trade data including volume, count, prices |
institutional-vs-individual |
InstitutionalVsIndividual |
Buy/sell breakdown between individual and institutional investors |
contract-info |
ContractInfo |
Contract details such as open interest and margin |
fund-info |
FundInfo |
Fund NAV, units, market capitalization, timestamp |
6.1 Example: best-limit
{
"channel": "best-limit",
"symbolIsin": "IRO1XYZ1234",
"timestamp": "2025-11-14T12:00:00.000000",
"data": {
"1": {
"buy_order_count": 5,
"buy_quantity": 1000,
"buy_price": 10000,
"sell_order_count": 3,
"sell_quantity": 800,
"sell_price": 10050
},
"2": {
"buy_order_count": 4,
"buy_quantity": 500,
"buy_price": 9950,
"sell_order_count": 2,
"sell_quantity": 400,
"sell_price": 10100
}
}
}
6.2 Example: order-book
{
"channel": "order-book",
"symbolIsin": "IRO1XYZ1234",
"timestamp": "2025-11-14T12:00:00.000000",
"data": {
"Buy": [
{
"price": 0,
"quantity": 0,
"count": 0
}
],
"Sell": [
{
"price": 0,
"quantity": 0,
"count": 0
}
]
}
}
6.3 Example: ohlcv-last-1m
{
"channel": "ohlcv-last-1m",
"symbolIsin": "IRO1XYZ1234",
"timestamp": "2025-11-14T12:00:00.000000",
"data": {
"open": 10000.0,
"high": 10100.0,
"low": 9950.0,
"close": 10050.0,
"volume": 3500
}
}
6.4 Example: aggregate
{
"channel": "aggregate",
"symbolIsin": "IRO1XYZ1234",
"timestamp": "2025-11-14T12:00:00.000000",
"data": {
"date": "string",
"time": "string",
"trade_count": 0,
"total_volume": 0,
"total_value": 0,
"closing_price": 0,
"last_price": 0,
"low_price": 0,
"high_price": 0,
"open_price": 0,
"previous_close": 0
}
}
6.5 Example: institutional-vs-individual
{
"channel": "institutional-vs-individual",
"symbolIsin": "IRO1XYZ1234",
"timestamp": "2025-11-14T12:00:00.000000",
"data": {
"buy_count_individual": 0,
"buy_volume_individual": 0,
"buy_count_institution": 0,
"buy_volume_institution": 0,
"sell_count_individual": 0,
"sell_volume_individual": 0,
"sell_count_institution": 0,
"sell_volume_institution": 0
}
}
6.6 Example: contract-info
{
"channel": "contract-info",
"symbolIsin": "IRO1XYZ1234",
"timestamp": "2025-11-14T12:00:00.000000",
"data": {
"open_interest": 0,
"initial_margin": 0,
"required_margin": 0
}
}
6.7 Example: fund-info
{
"channel": "fund-info",
"symbolIsin": "IRO1XYZ1234",
"timestamp": "2025-11-14T12:00:00.000000",
"data": {
"nav": 0,
"units": 0,
"marketCap": 0,
"as_of": "2025-11-14T22:10:42.802Z"
}
}
7. Error Handling
| Code | Description |
|---|---|
1008 |
Policy violation (invalid JWT, invalid symbols, or invalid channels) |
| Connection closed | Occurs if Redis stream fails or server error |
8. Code Examples
8.1 Python (WebSocket Client)
import asyncio
import websockets
import json
async def subscribe(url: str, token: str):
headers = {"Authorization": token}
async with websockets.connect(url, extra_headers=headers) as ws:
async for message in ws:
data = json.loads(message)
# safely extract identifier regardless of endpoint
symbol = data.get("symbolIsin") or data.get("symbolName")
channel = data.get("channel")
ts = data.get("timestamp")
# process payload...
print(f"{ts} | {symbol} | {channel} -> {data['data']}")
# example use (choose the endpoint you need)
url = "wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin?channels=order-book&channels=best-limit&symbol_isins=IRT3SATF0001"
token = "<your_token>"
asyncio.run(subscribe(url, token))
8.2 JavaScript (WebSocket Client)
const WebSocket = require('ws');
function normalizeMessage(message) {
const msg = JSON.parse(message);
const symbol = msg.symbolIsin || msg.symbolName;
return { ...msg, symbol };
}
const url = 'wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin?channels=order-book&channels=best-limit&symbol_isins=IRT3SATF0001';
const token = '<your_token>';
const ws = new WebSocket(url, { headers: { Authorization: token } });
ws.on('open', () => console.log('Connected'));
ws.on('message', (data) => {
const message = normalizeMessage(data);
console.log(message.timestamp, message.symbol, message.channel, message.data);
});
ws.on('close', () => console.log('Disconnected'));
8.3 Go (WebSocket Client)
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/gorilla/websocket"
)
func main() {
url := "wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin?channels=order-book&channels=best-limit&symbol_isins=IRT3SATF0001"
header := map[string][]string{
"Authorization": {"<your_token>"},
}
c, _, err := websocket.DefaultDialer.Dial(url, header)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
var m map[string]interface{}
json.Unmarshal(message, &m)
symbol := ""
if v, ok := m["symbolIsin"]; ok {
symbol = v.(string)
} else if v, ok := m["symbolName"]; ok {
symbol = v.(string)
}
fmt.Println(m["timestamp"], symbol, m["channel"])
}
}
8.4 Rust (WebSocket Client)
use tokio_tungstenite::connect_async;
use futures_util::{StreamExt};
use url::Url;
use serde_json::Value;
#[tokio::main]
async fn main() {
let url = Url::parse("wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin?channels=order-book&channels=best-limit&symbol_isins=IRT3SATF0001").unwrap();
let req = tokio_tungstenite::tungstenite::client::IntoClientRequest::into_client_request(url).unwrap();
let (ws_stream, _) = connect_async(req).await.expect("Failed to connect");
let (_write, mut read) = ws_stream.split();
while let Some(message) = read.next().await {
let msg = message.unwrap();
if msg.is_text() {
let v: Value = serde_json::from_str(msg.to_text().unwrap()).unwrap();
let symbol = v.get("symbolIsin").or_else(|| v.get("symbolName"));
println!("{:?} {:?} {:?}", v["timestamp"], symbol, v["channel"]);
}
}
}
8.5 Subscription Notes
- Multiple symbols and channels can be subscribed in a single WebSocket connection.
- The server streams messages continuously; handle them asynchronously.
- Always respect Rate Limits if applicable.
9. Best Practices
- Reconnect with exponential backoff in case of disconnects.
- Validate your JWT before subscribing.
- Subscribe only to the channels you need to reduce bandwidth.
- Handle
symbolIsin/symbolNameextraction consistently in your client code:- Prefer a helper function that returns a single canonical
symbolvalue. - Log incoming messages and detect which endpoint you are connected to (optional, for debugging).
- Prefer a helper function that returns a single canonical
Appendix: Quick Developer Checklist (for avoiding common mistakes)
- ✅ Use correct endpoint for your identifier type (
symbol/isinvssymbol/name). - ✅ Provide
Authorizationheader with a valid token on the WebSocket handshake. - ✅ Include
channelsandsymbol_isins/symbol_namesas repeated query params. - ✅ Handle both
symbolIsinandsymbolNamein your message parsing to make the client resilient. - ✅ Treat messages'
datapayload consistently across endpoints (same schema). - ✅ Monitor for WS close code
1008to detect authorization or policy errors.