HTTP/HTTPS 驗證機制
開發者必須使用其 API Key 和 API Secret 對每個請求進行簽名。簽名是透過將請求方法、請求路徑、時間戳記與請求參數/主體以 | 符號串接,然後應用 HMAC-SHA256 雜湊演算法生成的。簽名與相關元資料會包含在請求標頭中。伺服器會驗證簽名、驗證使用者身份,並授權後才處理請求。
📌 驗證流程概述
每個請求必須包含:
- API Key
- 時間戳記
- 使用 API Secret 與請求內容計算的簽名
API 會驗證:
- API Key 是否有效且啟用中
- 簽名是否與請求內容匹配
- 時間戳記是否在允許的範圍內(預設誤差 ±5 分鐘)
若以上任一項驗證失敗,請求將返回錯誤碼,如:
json
{"code":10010008,"message":"Signature verification failed"}📤 必填請求標頭
所有經過身份驗證的 API 請求必須包含下列自訂標頭:
| 標頭名稱 | 範例值 | 說明 |
|---|---|---|
X-API-Key | A1B2C3D4E5F6... | 您的公用 API 金鑰 |
X-API-Signature | a8f9c3e... | 經 HMAC-SHA256 產生的簽名 |
X-API-Timestamp | 1715100000000 | 當前的 UNIX 毫秒級時間戳 |
選填(建議用於追蹤):
| 標頭名稱 | 範例值 | 說明 |
|---|---|---|
X-REQUEST-ID | uuid-string | 用戶端自定請求 ID,便於除錯與日誌關聯追蹤 |
🔢 簽名生成步驟
使用 大寫 的 HTTP 請求方法(例如:
POST)。接著使用
|連接 API 路徑(不含網域與協議,必須以/開頭!如/trade/v1/orders)。再接上 UNIX 毫秒級時間戳。
最後接上請求參數:
- GET 請求:使用
key=value串連的查詢參數(不含?),若無則為空字串。注意,請求的參數不要改變其順序,要確保簽署的和請求的參數是一致的。 - 非 GET 請求:使用原始 JSON 字串作為 body,若無則為空字串。注意,請確保請求體和簽名的一致,不要因為類似格式化而改變,導致請求的和簽名的不是同一份正文。
- GET 請求:使用
最終簽名字串格式:
{request_method}|{request_path}|{timestamp}|{query_string_or_request_body}使用 API Secret 對此簽名字串進行 HMAC-SHA256 加密。
將結果進行 Base64 編碼。
將下列標頭加入請求:
X-API-KeyX-API-TimestampX-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,方便微服務間追蹤請求

