Skip to content

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 signature

Java

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}&timestamp=${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

  1. Timestamp: Must use millisecond-level Unix timestamp
  2. Signature does not include signature field: When generating signature, the parameter dictionary should not include the signature field
  3. 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

Released under the MIT License.