Signature
Every API request to Webull must include a cryptographic signature in the request header. The signature is computed from the request content and your App Secret, ensuring the integrity and authenticity of each request.
x-signature: <signature_value>
The Webull SDK handles signature generation automatically. If you're using the SDK, you can skip this page — it's here for those implementing signature logic manually.
Required Request Headers
Every API request must include the following headers:
| Header | Required | Description |
|---|---|---|
x-app-key | Yes | A unique identifier issued to a developer for accessing the API |
x-timestamp | Yes | Request timestamp in ISO 8601 format: YYYY-MM-DDThh:mm:ssZ (UTC only) |
x-signature | Yes | The computed signature value (output of the algorithm described below) |
x-signature-algorithm | Yes | Signature algorithm (e.g. HMAC-SHA256) |
x-signature-version | Yes | Signature algorithm version (e.g. 1.0) |
x-signature-nonce | Yes | Unique random string, regenerated for each request |
x-version | Yes | Interface version (accepts v2) |
The app_secret is a unique key issued to developers. It is not included in any HTTP request header — it is used solely on the client side for signature generation. See Step 2: Construct the Key for details.
What Gets Signed
The signature is computed from four parts of the HTTP request:
- Request path
- Query parameters
- Request body
- Signing headers — the following headers participate in signature computation:
x-app-keyx-signature-algorithmx-signature-versionx-signature-noncex-timestamphost
x-signature and x-version do not participate in signing. x-signature carries the output of the signature itself; x-version is a required request header but is excluded from the signature computation.
- The content being signed does not require URL Encoding at this stage.
- For POST requests,
Content-Typemust beapplication/json.
Signature Algorithm
Step 1: Construct the Signature String
- Merge all query parameters and the signing headers (listed in What Gets Signed) into a single list.
- Sort all parameter names in ascending alphabetical order.
- Join them as
name1=value1&name2=value2&...→ this isstr1. - If the request has a body, compute its MD5 hash and convert to uppercase:
toUpper(MD5(body))→ this isstr2. - Concatenate:
str3=path+&+str1+&+str2- If the body is empty:
str3=path+&+str1
- If the body is empty:
- URL-encode
str3→ this isencoded_string.
- There must be no extra spaces between body parameter keys and values.
- If the body is empty, omit
str2entirely.
Step 2: Construct the Key
Append & to the end of your App Secret:
app_secret = "<your_app_secret>&"
Step 3: Generate the Signature
signature = base64(HMAC-SHA256(app_secret, encoded_string))
Worked Example
Below is a complete example showing each step of the signature generation process.
Request Details
Path: /trade/place_order
Query Parameters:
| Name | Value |
|---|---|
| a1 | webull |
| a2 | 123 |
| a3 | xxx |
| q1 | yyy |
Request Headers:
| Name | Value |
|---|---|
| x-app-key | 776da210ab4a452795d74e726ebd74b6 |
| x-timestamp | 2022-01-04T03:55:31Z |
| x-signature-version | 1.0 |
| x-signature-algorithm | HMAC-SHA256 |
| x-signature-nonce | 48ef5afed43d4d91ae514aaeafbc29ba |
| host | api.webull.com.au |
Body:
{"k1":123,"k2":"this is the api request body","k3":true,"k4":{"foo":[1,2]}}
App Secret: 0f50a2e853334a9aae1a783bee120c1f
Step 1: Construct the Signature String
-
Merge query parameters and signing headers into a single list, then sort all parameter names in ascending alphabetical order:
a1=webull, a2=123, a3=xxx,
host=api.webull.com.au,
q1=yyy,
x-app-key=776da210ab4a452795d74e726ebd74b6,
x-signature-algorithm=HMAC-SHA256,
x-signature-nonce=48ef5afed43d4d91ae514aaeafbc29ba,
x-signature-version=1.0,
x-timestamp=2022-01-04T03:55:31Z -
Join them as key=value pairs with
&→ str1:a1=webull&a2=123&a3=xxx&host=api.webull.com.au&q1=yyy&x-app-key=776da210ab4a452795d74e726ebd74b6&x-signature-algorithm=HMAC-SHA256&x-signature-nonce=48ef5afed43d4d91ae514aaeafbc29ba&x-signature-version=1.0&x-timestamp=2022-01-04T03:55:31Z -
Compute MD5 of the body and convert to uppercase → str2:
E296C96787E1A309691CEF3692F5EEDD -
Concatenate path +
&+ str1 +&+ str2 → str3:/trade/place_order&a1=webull&a2=123&a3=xxx&host=api.webull.com.au&q1=yyy&x-app-key=776da210ab4a452795d74e726ebd74b6&x-signature-algorithm=HMAC-SHA256&x-signature-nonce=48ef5afed43d4d91ae514aaeafbc29ba&x-signature-version=1.0&x-timestamp=2022-01-04T03:55:31Z&E296C96787E1A309691CEF3692F5EEDD -
URL-encode str3 → encoded_string
Step 2: Construct the Key
app_secret = "0f50a2e853334a9aae1a783bee120c1f&"
Step 3: Generate the Signature
signature = base64(HMAC-SHA256(app_secret, encoded_string))
Use the values above to test your signature code.
Edge Cases
Duplicate Parameter Names
If a request contains multiple parameters with the same name, sort all values in ascending order and join them with &, then use the combined value in str1.
JSON Body Serialization
When computing the MD5 hash of the request body, ensure the JSON string has no extra spaces between keys and values (use compact serialization like separators=(',', ':') in Python or equivalent in your language).
Additionally, the JSON body used for MD5 computation must be exactly the same string sent in the HTTP request body.
Language-Specific HTML Escaping
Some languages automatically escape special characters in JSON output. You must reverse these escapes before computing the body MD5.