Skip to content

WebSocket Heartbeat Mechanism

Overview

HabitTrade API's WebSocket connections support server-side heartbeat mechanism to ensure connection stability and reliability. The system periodically sends heartbeat messages to all active connections, helping clients detect connection status.

Heartbeat Message Format

The server automatically sends heartbeat messages to all WebSocket connections every 30 seconds:

json
{
  "type": "heartbeat",
  "data": {
    "timestamp": 1703123456789
  }
}

Field Description

  • type: Message type, fixed as "heartbeat" for heartbeat messages
  • data.timestamp: Unix timestamp in milliseconds when the heartbeat was sent

Applicable Endpoints

The heartbeat mechanism applies to the following WebSocket endpoints:

Trading Endpoints

  • /ws/trade/* - Trading WebSocket connections

Market Data Endpoints

  • /ws/market/* - All market data WebSocket connections

Client Implementation Recommendations

1. Heartbeat Monitoring

It's recommended that clients monitor heartbeat messages to detect connection health:

Python
import json
import time

def on_message(ws, message):
    data = json.loads(message)
    if data.get('type') == 'heartbeat':
        print(f"Heartbeat received: {data['data']['timestamp']}")
        global last_heartbeat_time
        last_heartbeat_time = time.time() * 1000
    # Handle other business messages...
Java
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public void onMessage(String message) {
    ObjectMapper mapper = new ObjectMapper();
    JsonNode data = mapper.readTree(message);
    if ("heartbeat".equals(data.get("type").asText())) {
        System.out.println("Heartbeat received: " + data.get("data").get("timestamp").asLong());
        lastHeartbeatTime = System.currentTimeMillis();
    }
    // Handle other business messages...
}
C#
using Newtonsoft.Json.Linq;

private void OnMessage(string message)
{
    var data = JObject.Parse(message);
    if (data["type"]?.ToString() == "heartbeat")
    {
        Console.WriteLine($"Heartbeat received: {data["data"]["timestamp"]}");
        lastHeartbeatTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
    }
    // Handle other business messages...
}
go
import (
    "encoding/json"
    "fmt"
    "time"
)

type HeartbeatMessage struct {
    Type string `json:"type"`
    Data struct {
        Timestamp int64 `json:"timestamp"`
    } `json:"data"`
}

func onMessage(message []byte) {
    var msg HeartbeatMessage
    json.Unmarshal(message, &msg)
    if msg.Type == "heartbeat" {
        fmt.Printf("Heartbeat received: %d\n", msg.Data.Timestamp)
        lastHeartbeatTime = time.Now().UnixMilli()
    }
    // Handle other business messages...
}
javascript
websocket.onmessage = function(event) {
    const message = JSON.parse(event.data);
    
    if (message.type === 'heartbeat') {
        console.log('Heartbeat received:', new Date(message.data.timestamp));
        // Update last heartbeat time
        lastHeartbeatTime = Date.now();
    }
    // Handle other business messages...
};

2. Connection Timeout Detection

You can implement client-side timeout detection:

Python
import threading
import time

def check_heartbeat_timeout():
    while True:
        current_time = time.time() * 1000
        if current_time - last_heartbeat_time > 60000:
            print("Heartbeat timeout, preparing to reconnect")
            reconnect()
        time.sleep(10)  # Check every 10 seconds

# Start heartbeat check thread
heartbeat_thread = threading.Thread(target=check_heartbeat_timeout)
heartbeat_thread.daemon = True
heartbeat_thread.start()
Java
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
    long now = System.currentTimeMillis();
    if (now - lastHeartbeatTime > 60000) {
        System.out.println("Heartbeat timeout, preparing to reconnect");
        reconnect();
    }
}, 10, 10, TimeUnit.SECONDS); // Check every 10 seconds
C#
using System;
using System.Threading;

private Timer heartbeatTimer;

private void StartHeartbeatCheck()
{
    heartbeatTimer = new Timer(CheckHeartbeatTimeout, null, 
        TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));
}

private void CheckHeartbeatTimeout(object state)
{
    var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
    if (now - lastHeartbeatTime > 60000)
    {
        Console.WriteLine("Heartbeat timeout, preparing to reconnect");
        Reconnect();
    }
}
go
import "time"

func startHeartbeatCheck() {
    ticker := time.NewTicker(10 * time.Second)
    go func() {
        for range ticker.C {
            now := time.Now().UnixMilli()
            if now - lastHeartbeatTime > 60000 {
                fmt.Println("Heartbeat timeout, preparing to reconnect")
                reconnect()
            }
        }
    }()
}
javascript
// Check heartbeat timeout (recommended: 60 seconds)
setInterval(() => {
    const now = Date.now();
    if (now - lastHeartbeatTime > 60000) {
        console.log('Heartbeat timeout, preparing to reconnect');
        // Reconnect
        reconnect();
    }
}, 10000); // Check every 10 seconds

3. Ignoring Heartbeat Messages

If heartbeat functionality is not needed, these messages can be safely ignored:

Python
import json

def on_message(ws, message):
    data = json.loads(message)
    # Ignore heartbeat messages
    if data.get('type') == 'heartbeat':
        return
    # Handle business messages
    handle_business_message(data)
Java
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public void onMessage(String message) {
    ObjectMapper mapper = new ObjectMapper();
    JsonNode data = mapper.readTree(message);
    // Ignore heartbeat messages
    if ("heartbeat".equals(data.get("type").asText())) {
        return;
    }
    // Handle business messages
    handleBusinessMessage(data);
}
C#
using Newtonsoft.Json.Linq;

private void OnMessage(string message)
{
    var data = JObject.Parse(message);
    // Ignore heartbeat messages
    if (data["type"]?.ToString() == "heartbeat")
    {
        return;
    }
    // Handle business messages
    HandleBusinessMessage(data);
}
go
import "encoding/json"

func onMessage(message []byte) {
    var data map[string]interface{}
    json.Unmarshal(message, &data)
    // Ignore heartbeat messages
    if data["type"] == "heartbeat" {
        return
    }
    // Handle business messages
    handleBusinessMessage(data)
}
javascript
websocket.onmessage = function(event) {
    const message = JSON.parse(event.data);
    
    // Ignore heartbeat messages
    if (message.type === 'heartbeat') {
        return;
    }
    
    // Handle business messages
    handleBusinessMessage(message);
};

Client Disconnection Handling

Proper Disconnection Process

When clients actively disconnect, they need to send a close frame to ensure proper connection closure:

Python
def close_connection():
    if ws and not ws.closed:
        ws.close(code=1000, reason='Client closing connection')
        print("Close frame sent")
Java
public void closeConnection() {
    if (webSocket != null && webSocket.isOpen()) {
        webSocket.close(1000, "Client closing connection");
        System.out.println("Close frame sent");
    }
}
C#
public async Task CloseConnection()
{
    if (webSocket != null && webSocket.State == WebSocketState.Open)
    {
        await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, 
            "Client closing connection", CancellationToken.None);
        Console.WriteLine("Close frame sent");
    }
}
go
func closeConnection() {
    if conn != nil {
        err := conn.WriteMessage(websocket.CloseMessage, 
            websocket.FormatCloseMessage(websocket.CloseNormalClosure, 
            "Client closing connection"))
        if err == nil {
            fmt.Println("Close frame sent")
        }
        conn.Close()
    }
}
javascript
// Proper way to disconnect
function closeConnection() {
    if (websocket && websocket.readyState === WebSocket.OPEN) {
        // Send close frame
        websocket.close(1000, 'Client closing connection');
        console.log('Close frame sent');
    }
}

Disconnection Process Explanation

The complete WebSocket disconnection process includes the following steps:

  1. Client Initiates Close: Call websocket.close() to send close frame
  2. Server Confirmation: Server receives close frame and sends confirmation
  3. Connection Closed: Both sides complete handshake, TCP connection officially closed
  4. Resource Cleanup: Server automatically cleans up related connection resources

Listening to Disconnection Events

Python
def on_close(ws, close_status_code, close_msg):
    print(f"Connection closed: {close_status_code}, {close_msg}")
    # Clean up client resources
    cleanup()
Java
public void onClose(int code, String reason, boolean remote) {
    System.out.println("Connection closed: " + code + ", " + reason);
    // Clean up client resources
    cleanup();
}
C#
private void OnClose(WebSocketCloseStatus closeStatus, string statusDescription)
{
    Console.WriteLine($"Connection closed: {closeStatus}, {statusDescription}");
    // Clean up client resources
    Cleanup();
}
go
func onClose(code int, text string) {
    fmt.Printf("Connection closed: %d, %s\n", code, text)
    // Clean up client resources
    cleanup()
}
javascript
websocket.onclose = function(event) {
    console.log('Connection closed:', event.code, event.reason);
    // Clean up client resources
    cleanup();
};

Best Practices

  1. Monitor Heartbeats: Recommended to monitor heartbeat messages for connection status detection
  2. Set Timeout: Recommended to set a 60-second heartbeat timeout
  3. Auto Reconnect: Implement automatic reconnection when heartbeat timeout occurs
  4. Logging: Log heartbeat status to help with troubleshooting

Troubleshooting

Missing Heartbeat Messages

If heartbeat messages are not received for an extended period:

  1. Check network connection status
  2. Verify WebSocket connection is normal
  3. Check for firewall or proxy interference
  4. Consider re-establishing the connection

Frequent Connection Drops

If connections drop frequently:

  1. Check client network stability
  2. Ensure heartbeat messages are handled correctly
  3. Check for client resource limitations
  4. Contact technical support for assistance