跳轉到內容

HTTP/HTTPS 驗證機制

開發者必須使用其 API KeyAPI Secret 對每個請求進行簽名。簽名是透過將請求方法、請求路徑、時間戳記與請求參數/主體以 | 符號串接,然後應用 HMAC-SHA256 雜湊演算法生成的。簽名與相關元資料會包含在請求標頭中。伺服器會驗證簽名、驗證使用者身份,並授權後才處理請求。

📌 驗證流程概述

每個請求必須包含:

  • API Key
  • 時間戳記
  • 使用 API Secret 與請求內容計算的簽名

API 會驗證:

  1. API Key 是否有效且啟用中
  2. 簽名是否與請求內容匹配
  3. 時間戳記是否在允許的範圍內(預設誤差 ±5 分鐘)

若以上任一項驗證失敗,請求將返回錯誤碼,如:

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

📤 必填請求標頭

所有經過身份驗證的 API 請求必須包含下列自訂標頭:

標頭名稱範例值說明
X-API-KeyA1B2C3D4E5F6...您的公用 API 金鑰
X-API-Signaturea8f9c3e...經 HMAC-SHA256 產生的簽名
X-API-Timestamp1715100000000當前的 UNIX 毫秒級時間戳

選填(建議用於追蹤)

標頭名稱範例值說明
X-REQUEST-IDuuid-string用戶端自定請求 ID,便於除錯與日誌關聯追蹤

🔢 簽名生成步驟

  1. 使用 大寫 的 HTTP 請求方法(例如:POST)。

  2. 接著使用 | 連接 API 路徑(不含網域與協議,必須以/開頭!如 /trade/v1/orders)。

  3. 再接上 UNIX 毫秒級時間戳。

  4. 最後接上請求參數:

    • GET 請求:使用 key=value 串連的查詢參數(不含 ?),若無則為空字串。注意,請求的參數不要改變其順序,要確保簽署的和請求的參數是一致的。
    • 非 GET 請求:使用原始 JSON 字串作為 body,若無則為空字串。注意,請確保請求體和簽名的一致,不要因為類似格式化而改變,導致請求的和簽名的不是同一份正文。
  5. 最終簽名字串格式:

{request_method}|{request_path}|{timestamp}|{query_string_or_request_body}
  1. 使用 API Secret 對此簽名字串進行 HMAC-SHA256 加密。

  2. 將結果進行 Base64 編碼

  3. 將下列標頭加入請求:

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

範例(簽名字串構造流程):

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

最終簽名為:

Base64(HMAC-SHA256(signature_string, API_Secret))

如果驗證程序報錯,請參閱HTTP 回傳代碼文檔下的錯誤碼章節


示例代码

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));

🧷 安全最佳實踐

  • 絕不可洩漏您的 Secret,建議安全儲存於金鑰管理系統或加密檔中
  • 進行時間戳驗證 以防止重放攻擊
  • 定期更換金鑰,可透過 API 金鑰管理面板操作
  • 限制 IP 存取(建議使用 IP 白名單)
  • X-REQUEST-ID 中使用 UUID,方便微服務間追蹤請求