Signature
The Webull SDK has already encapsulated the signature process, so if you are using the Webull SDK, no extra handling is needed. This article provides a detailed explanation of how the signature value is generated.
Signature Overview
API request signatures are values generated from the request content and a secret key using a specific signature algorithm. This cryptographic approach ensures the integrity and authenticity of the API request data, prevents tampering during transmission, and verifies the identity of the request originator. To ensure secure communication between both parties, when using the OpenAPI, Webull performs security verification for each API request through the signature. Regardless of whether you submit requests via HTTP or HTTPS, you must include the signature value in the request header as shown:
x-signature: signature_value
Signature Composition
The signature content consists of four parts:
-
HTTP request path
-
HTTP request query params
-
HTTP request body
-
HTTP request headers
- The contents being signed do not require URL Encoding
- For HTTP POST requests,
Content-Typemust beapplication/json.
The relevant signature headers are:
| Name | Description |
|---|---|
| x-app-key | App Key |
| x-signature-algorithm | Signature algorithm, default is HMAC-SHA1 |
| x-signature-version | Signature algorithm version, default is 1.0 |
| x-signature-nonce | Signature nonce, regenerate for each request |
| x-timestamp | RFC-3339 formatted request timestamp, format: YYYY-MM-DDThh:mm:ssZ, e.g. 2023-07-16T19:23:51Z, only supports UTC timezone |
| host | The request's Host in host:port format, e.g. api.webull.hk:8080 or api.webull.hk |
Signature Rules
Content Participating in the Signature
1. HTTP request path
2. HTTP request query params
3. HTTP request body
4. HTTP request headers: x-app-key、x-signature-algorithm、x-signature-version、x-signature-nonce、x-timestamp、host
Constructing the Signature Content
-
Sort all parameter names (from both request params and headers) in ascending string order.
-
Join the parameter names and values in order as
name1=value1&name2=value2to create a new string called str1. -
Use MD5 to produce a 128-bit (16-byte) hash value for the body parameters and convert it to uppercase: toUpper(MD5(body)). Call this string str2.
-
Join
path,&,str1,&,str2in order to create a new string called str3. -
URL encode
str3to get encoded_string.
- There must be no spaces between body parameter keys and values.
- If the body is empty, then
str3 = path + "&" + str1
Constructing the Key
Append the character & to the end of your App Secret to get app_secret.
For info on how to obtain your App Secret, please refer to Applying for API.
Generating the Signature
Algorithm:
signature = base64(HMAC-SHA1(app_secret, encoded_string))
Example
Request Contents
Request Path:
/trade/place_order
Request Query Parameters:
| Name | Example Value | Note |
|---|---|---|
| a1 | webull | No URL Encoding needed |
| a2 | 123 | No URL Encoding needed |
| a3 | xxx | No URL Encoding needed |
| q1 | yyy | No URL Encoding needed |
Request Headers:
| Name | Example Value | Note |
|---|---|---|
| x-app-key | 776da210ab4a452795d74e726ebd74b6 | No URL Encoding needed |
| x-timestamp | 2022-01-04T03:55:31Z | No URL Encoding needed |
| x-signature-version | 1.0 | No URL Encoding needed |
| x-signature-algorithm | HMAC-SHA1 | No URL Encoding needed |
| x-signature-nonce | 48ef5afed43d4d91ae514aaeafbc29ba | No URL Encoding needed |
| host | api.webull.hk | No URL Encoding needed |
Request Body:
{"k1":123,"k2":"this is the api request body","k3":true,"k4":{"foo":[1,2]}}
App Secret:
0f50a2e853334a9aae1a783bee120c1f
Step 1: Constructing The Request Content
-
Sort all parameter names in ascending string order, resulting in:
Parameter name: a1, value: webull
Parameter name: a2, value: 123
Parameter name: a3, value: xxx
Header name: host, value: api.webull.hk
Parameter name: q1, value: yyy
Header name: x-app-key, value: 776da210ab4a452795d74e726ebd74b6
Header name: x-signature-algorithm, value: HMAC-SHA1
Header name: x-signature-nonce, value: 48ef5afed43d4d91ae514aaeafbc29ba
Header name: x-signature-version, value: 1.0
Header name: x-timestamp, value: 2022-01-04T03:55:31Z -
Join parameter names and values in order to get str1:
# str1
str1 = "a1=webull&a2=123&a3=xxx&host=api.webull.hk&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" -
Use the MD5 to produce hash value for the Body parameters and convert to uppercase:
toUpper(MD5 (body))to get str2:Body Parameter{"k1":123,"k2":"this is the api request body","k3":true,"k4":{"foo":[1,2]}}# str2
str2 = "E296C96787E1A309691CEF3692F5EEDD" -
Join
path,&,str1,&,str2in order to get str3:# str3
str3 = "/trade/place_order&a1=webull&a2=123&a3=xxx&host=api.webull.hk&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" -
URL encode str3 to get
encoded_string:# encoded_string
encoded_string = "%2Ftrade%2Fplace_order%26a1%3Dwebull%26a2%3D123%26a3%3Dxxx%26host%3Dapi.webull.hk%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"
Step2: Constructing The Key
Append the character & to the end of your App Secret to get app_secret, e.g.:
# app_secret
app_secret = "0f50a2e853334a9aae1a783bee120c1f&"
Step 3: Generating the Signature Value
The algorithm for generating the signature value:
signature = base64(HMAC-SHA1(app_secret, encoded_string))
-
Use the
HMAC-SHA1encryption algorithm, and encrypt theencoded_stringwith the keyapp_secret. -
Then, encode the encrypted result using Base64.
-
The final signature string is:
kvlS6opdZDhEBo5jq40nHYXaLvM=.
Additional Notes
-
If there are multiple parameters with the same name in the request, sort all values in ascending order, and then join them together using
&. For example:url?name1=value1&name1=value2&name1=value3After sorting the values in ascending order and combining with
&:name1 = value1&value2&value3 -
In Golang, the default for
json.MarshalhasescapeHtmlset totrue, which will escape<,>, and&. In this case, you need to replace the escaped content back to the original characters as shown below:func trans(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
}