Skip to content

HTTP/HTTPS Authentication

Developers must sign each request using their API Key and API Secret. The signature is generated by concatenating the request method, request path, timestamp, and request parameters/body with the | symbol, then applying an HMAC-SHA256 hash. The signature and relevant metadata are included in request headers. The server validates the signature, authenticates the user, and authorizes access before processing the request.

📌 Overview of the Authentication Process

Each request must include:

  • An API Key
  • A timestamp
  • A signature computed using your API Secret and request content

The API will verify:

  1. The API Key is valid and active
  2. The signature matches the request
  3. The timestamp is recent (default tolerance: ±5 minutes)

If any of these checks fail, the request will be rejected with an error code, such as:

json
{"code":10010008,"message":"Signature verification failed"}

📤 Required Request Headers

All authenticated API requests must include the following custom headers:

HeaderExampleDescription
X-API-KeyA1B2C3D4E5F6...Your public API Key
X-API-Signaturea8f9c3e...HMAC-SHA256 signature
X-API-Timestamp1715100000000Current Unix timestamp in milliseconds

Optional (recommended for traceability):

HeaderExampleDescription
X-REQUEST-IDuuid-stringClient-generated ID for debugging/log correlation

🔢 Signature Generation Steps

  1. Start with the HTTP request method in uppercase (e.g., POST).

  2. Append the API path (excluding domain and protocol, Must start with /! e.g., /trade/v1/orders) using |.

  3. Append the Unix timestamp (milliseconds, matching X-API-Timestamp) using |.

  4. Append the request parameters:

    • For GET requests:
      • If query parameters exist, concatenate them using & in key=value format (no need to sort), then append with |. Do not include the leading ?.
      • If no parameters exist, append an empty string. Note: Do not change the order of the request parameters. Ensure that the signature and request parameters are consistent.
    • For non-GET requests (e.g., POST, PUT):
      • Use the full raw JSON body string and append with |. Please ensure that the request body and signature are consistent. Do not change them due to similar formatting, which may cause the request body and signature to be different.
      • If the request body is empty, append an empty string.
  5. Concatenate all parts to form the signature string in the format:

    {request_method}|{request_path}|{timestamp}|{query_string_or_request_body}
  6. Use your API Secret to generate an HMAC-SHA256 hash of the signature string.

  7. Base64-encode the resulting hash.

  8. Add the following headers to your request:

    • X-API-Key
    • X-API-Timestamp
    • X-API-Signature

Example (String Construction Flow)

POST|/trade/v1/orders|1746774142003|{"market":"hkex","product_code":"00700",...}

The final signature is:

Base64(HMAC-SHA256(signature_string, API_Secret))

If an error occurs during the authentication process, please refer to the Error Codes section under the HTTP Return Codes document.


Sample Code

Python

import requests
import hmac
import hashlib
import base64
import time

class ApiClient:
    def __init__(self, api_key, api_secret, base_url):
        self.api_key = api_key
        self.api_secret = api_secret
        self.base_url = base_url

    def send_request(self, method, path, query_string='', body=''):
        timestamp = int(time.time() * 1000)  # Milliseconds
        signature_string = self.build_signature_string(method, path, timestamp, query_string, body)
        signature = self.generate_signature(signature_string, self.api_secret)

        url = self.base_url + path
        if query_string:
            url += '?' + query_string

        headers = {
            'X-API-Key': self.api_key,
            'X-API-Timestamp': str(timestamp),
            'X-API-Signature': signature
        }

        if method.upper() == 'GET':
            response = requests.get(url, headers=headers)
        else:
            response = requests.request(method.upper(), url, headers=headers, data=body)

        return response.text

    def build_signature_string(self, method, path, timestamp, query_string, body):
        signature_string = f"{method.upper()}|{path}|{timestamp}"
        if method.upper() == 'GET':
            signature_string += f"|{query_string}"
        else:
            signature_string += f"|{body}"
        return signature_string

    def generate_signature(self, data, secret):
        key = secret.encode('utf-8')
        message = data.encode('utf-8')
        hmac_obj = hmac.new(key, message, hashlib.sha256)
        return base64.b64encode(hmac_obj.digest()).decode('utf-8')

api_key = 'your_api_key_here'
api_secret = 'your_api_secret_here'
base_url = 'https://api.example.com'

client = ApiClient(api_key, api_secret, base_url)

# GET request with query parameters
get_response = client.send_request('GET', '/trade/v1/orders', 'symbol=BTCUSDT&page_size=10')
print('GET Response:', get_response)

# POST request with JSON body
post_body = '{"symbol":"BTCUSDT","side":"BUY","type":"LIMIT","price":"50000","quantity":"0.1"}'
post_response = client.send_request('POST', '/trade/v1/orders', body=post_body)
print('POST Response:', post_response)
Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import java.util.stream.Collectors;

public class ApiAuthExample {

    // Example usage
    public static void main(String[] args) {
        String apiKey = "YOUR_API_KEY";
        String apiSecret = "YOUR_API_SECRET";
        String baseUrl = "https://url_here";

        // GET request example
        Map<String, String> getParams = Map.of("symbol", "BTCUSDT", "page_size", "10");
        sendGetRequest(baseUrl + "/trade/v1/orders", getParams, apiKey, apiSecret);

        // POST request example
        String postBody = "{\"symbol\":\"BTC\",\"quantity\":1.5}";
        sendPostRequest(baseUrl + "/trade/v1/orders", postBody, apiKey, apiSecret);
    }

    /**
     * Sends a GET request with API authentication headers
     * @param url Full endpoint URL with protocol and domain
     * @param params Query parameters (key-value pairs)
     * @param apiKey API key credential
     * @param apiSecret API secret credential
     */
    public static void sendGetRequest(String url, Map<String, String> params, String apiKey, String apiSecret) {
        try {
            long timestamp = System.currentTimeMillis();
            String queryString = buildQueryString(params);
            URL urlObj = new URL(url);

            // Build signature components
            String path = urlObj.getPath();
            String signatureData = String.format("GET|%s|%d|%s", path, timestamp, queryString);
            String signature = generateSignature(signatureData, apiSecret);

            // Create connection
            String fullUrl = queryString.isEmpty() ? url : url + "?" + queryString;
            HttpURLConnection conn = (HttpURLConnection) new URL(fullUrl).openConnection();
            conn.setRequestMethod("GET");

            // Set headers
            conn.setRequestProperty("X-API-Key", apiKey);
            conn.setRequestProperty("X-API-Timestamp", String.valueOf(timestamp));
            conn.setRequestProperty("X-API-Signature", signature);

            // Handle response
            int responseCode = conn.getResponseCode();
            System.out.println("GET Response Code: " + responseCode);
            try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(conn.getInputStream()))) {
                String line;
                StringBuilder response = new StringBuilder();
                while ((line = reader.readLine()) != null) {
                    response.append(line);
                }
                System.out.println("Response Body: " + response);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Sends a POST request with API authentication headers
     * @param url Full endpoint URL with protocol and domain
     * @param requestBody JSON-formatted request body
     * @param apiKey API key credential
     * @param apiSecret API secret credential
     */
    public static void sendPostRequest(String url, String requestBody, String apiKey, String apiSecret) {
        try {
            long timestamp = System.currentTimeMillis();
            URL urlObj = new URL(url);

            // Build signature components
            String path = urlObj.getPath();
            String signatureData = String.format("POST|%s|%d|%s", path, timestamp, requestBody);
            String signature = generateSignature(signatureData, apiSecret);

            // Create connection
            HttpURLConnection conn = (HttpURLConnection) urlObj.openConnection();
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setDoOutput(true);

            // Set headers
            conn.setRequestProperty("X-API-Key", apiKey);
            conn.setRequestProperty("X-API-Timestamp", String.valueOf(timestamp));
            conn.setRequestProperty("X-API-Signature", signature);

            // Send request body
            try (OutputStream os = conn.getOutputStream()) {
                byte[] input = requestBody.getBytes(StandardCharsets.UTF_8);
                os.write(input, 0, input.length);
            }

            // Handle response
            int responseCode = conn.getResponseCode();
            System.out.println("POST Response Code: " + responseCode);
            try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(conn.getInputStream()))) {
                String line;
                StringBuilder response = new StringBuilder();
                while ((line = reader.readLine()) != null) {
                    response.append(line);
                }
                System.out.println("Response Body: " + response);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Generates HMAC-SHA256 signature for request authentication
     * @param data Concatenated signature string
     * @param apiSecret API secret credential
     * @return Base64-encoded signature
     */
    private static String generateSignature(String data, String apiSecret) {
        try {
            Mac hmac = Mac.getInstance("HmacSHA256");
            SecretKeySpec keySpec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            hmac.init(keySpec);
            byte[] rawSignature = hmac.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(rawSignature);
        } catch (Exception e) {
            throw new RuntimeException("Failed to generate signature", e);
        }
    }

    /**
     * Builds URL query string from parameters
     * @param params Map of query parameters
     * @return URL-encoded query string (without leading '?')
     */
    private static String buildQueryString(Map<String, String> params) {
        if (params == null || params.isEmpty()) return "";
        return params.entrySet().stream()
            .map(entry -> entry.getKey() + "=" + entry.getValue())
            .collect(Collectors.joining("&"));
    }
}
C#
public class ApiClient
{
    private readonly string _apiKey;
    private readonly string _apiSecret;
    private readonly HttpClient _httpClient;

    public ApiClient(string apiKey, string apiSecret, string baseUrl)
    {
        _apiKey = apiKey;
        _apiSecret = apiSecret;
        _httpClient = new HttpClient { BaseAddress = new Uri(baseUrl) };
    }

    public async Task<string> SendRequestAsync(string method, string path, string queryString = "", string body = "")
    {
        // Generate current Unix timestamp in milliseconds
        long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();

        // Build the signature string
        string signatureString = BuildSignatureString(method, path, timestamp, queryString, body);

        // Generate the HMAC-SHA256 signature and encode it to Base64
        string signature = GenerateSignature(signatureString, _apiSecret);

        // Prepare the HTTP request
        var request = new HttpRequestMessage(new HttpMethod(method),
            path + (string.IsNullOrEmpty(queryString) ? "" : "?" + queryString));

        if (!method.Equals("GET", StringComparison.CurrentCultureIgnoreCase) && !string.IsNullOrEmpty(body))
        {
            request.Content = new StringContent(body, Encoding.UTF8, "application/json");
        }


        // Add required headers
        request.Headers.Add("X-API-Key", _apiKey);
        request.Headers.Add("X-API-Timestamp", timestamp.ToString());
        request.Headers.Add("X-API-Signature", signature);

        // Send the request and return the response
        var response = await _httpClient.SendAsync(request);

        // response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }

    private string BuildSignatureString(string method, string path, long timestamp, string queryString, string body)
    {
        // Start with method (uppercase) and concatenate path and timestamp
        string signatureString = $"{method.ToUpper()}|{path}|{timestamp}";

        // Append query string for GET or body for POST
        if (method.ToUpper() == "GET")
        {
            signatureString += "|" + queryString;
        }
        else
        {
            signatureString += "|" + (body ?? "");
        }
        return signatureString;
    }

    private string GenerateSignature(string data, string secret)
    {
        using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)))
        {
            byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
            return Convert.ToBase64String(hash);
        }
    }
}

class Program
{
    static async Task Main(string[] args)
    {
        string apiKey = "your_api_key_here";
        string apiSecret = "your_api_secret_here";
        string baseUrl = "https://url_here";

        var client = new ApiClient(apiKey, apiSecret, baseUrl);

        // Example 1: GET request with query parameters
        string getResponse = await client.SendRequestAsync(
            "GET",
            "/trade/v1/orders",
            "symbol=BTCUSDT&page_size=10"
        );
        Console.WriteLine("GET Response: " + getResponse);

        // Example 2: POST request with JSON body
        string postBody = "{\"symbol\":\"BTCUSDT\",\"side\":\"BUY\",\"type\":\"LIMIT\",\"price\":\"50000\",\"quantity\":\"0.1\"}";
        string postResponse = await client.SendRequestAsync(
            "POST",
            "/trade/v1/orders",
            body: postBody
        );
        Console.WriteLine("POST Response: " + postResponse);
    }
}
Go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
)

type ApiClient struct {
	apiKey    string
	apiSecret string
	client    *http.Client
	baseUrl   string
}

func NewApiClient(apiKey, apiSecret, baseUrl string) *ApiClient {
	return &ApiClient{
		apiKey:    apiKey,
		apiSecret: apiSecret,
		client:    &http.Client{},
		baseUrl:   baseUrl,
	}
}

func (c *ApiClient) SendRequest(method, path, queryString, body string) (string, error) {
	timestamp := time.Now().UnixNano() / 1e6 // Milliseconds
	signatureString := c.buildSignatureString(method, path, timestamp, queryString, body)
	signature := c.generateSignature(signatureString, c.apiSecret)

	url := c.baseUrl + path
	if queryString != "" {
		url += "?" + queryString
	}

	req, err := http.NewRequest(strings.ToUpper(method), url, strings.NewReader(body))
	if err != nil {
		return "", err
	}

	req.Header.Add("X-API-Key", c.apiKey)
	req.Header.Add("X-API-Timestamp", fmt.Sprintf("%d", timestamp))
	req.Header.Add("X-API-Signature", signature)
	if method != "GET" && body != "" {
		req.Header.Add("Content-Type", "application/json")
	}

	resp, err := c.client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	return string(responseBody), nil
}

func (c *ApiClient) buildSignatureString(method, path string, timestamp int64, queryString, body string) string {
	signatureString := fmt.Sprintf("%s|%s|%d", strings.ToUpper(method), path, timestamp)
	if strings.ToUpper(method) == "GET" {
		signatureString += "|" + queryString
	} else {
		signatureString += "|" + body
	}
	return signatureString
}

func (c *ApiClient) generateSignature(data, secret string) string {
	key := []byte(secret)
	h := hmac.New(sha256.New, key)
	h.Write([]byte(data))
	return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

func main() {
	apiKey := "your_api_key_here"
	apiSecret := "your_api_secret_here"
	baseUrl := "https://url_here"

	client := NewApiClient(apiKey, apiSecret, baseUrl)

	// GET request with query parameters
	getResponse, err := client.SendRequest("GET", "/trade/v1/orders", "symbol=BTCUSDT&page_size=10", "")
	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("GET Response:", getResponse)
	}

	// POST request with JSON body
	postBody := `{"symbol":"BTCUSDT","side":"BUY","type":"LIMIT","price":"50000","quantity":"0.1"}`
	postResponse, err := client.SendRequest("POST", "/trade/v1/orders", "", postBody)
	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("POST Response:", postResponse)
	}
}
js
const axios = require('axios');
const crypto = require('crypto');

class ApiClient {
	constructor(apiKey, apiSecret, baseUrl) {
		this.apiKey = apiKey;
		this.apiSecret = apiSecret;
		this.baseUrl = baseUrl;
	}

	async sendRequest(method, path, queryString = '', body = '') {
		const timestamp = Date.now();
		const signatureString = this.buildSignatureString(method, path, timestamp, queryString, body);
		const signature = this.generateSignature(signatureString, this.apiSecret);

		const url = this.baseUrl + path + (queryString ? `?${queryString}` : '');
		const headers = {
			'X-API-Key': this.apiKey,
			'X-API-Timestamp': timestamp.toString(),
			'X-API-Signature': signature,
		};

		try {
			const response = await axios({
				method: method.toUpperCase(),
				url,
				headers,
				data: body,
			});
			return response.data;
		} catch (error) {
			console.error('Request failed:', error);
			throw error;
		}
	}

	buildSignatureString(method, path, timestamp, queryString, body) {
		let signatureString = `${method.toUpperCase()}|${path}|${timestamp}`;
		if (method.toUpperCase() === 'GET') {
			signatureString += `|${queryString}`;
		} else {
			signatureString += `|${body}`;
		}
		return signatureString;
	}

	generateSignature(data, secret) {
		const hmac = crypto.createHmac('sha256', secret);
		hmac.update(data);
		return hmac.digest('base64');
	}
}

const apiKey = 'your_api_key_here';
const apiSecret = 'your_api_secret_here';
const baseUrl = 'https://url_here';

const client = new ApiClient(apiKey, apiSecret, baseUrl);

// GET request with query parameters
client
	.sendRequest('GET', '/trade/v1/orders', 'symbol=BTCUSDT&page_size=10')
	.then(response => console.log('GET Response:', response))
	.catch(error => console.error('Error:', error));

// POST request with JSON body
const postBody = JSON.stringify({
	symbol: 'BTCUSDT',
	side: 'BUY',
	type: 'LIMIT',
	price: '50000',
	quantity: '0.1',
});
client
	.sendRequest('POST', '/trade/v1/orders', '', postBody)
	.then(response => console.log('POST Response:', response))
	.catch(error => console.error('Error:', error));

🧷 Security Best Practices

  • Never share your Secret; store it securely (e.g., in a key vault or encrypted file)
  • Use timestamp validation to prevent replay attacks
  • Rotate keys regularly via the API key management panel
  • Restrict IPs if possible (IP whitelisting)
  • Use UUIDs in X-REQUEST-ID to trace requests across microservices