Code Examples
This document provides API integration examples for different programming languages, applicable to all endpoints that require signatures (common to both Spot and Contract).
Signature Generation Methods
All endpoints that require signatures (TRADE and USER_DATA) need to use the HMAC SHA256 algorithm to generate signatures.
Python
python
import hmac
import hashlib
import time
def generate_signature(secret_key, params):
"""
Generate HMAC SHA256 signature
:param secret_key: API Secret Key
:param params: Parameter dictionary (excluding signature)
:return: Signature string
"""
# Concatenate parameters into query string format
query_string = "&".join([f"{k}={v}" for k, v in params.items()])
# Generate HMAC SHA256 signature
signature = hmac.new(
secret_key.encode('utf-8'),
query_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signatureJava
java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.stream.Collectors;
public class SignatureUtil {
public static String generateSignature(String secretKey, String queryString)
throws NoSuchAlgorithmException, InvalidKeyException {
// Generate HMAC SHA256 signature
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(
secretKey.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
mac.init(secretKeySpec);
byte[] hash = mac.doFinal(queryString.getBytes(StandardCharsets.UTF_8));
return bytesToHex(hash);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
}Node.js
javascript
const crypto = require('crypto');
/**
* Generate HMAC SHA256 signature
* @param {string} secretKey - API Secret Key
* @param {Object} params - Parameter dictionary (excluding signature)
* @returns {string} Signature string
*/
function generateSignature(secretKey, params) {
// Concatenate parameters into query string format
const queryString = Object.keys(params)
.map(key => `${key}=${params[key]}`)
.join('&');
// Generate HMAC SHA256 signature
const signature = crypto
.createHmac('sha256', secretKey)
.update(queryString)
.digest('hex');
return signature;
}Go
go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
)
// GenerateSignature generates HMAC SHA256 signature
func GenerateSignature(secretKey string, params map[string]string) string {
// Concatenate query string
var parts []string
for k, v := range params {
parts = append(parts, fmt.Sprintf("%s=%s", k, v))
}
queryString := strings.Join(parts, "&")
// Generate HMAC SHA256 signature
mac := hmac.New(sha256.New, []byte(secretKey))
mac.Write([]byte(queryString))
signature := hex.EncodeToString(mac.Sum(nil))
return signature
}Complete Request Examples
The following examples demonstrate how to call an endpoint that requires a signature (applicable to all TRADE and USER_DATA endpoints).
Python Complete Example
python
import requests
import hmac
import hashlib
import time
def call_signed_api(endpoint, params, api_key, secret_key, method='POST'):
"""
Call an API endpoint that requires a signature
:param endpoint: API endpoint path, e.g., '/api/v1/futures/order' or '/api/v1/spot/order'
:param params: Parameter dictionary
:param api_key: API Key
:param secret_key: Secret Key
:param method: HTTP method, 'GET' or 'POST'
:return: Response result
"""
# Add timestamp (required)
params['timestamp'] = int(time.time() * 1000)
# Optional: Add recvWindow
if 'recvWindow' not in params:
params['recvWindow'] = 5000
# Generate signature (using the same query_string format)
query_string_for_sign = "&".join([f"{k}={v}" for k, v in params.items()])
signature = hmac.new(
secret_key.encode('utf-8'),
query_string_for_sign.encode('utf-8'),
hashlib.sha256
).hexdigest()
params['signature'] = signature
# Build complete query string (including signature, using the same order as signature generation)
query_string = "&".join([f"{k}={v}" for k, v in params.items()])
# Send request (using the same query_string format to ensure consistent order)
url = f"https://api.toobit.com{endpoint}"
headers = {"X-BB-APIKEY": api_key}
if method == 'GET':
# GET request: Manually build query string to ensure consistent order with signature
url_with_params = f"{url}?{query_string}"
response = requests.get(url_with_params, headers=headers)
else:
# POST request: Use manually built query string as body to ensure consistent order
headers["Content-Type"] = "application/x-www-form-urlencoded"
response = requests.post(url, headers=headers, data=query_string)
return response.json()
# Usage example
if __name__ == "__main__":
api_key = "your-api-key"
secret_key = "your-secret-key"
# Example 1: Query account balance (GET request)
params = {
"recvWindow": 5000
}
result = call_signed_api("/api/v1/futures/balance", params, api_key, secret_key, method='GET')
print(result)
# Example 2: Place order (POST request)
order_params = {
"symbol": "BTC-SWAP-USDT",
"side": "BUY_OPEN",
"type": "LIMIT",
"quantity": "10",
"price": "30000",
"newClientOrderId": "test_order_001",
"recvWindow": 5000
}
result = call_signed_api("/api/v1/futures/order", order_params, api_key, secret_key, method='POST')
print(result)Java Complete Example
java
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.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class ApiClient {
private String apiKey;
private String secretKey;
private String baseUrl = "https://api.toobit.com";
public ApiClient(String apiKey, String secretKey) {
this.apiKey = apiKey;
this.secretKey = secretKey;
}
private String buildQueryString(Map<String, String> params) {
// Build query string (using the same order for signature and request submission)
return params.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
}
public String callSignedApi(String endpoint, Map<String, String> params, String method)
throws Exception {
// Add timestamp
params.put("timestamp", String.valueOf(System.currentTimeMillis()));
// Add recvWindow (if not present)
if (!params.containsKey("recvWindow")) {
params.put("recvWindow", "5000");
}
// Generate signature (using buildQueryString to ensure consistent order)
String queryStringForSign = buildQueryString(params);
String signature = SignatureUtil.generateSignature(secretKey, queryStringForSign);
params.put("signature", signature);
// Build query string (using the same order as signature generation)
String queryString = buildQueryString(params);
// Build URL
String urlString = baseUrl + endpoint;
if ("GET".equals(method)) {
urlString += "?" + queryString;
}
// Send request
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(method);
conn.setRequestProperty("X-BB-APIKEY", apiKey);
if ("POST".equals(method)) {
conn.setDoOutput(true);
// POST request: Use the same query string as body as used for signature
try (OutputStream os = conn.getOutputStream()) {
os.write(queryString.getBytes(StandardCharsets.UTF_8));
}
}
// Read response
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
return response.toString();
}
// Usage example
public static void main(String[] args) throws Exception {
ApiClient client = new ApiClient("your-api-key", "your-secret-key");
// Example 1: Query account balance (GET request)
Map<String, String> params1 = new HashMap<>();
params1.put("recvWindow", "5000");
String result1 = client.callSignedApi("/api/v1/futures/balance", params1, "GET");
System.out.println(result1);
// Example 2: Place order (POST request)
Map<String, String> params2 = new HashMap<>();
params2.put("symbol", "BTC-SWAP-USDT");
params2.put("side", "BUY_OPEN");
params2.put("type", "LIMIT");
params2.put("quantity", "10");
params2.put("price", "30000");
params2.put("newClientOrderId", "test_order_001");
params2.put("recvWindow", "5000");
String result2 = client.callSignedApi("/api/v1/futures/order", params2, "POST");
System.out.println(result2);
}
}Node.js Complete Example
javascript
const crypto = require('crypto');
const https = require('https');
const querystring = require('querystring');
class ApiClient {
constructor(apiKey, secretKey) {
this.apiKey = apiKey;
this.secretKey = secretKey;
this.baseUrl = 'https://api.toobit.com';
}
buildQueryString(params) {
// Build query string (using the same order for signature and request submission)
const keys = Object.keys(params);
return keys
.map(key => `${key}=${params[key]}`)
.join('&');
}
generateSignature(params) {
// Use buildQueryString to ensure consistent order
const queryString = this.buildQueryString(params);
return crypto
.createHmac('sha256', this.secretKey)
.update(queryString)
.digest('hex');
}
async callSignedApi(endpoint, params, method = 'POST') {
// Add timestamp
params.timestamp = Date.now();
// Add recvWindow (if not present)
if (!params.recvWindow) {
params.recvWindow = 5000;
}
// Generate signature
const signature = this.generateSignature(params);
params.signature = signature;
// Build query string (using the same order as signature generation)
const queryString = this.buildQueryString(params);
// Build request
const url = new URL(this.baseUrl + endpoint);
const options = {
method: method,
headers: {
'X-BB-APIKEY': this.apiKey
}
};
if (method === 'GET') {
// GET request: Use the same query string as used for signature
url.search = queryString;
} else {
// POST request: Use the same query string as body as used for signature
options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
return new Promise((resolve, reject) => {
const req = https.request(url, options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (e) {
resolve(data);
}
});
});
req.on('error', (e) => {
reject(e);
});
if (method === 'POST') {
// POST request: Use the same query string as used for signature
req.write(queryString);
}
req.end();
});
}
}
// Usage example
async function main() {
const client = new ApiClient('your-api-key', 'your-secret-key');
// Example 1: Query account balance (GET request)
try {
const result1 = await client.callSignedApi(
'/api/v1/futures/balance',
{ recvWindow: 5000 },
'GET'
);
console.log(result1);
} catch (error) {
console.error('Error:', error);
}
// Example 2: Place order (POST request)
try {
const result2 = await client.callSignedApi(
'/api/v1/futures/order',
{
symbol: 'BTC-SWAP-USDT',
side: 'BUY_OPEN',
type: 'LIMIT',
quantity: '10',
price: '30000',
newClientOrderId: 'test_order_001',
recvWindow: 5000
},
'POST'
);
console.log(result2);
} catch (error) {
console.error('Error:', error);
}
}
main();Go Complete Example
go
package main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
)
type ApiClient struct {
apiKey string
secretKey string
baseUrl string
}
func NewApiClient(apiKey, secretKey string) *ApiClient {
return &ApiClient{
apiKey: apiKey,
secretKey: secretKey,
baseUrl: "https://api.toobit.com",
}
}
func (c *ApiClient) buildQueryString(params map[string]string, keys []string) string {
// Build query string (using the provided keys order to ensure signature and request consistency)
// Note: Go's map iteration order is random, so we must use the same keys slice to build query string
var parts []string
for _, k := range keys {
if v, exists := params[k]; exists {
parts = append(parts, fmt.Sprintf("%s=%s", k, v))
}
}
return strings.Join(parts, "&")
}
func (c *ApiClient) getParamKeys(params map[string]string) []string {
// Get parameter key list (fixed order, used to ensure signature and request consistency)
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
return keys
}
func (c *ApiClient) generateSignature(params map[string]string, keys []string) string {
// Build query string (using the provided keys order to ensure consistency with request submission)
queryString := c.buildQueryString(params, keys)
// Generate HMAC SHA256 signature
mac := hmac.New(sha256.New, []byte(c.secretKey))
mac.Write([]byte(queryString))
signature := hex.EncodeToString(mac.Sum(nil))
return signature
}
func (c *ApiClient) CallSignedApi(endpoint string, params map[string]string, method string) (map[string]interface{}, error) {
// Add timestamp
params["timestamp"] = strconv.FormatInt(time.Now().UnixMilli(), 10)
// Add recvWindow (if not present)
if _, exists := params["recvWindow"]; !exists {
params["recvWindow"] = "5000"
}
// Get parameter key list (used to ensure signature and request use the same order)
keys := c.getParamKeys(params)
// Generate signature (using keys order)
signature := c.generateSignature(params, keys)
params["signature"] = signature
// Re-get keys (because signature was added)
keys = c.getParamKeys(params)
// Build query string (using the same keys order as signature generation)
queryString := c.buildQueryString(params, keys)
var req *http.Request
var err error
url := c.baseUrl + endpoint
if method == "GET" {
// GET request: Use the same query string as used for signature
url += "?" + queryString
req, err = http.NewRequest(method, url, nil)
} else {
// POST request: Use the same query string as body as used for signature
req, err = http.NewRequest(method, url, strings.NewReader(queryString))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
if err != nil {
return nil, err
}
req.Header.Set("X-BB-APIKEY", c.apiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var result map[string]interface{}
err = json.Unmarshal(body, &result)
if err != nil {
return nil, err
}
return result, nil
}
// Usage example
func main() {
client := NewApiClient("your-api-key", "your-secret-key")
// Example 1: Query account balance (GET request)
params1 := map[string]string{
"recvWindow": "5000",
}
result1, err := client.CallSignedApi("/api/v1/futures/balance", params1, "GET")
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %+v\n", result1)
}
// Example 2: Place order (POST request)
params2 := map[string]string{
"symbol": "BTC-SWAP-USDT",
"side": "BUY_OPEN",
"type": "LIMIT",
"quantity": "10",
"price": "30000",
"newClientOrderId": "test_order_001",
"recvWindow": "5000",
}
result2, err := client.CallSignedApi("/api/v1/futures/order", params2, "POST")
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %+v\n", result2)
}
}cURL Example
bash
#!/bin/bash
API_KEY="your-api-key"
SECRET_KEY="your-secret-key"
BASE_URL="https://api.toobit.com"
# Signature generation function
generate_signature() {
local params="$1"
echo -n "$params" | openssl dgst -sha256 -hmac "$SECRET_KEY" | awk '{print $2}'
}
# Call API that requires signature
call_signed_api() {
local endpoint="$1"
local method="${2:-POST}"
local params="$3"
# Add timestamp
local timestamp=$(date +%s)000
params="${params}×tamp=${timestamp}"
# Add recvWindow (if not present)
if [[ ! "$params" =~ recvWindow ]]; then
params="${params}&recvWindow=5000"
fi
# Generate signature
local signature=$(generate_signature "$params")
params="${params}&signature=${signature}"
# Send request
if [ "$method" = "GET" ]; then
curl -X GET "${BASE_URL}${endpoint}?${params}" \
-H "X-BB-APIKEY: ${API_KEY}"
else
curl -X POST "${BASE_URL}${endpoint}" \
-H "X-BB-APIKEY: ${API_KEY}" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "${params}"
fi
}
# Usage examples
# Example 1: Query account balance (GET request)
call_signed_api "/api/v1/futures/balance" "GET" ""
# Example 2: Place order (POST request)
call_signed_api "/api/v1/futures/order" "POST" \
"symbol=BTC-SWAP-USDT&side=BUY_OPEN&type=LIMIT&quantity=10&price=30000&newClientOrderId=test_001"Notes
- Timestamp: Must use millisecond-level Unix timestamp
- Signature does not include signature field: When generating signature, the parameter dictionary should not include the
signaturefield - Mixed parameters: When using both query string and request body, the signature input should be query string first, then request body, with no
&separator in between
More Information
- For detailed signature instructions, please refer to Basic Information - SIGNED Endpoint Security
- For specific endpoint parameters and response formats, please refer to the corresponding endpoint documentation