簽名
每個發送至 Webull 的 API 請求都必須在請求標頭中包含加密簽名。簽名由請求內容和 App Secret 計算生成,確保每個請求的完整性和真實性。
x-signature: <signature_value>
Webull SDK 會自動處理簽名生成。如果您使用 SDK,可以跳過本頁 — 本文適用於需要手動實現簽名邏輯的開發者。
必要的請求標頭
每個 API 請求必須包含以下標頭:
| 標頭 | 必填 | 說明 |
|---|---|---|
x-app-key | 是 | 發給開發者用於存取 API 的唯一識別碼 |
x-timestamp | 是 | 請求時間戳,ISO 8601 格式:YYYY-MM-DDThh:mm:ssZ(僅限 UTC) |
x-signature | 是 | 計算生成的簽名值(即下方演算法的輸出結果) |
x-signature-algorithm | 是 | 簽名演算法(如 HMAC-SHA1) |
x-signature-version | 是 | 簽名演算法版本(如 1.0) |
x-signature-nonce | 是 | 唯一隨機字串,每次請求重新生成 |
x-version | 是 | 介面版本(接受 v2) |
app_secret 是發給開發者的唯一密鑰。它不會包含在任何 HTTP 請求標頭中 — 僅在客戶端用於簽名生成。詳見步驟 2:構建密鑰。
簽名內容
簽名由 HTTP 請求的四個部分計算而成:
-
請求路徑
-
查詢參數
-
請求主體
-
簽名標頭 — 以下標頭參與簽名計算:
x-app-keyx-signature-algorithmx-signature-versionx-signature-noncex-timestamphost
備註x-signature和x-version不參與簽名計算。x-signature承載簽名本身的輸出結果;x-version是必要的請求標頭,但不納入簽名計算。
- 參與簽名的內容在此階段不需要 URL 編碼。
- POST 請求的
Content-Type必須為application/json。
簽名演算法
步驟 1:構建簽名字串
- 將所有查詢參數和簽名標頭(見簽名內容中的列表)合併為一個列表。
- 按參數名稱的字母升序排列。
- 以
name1=value1&name2=value2&...格式拼接 → 得到str1。 - 如果請求有主體,計算其 MD5 雜湊值並轉為大寫:
toUpper(MD5(body))→ 得到str2。 - 拼接:
str3=path+&+str1+&+str2- 如果主體為空:
str3=path+&+str1
- 如果主體為空:
- 對
str3進行 URL 編碼 → 得到encoded_string。
- 主體參數的鍵和值之間不得有多餘空格。
- 如果主體為空,完全省略
str2。
步驟 2:構建密鑰
在 App Secret 末尾附加字元 &:
app_secret = "<your_app_secret>&"
步驟 3:生成簽名
signature = base64(HMAC-SHA1(app_secret, encoded_string))
完整範例
以下是簽名生成過程的完整範例。
請求詳情
路徑: /trade/place_order
查詢參數:
| 名稱 | 值 |
|---|---|
| a1 | webull |
| a2 | 123 |
| a3 | xxx |
| q1 | yyy |
請求標頭:
| 名稱 | 值 |
|---|---|
| x-app-key | 776da210ab4a452795d74e726ebd74b6 |
| x-timestamp | 2022-01-04T03:55:31Z |
| x-signature-version | 1.0 |
| x-signature-algorithm | HMAC-SHA1 |
| x-signature-nonce | 48ef5afed43d4d91ae514aaeafbc29ba |
| host | api.webull.com |
主體:
{"k1":123,"k2":"this is the api request body","k3":true,"k4":{"foo":[1,2]}}
App Secret: 0f50a2e853334a9aae1a783bee120c1f
步驟 1:構建簽名字串
-
將查詢參數和簽名標頭合併為一個列表,按參數名稱的字母升序排列:
a1=webull, a2=123, a3=xxx,
host=api.webull.com,
q1=yyy,
x-app-key=776da210ab4a452795d74e726ebd74b6,
x-signature-algorithm=HMAC-SHA1,
x-signature-nonce=48ef5afed43d4d91ae514aaeafbc29ba,
x-signature-version=1.0,
x-timestamp=2022-01-04T03:55:31Z -
以
key=value格式用&拼接 → str1:a1=webull&a2=123&a3=xxx&host=api.webull.com&q1=yyy&x-app-key=776da210ab4a452795d74e726ebd74b6&x-signature-algorithm=HMAC-SHA1&x-signature-nonce=48ef5afed43d4d91ae514aaeafbc29ba&x-signature-version=1.0&x-timestamp=2022-01-04T03:55:31Z -
計算主體的 MD5 並轉為大寫 → str2:
E296C96787E1A309691CEF3692F5EEDD -
拼接 path +
&+ str1 +&+ str2 → str3:/trade/place_order&a1=webull&a2=123&a3=xxx&host=api.webull.com&q1=yyy&x-app-key=776da210ab4a452795d74e726ebd74b6&x-signature-algorithm=HMAC-SHA1&x-signature-nonce=48ef5afed43d4d91ae514aaeafbc29ba&x-signature-version=1.0&x-timestamp=2022-01-04T03:55:31Z&E296C96787E1A309691CEF3692F5EEDD -
對 str3 進行 URL 編碼 → encoded_string:
%2Ftrade%2Fplace_order%26a1%3Dwebull%26a2%3D123%26a3%3Dxxx%26host%3Dapi.webull.com%26q1%3Dyyy%26x-app-key%3D776da210ab4a452795d74e726ebd74b6%26x-signature-algorithm%3DHMAC-SHA1%26x-signature-nonce%3D48ef5afed43d4d91ae514aaeafbc29ba%26x-signature-version%3D1.0%26x-timestamp%3D2022-01-04T03%3A55%3A31Z%26E296C96787E1A309691CEF3692F5EEDD
完整範例將演算法步驟 1–3 合併為一步以提高可讀性,邏輯與上方 6 步演算法完全一致。
步驟 2:構建密鑰
app_secret = "0f50a2e853334a9aae1a783bee120c1f&"
步驟 3:生成簽名
signature = base64(HMAC-SHA1(app_secret, encoded_string))
結果: kvlS6opdZDhEBo5jq40nHYXaLvM=
邊界情況
重複參數名稱
如果請求中包含多個同名參數,按值的升序排列後以 & 拼接,然後將合併後的值用於 str1:
# URL: /path?name1=value1&name1=value2&name1=value3
# 按值升序排列後:
name1 = value1&value2&value3
# 合併後的值在 str1 中的形式:
# name1=value1&value2&value3
即重複的鍵合併為一個 name1=... 條目,所有值以 & 拼接。
JSON 主體序列化
計算請求主體的 MD5 雜湊值時,確保 JSON 字串的鍵和值之間沒有多餘空格(使用緊湊序列化,如 Python 中的 separators=(',', ':'),或其他語言的等效方式)。
語言特定的 HTML 轉義
部分程式語言會自動轉義 JSON 輸出中的特殊字元。計算主體 MD5 前必須還原這些轉義。例如:
Go — json.Marshal 預設會轉義 <、> 和 &(escapeHtml = true):
func unescapeJSON(data []byte) []byte {
data = bytes.Replace(data, []byte("\\u0026"), []byte("&"), -1)
data = bytes.Replace(data, []byte("\\u003c"), []byte("<"), -1)
data = bytes.Replace(data, []byte("\\u003e"), []byte(">"), -1)
return data
}
如果您使用的語言或框架有類似行為,請確保使用原始 JSON(未經 HTML 轉義)進行簽名計算。
常見問題
如果收到 INVALID_TOKEN 或 SIGNATURE_INVALID 錯誤,請檢查以下幾點:
-
請求主體序列化不一致 — 用於計算 MD5 的 JSON 主體必須與 HTTP 請求中實際發送的字串完全一致。如果在 Python 的
requests.post()中使用json=body,函式庫會自行序列化主體,可能產生與您計算 MD5 時不同的字串。請自行序列化主體(如json.dumps(body, separators=(',', ':'))),並以data=body_string搭配Content-Type: application/json發送。 -
緊湊 JSON — 使用無空格的緊湊序列化(如 Python 中的
separators=(',', ':'))。多餘的空格會改變 MD5 雜湊值,導致簽名驗證失敗。