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"
    "sort"
    "strings"
)

// GenerateSignature generates HMAC SHA256 signature
// Note: Go map iteration order is random, sort by parameter name for deterministic signature
func GenerateSignature(secretKey string, params map[string]string) string {
    // Sort by parameter name and concatenate query string
    keys := make([]string, 0, len(params))
    for k := range params {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    var parts []string
    for _, k := range keys {
        parts = append(parts, fmt.Sprintf("%s=%s", k, params[k]))
    }
    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.LinkedHashMap;
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 LinkedHashMap<>();
        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 LinkedHashMap<>();
        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
    
    // Append "signature" to existing keys to preserve order (Go map iteration is random, do not call getParamKeys again)
    keysForRequest := append(keys, "signature")
    
    // Build query string (using the same keys order as signature generation)
    queryString := c.buildQueryString(params, keysForRequest)
    
    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
    if [ -z "$params" ]; then
        params="timestamp=${timestamp}"
    else
        params="${params}&timestamp=${timestamp}"
    fi
    
    # 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"

JSON Request Body Signing Examples (API v2)

Endpoints under /api/v2/ with Content-Type: application/json (e.g. new order, batch orders) use the following signing scheme:

String-to-sign construction rule:

string_to_sign = query string (excluding signature) + raw JSON body

The two parts are concatenated directly with no separator.

Request structure:

LocationParameters
URL query paramstimestamp, recvWindow (optional), signature, and other shared params (e.g. category)
Request headersX-BB-APIKEY, Content-Type: application/json
Request bodyRaw JSON string — must be byte-for-byte identical to what was signed; must use compact format (no newlines). The server reads the body line-by-line and strips line terminators, so pretty-printed JSON will cause a signature mismatch.

Python

python
import hmac
import hashlib
import json
import time
import requests

API_KEY  = "your-api-key"
SECRET   = "your-secret-key"
BASE_URL = "https://api.toobit.com"

def v2_json_request(endpoint, body, extra_query=None):
    """
    V2 JSON body signed request.
    string_to_sign = query string (no signature) + raw JSON body (no separator).
    timestamp / recvWindow / signature go in the URL; business params go in the JSON body.
    """
    timestamp = int(time.time() * 1000)
    query = {"timestamp": timestamp, "recvWindow": 5000}
    if extra_query:
        query.update(extra_query)

    # Serialize JSON body — must be identical to what is actually sent
    json_body = json.dumps(body, separators=(',', ':'))

    # string_to_sign = query string + JSON body (no separator)
    query_str = "&".join(f"{k}={v}" for k, v in query.items())
    sign_str  = query_str + json_body

    sig = hmac.new(SECRET.encode(), sign_str.encode(), hashlib.sha256).hexdigest()

    url     = f"{BASE_URL}{endpoint}?{query_str}&signature={sig}"
    headers = {"X-BB-APIKEY": API_KEY, "Content-Type": "application/json"}
    return requests.post(url, headers=headers, data=json_body).json()


# Example 1: New regular order (JSON object body)
order = {
    "symbol": "BTC-SWAP-USDT",
    "side": "BUY",
    "positionSide": "LONG",
    "type": "LIMIT",
    "newClientOrderId": "v2_order_001",
    "quantity": "1",
    "price": "30000",
}
print(v2_json_request("/api/v2/futures/order", order))

# Example 2: Batch new orders (JSON array body)
batch = [
    {"symbol": "BTC-SWAP-USDT", "side": "BUY", "positionSide": "LONG",
     "type": "LIMIT", "newClientOrderId": "batch_001", "quantity": "1", "price": "30000"},
    {"symbol": "ETH-SWAP-USDT", "side": "SELL", "positionSide": "SHORT",
     "type": "LIMIT", "newClientOrderId": "batch_002", "quantity": "2", "price": "2000"},
]
print(v2_json_request("/api/v2/futures/batch-orders", batch, extra_query={"category": "USDT"}))

Java

java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Instant;

public class V2JsonApiClient {

    private static final String API_KEY  = "your-api-key";
    private static final String SECRET   = "your-secret-key";
    private static final String BASE_URL = "https://api.toobit.com";

    static String hmacSha256(String key, String data) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder();
        for (byte b : hash) sb.append(String.format("%02x", b));
        return sb.toString();
    }

    /**
     * V2 JSON body signed request.
     * string_to_sign = query string (no signature) + raw JSON body (no separator).
     *
     * @param endpoint   e.g. /api/v2/futures/order
     * @param jsonBody   raw JSON string — must match the actual request body byte-for-byte
     * @param extraQuery additional query params, e.g. "category=USDT"; pass null if none
     */
    static String v2JsonRequest(String endpoint, String jsonBody, String extraQuery) throws Exception {
        long timestamp = Instant.now().toEpochMilli();
        String queryStr = "timestamp=" + timestamp + "&recvWindow=5000"
                + (extraQuery != null && !extraQuery.isEmpty() ? "&" + extraQuery : "");

        // string_to_sign = query string + JSON body (no separator)
        String signature = hmacSha256(SECRET, queryStr + jsonBody);

        String url = BASE_URL + endpoint + "?" + queryStr + "&signature=" + signature;
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .header("X-BB-APIKEY", API_KEY)
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(jsonBody, StandardCharsets.UTF_8))
                .build();

        return HttpClient.newHttpClient()
                .send(request, HttpResponse.BodyHandlers.ofString())
                .body();
    }

    public static void main(String[] args) throws Exception {
        // Example 1: New regular order
        String orderBody = "{\"symbol\":\"BTC-SWAP-USDT\",\"side\":\"BUY\",\"positionSide\":\"LONG\","
                + "\"type\":\"LIMIT\",\"newClientOrderId\":\"v2_order_001\","
                + "\"quantity\":\"1\",\"price\":\"30000\"}";
        System.out.println(v2JsonRequest("/api/v2/futures/order", orderBody, null));

        // Example 2: Batch new orders (JSON array body)
        String batchBody = "[{\"symbol\":\"BTC-SWAP-USDT\",\"side\":\"BUY\",\"positionSide\":\"LONG\","
                + "\"type\":\"LIMIT\",\"newClientOrderId\":\"batch_001\",\"quantity\":\"1\",\"price\":\"30000\"},"
                + "{\"symbol\":\"ETH-SWAP-USDT\",\"side\":\"SELL\",\"positionSide\":\"SHORT\","
                + "\"type\":\"LIMIT\",\"newClientOrderId\":\"batch_002\",\"quantity\":\"2\",\"price\":\"2000\"}]";
        System.out.println(v2JsonRequest("/api/v2/futures/batch-orders", batchBody, "category=USDT"));
    }
}

Node.js

javascript
const crypto = require('crypto');
const https  = require('https');

const API_KEY  = 'your-api-key';
const SECRET   = 'your-secret-key';
const BASE_URL = 'api.toobit.com';

/**
 * V2 JSON body signed request.
 * string_to_sign = query string (no signature) + raw JSON body (no separator)
 */
function v2JsonRequest(endpoint, body, extraQuery = {}) {
    return new Promise((resolve, reject) => {
        const timestamp = Date.now();
        const query = { timestamp, recvWindow: 5000, ...extraQuery };

        // Serialize JSON body — must be identical to what is actually sent
        const jsonBody  = JSON.stringify(body);
        const queryStr  = Object.entries(query).map(([k, v]) => `${k}=${v}`).join('&');
        const signature = crypto.createHmac('sha256', SECRET)
            .update(queryStr + jsonBody)
            .digest('hex');

        const path = `${endpoint}?${queryStr}&signature=${signature}`;
        const options = {
            hostname: BASE_URL,
            path,
            method: 'POST',
            headers: {
                'X-BB-APIKEY': API_KEY,
                'Content-Type': 'application/json',
                'Content-Length': Buffer.byteLength(jsonBody),
            },
        };

        const req = https.request(options, res => {
            let data = '';
            res.on('data', chunk => data += chunk);
            res.on('end', () => resolve(JSON.parse(data)));
        });
        req.on('error', reject);
        req.write(jsonBody);
        req.end();
    });
}

// Example 1: New regular order
v2JsonRequest('/api/v2/futures/order', {
    symbol: 'BTC-SWAP-USDT', side: 'BUY', positionSide: 'LONG',
    type: 'LIMIT', newClientOrderId: 'v2_order_001', quantity: '1', price: '30000',
}).then(console.log).catch(console.error);

// Example 2: Batch new orders (JSON array body)
v2JsonRequest('/api/v2/futures/batch-orders', [
    { symbol: 'BTC-SWAP-USDT', side: 'BUY', positionSide: 'LONG',
      type: 'LIMIT', newClientOrderId: 'batch_001', quantity: '1', price: '30000' },
    { symbol: 'ETH-SWAP-USDT', side: 'SELL', positionSide: 'SHORT',
      type: 'LIMIT', newClientOrderId: 'batch_002', quantity: '2', price: '2000' },
], { category: 'USDT' }).then(console.log).catch(console.error);

Go

go
package main

import (
    "bytes"
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "strconv"
    "time"
)

const (
    apiKey  = "your-api-key"
    secret  = "your-secret-key"
    baseURL = "https://api.toobit.com"
)

func hmacSha256(key, data string) string {
    mac := hmac.New(sha256.New, []byte(key))
    mac.Write([]byte(data))
    return hex.EncodeToString(mac.Sum(nil))
}

// v2JsonRequest sends a V2 JSON body signed request.
// string_to_sign = query string (no signature) + raw JSON body (no separator).
// body may be a map (single order) or []map (batch); extraQuery e.g. "category=USDT".
func v2JsonRequest(endpoint string, body any, extraQuery string) (string, error) {
    jsonBytes, err := json.Marshal(body)
    if err != nil {
        return "", err
    }
    jsonBody := string(jsonBytes)

    timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
    queryStr  := "timestamp=" + timestamp + "&recvWindow=5000"
    if extraQuery != "" {
        queryStr += "&" + extraQuery
    }

    // string_to_sign = query string + JSON body (no separator)
    signature := hmacSha256(secret, queryStr+jsonBody)

    url := baseURL + endpoint + "?" + queryStr + "&signature=" + signature
    req, err := http.NewRequest("POST", url, bytes.NewBufferString(jsonBody))
    if err != nil {
        return "", err
    }
    req.Header.Set("X-BB-APIKEY", apiKey)
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    result, _ := io.ReadAll(resp.Body)
    return string(result), nil
}

func main() {
    // Example 1: New regular order
    order := map[string]string{
        "symbol": "BTC-SWAP-USDT", "side": "BUY", "positionSide": "LONG",
        "type": "LIMIT", "newClientOrderId": "v2_order_001",
        "quantity": "1", "price": "30000",
    }
    result, err := v2JsonRequest("/api/v2/futures/order", order, "")
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println(result)
    }

    // Example 2: Batch new orders (JSON array body)
    batch := []map[string]string{
        {"symbol": "BTC-SWAP-USDT", "side": "BUY", "positionSide": "LONG",
            "type": "LIMIT", "newClientOrderId": "batch_001", "quantity": "1", "price": "30000"},
        {"symbol": "ETH-SWAP-USDT", "side": "SELL", "positionSide": "SHORT",
            "type": "LIMIT", "newClientOrderId": "batch_002", "quantity": "2", "price": "2000"},
    }
    result, err = v2JsonRequest("/api/v2/futures/batch-orders", batch, "category=USDT")
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println(result)
    }
}

cURL

bash
#!/bin/bash

API_KEY="your-api-key"
SECRET_KEY="your-secret-key"
BASE_URL="https://api.toobit.com"

# V2 JSON body signing: string_to_sign = query string (no signature) + raw JSON body (no separator)
sign_v2_json() {
    local query_str="$1"  # e.g. "timestamp=xxx&recvWindow=5000"
    local json_body="$2"  # raw JSON string
    echo -n "${query_str}${json_body}" | openssl dgst -sha256 -hmac "$SECRET_KEY" | awk '{print $2}'
}

TIMESTAMP=$(date +%s)000

# Example 1: New regular order
JSON_BODY='{"symbol":"BTC-SWAP-USDT","side":"BUY","positionSide":"LONG","type":"LIMIT","newClientOrderId":"v2_001","quantity":"1","price":"30000"}'
QUERY_STR="timestamp=${TIMESTAMP}&recvWindow=5000"
SIG=$(sign_v2_json "$QUERY_STR" "$JSON_BODY")

curl -s -X POST "${BASE_URL}/api/v2/futures/order?${QUERY_STR}&signature=${SIG}" \
    -H "X-BB-APIKEY: ${API_KEY}" \
    -H "Content-Type: application/json" \
    -d "${JSON_BODY}"
echo ""

# Example 2: Batch new orders (JSON array body)
BATCH_BODY='[{"symbol":"BTC-SWAP-USDT","side":"BUY","positionSide":"LONG","type":"LIMIT","newClientOrderId":"batch_001","quantity":"1","price":"30000"},{"symbol":"ETH-SWAP-USDT","side":"SELL","positionSide":"SHORT","type":"LIMIT","newClientOrderId":"batch_002","quantity":"2","price":"2000"}]'
QUERY_STR2="timestamp=${TIMESTAMP}&recvWindow=5000&category=USDT"
SIG2=$(sign_v2_json "$QUERY_STR2" "$BATCH_BODY")

curl -s -X POST "${BASE_URL}/api/v2/futures/batch-orders?${QUERY_STR2}&signature=${SIG2}" \
    -H "X-BB-APIKEY: ${API_KEY}" \
    -H "Content-Type: application/json" \
    -d "${BATCH_BODY}"
echo ""

Notes

  1. Timestamp: Must use millisecond-level Unix timestamp
  2. Signature format: The signature must be a lowercase hexadecimal string and is case sensitive; do not use uppercase
  3. Signature does not include signature field: When generating signature, the parameter dictionary should not include the signature field
  4. 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
  5. API v2 + JSON body: For POST under /api/v2/ with Content-Type: application/json (e.g. POST /api/v2/futures/batch-orders), the string to sign is the query string (including timestamp, etc.) concatenated with the raw JSON body—no & between them. The JSON body must use compact format (no newlines); the server reads the body line-by-line and strips line terminators, so sending pretty-printed JSON will cause a signature mismatch. The signed JSON string must match the actual request body byte-for-byte. See Basic information — JSON request body signing (API v2).

More Information

Released under the MIT License.