代码示例
本文档提供不同编程语言的API接入示例,适用于所有需要签名的接口(现货和合约通用)。
签名生成方法
所有需要签名的接口(TRADE 和 USER_DATA)都需要使用 HMAC SHA256 算法生成签名。
Python
python
import hmac
import hashlib
import time
def generate_signature(secret_key, params):
"""
生成HMAC SHA256签名
:param secret_key: API Secret Key
:param params: 参数字典(不包含signature)
:return: 签名字符串
"""
# 参数拼接成query string格式
query_string = "&".join([f"{k}={v}" for k, v in params.items()])
# 生成HMAC SHA256签名
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 {
// 生成HMAC SHA256签名
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');
/**
* 生成HMAC SHA256签名
* @param {string} secretKey - API Secret Key
* @param {Object} params - 参数字典(不包含signature)
* @returns {string} 签名字符串
*/
function generateSignature(secretKey, params) {
// 参数拼接成query string格式
const queryString = Object.keys(params)
.map(key => `${key}=${params[key]}`)
.join('&');
// 生成HMAC SHA256签名
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 生成HMAC SHA256签名
// 注意:Go的map遍历顺序随机,需按参数名排序以保证签名确定性
func GenerateSignature(secretKey string, params map[string]string) string {
// 按参数名排序后拼接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, "&")
// 生成HMAC SHA256签名
mac := hmac.New(sha256.New, []byte(secretKey))
mac.Write([]byte(queryString))
signature := hex.EncodeToString(mac.Sum(nil))
return signature
}完整请求示例
以下示例展示如何调用一个需要签名的接口(适用于所有 TRADE 和 USER_DATA 接口)。
Python完整示例
python
import requests
import hmac
import hashlib
import time
def call_signed_api(endpoint, params, api_key, secret_key, method='POST'):
"""
调用需要签名的API接口
:param endpoint: API端点路径,如 '/api/v1/futures/order' 或 '/api/v1/spot/order'
:param params: 参数字典
:param api_key: API Key
:param secret_key: Secret Key
:param method: HTTP方法,'GET' 或 'POST'
:return: 响应结果
"""
# 添加时间戳(必需)
params['timestamp'] = int(time.time() * 1000)
# 可选:添加recvWindow
if 'recvWindow' not in params:
params['recvWindow'] = 5000
# 生成签名(使用相同的query_string格式)
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
# 构建完整的query string(包含signature,与签名时使用相同的顺序)
query_string = "&".join([f"{k}={v}" for k, v in params.items()])
# 发送请求(使用相同的query_string格式,确保顺序一致)
url = f"https://api.toobit.com{endpoint}"
headers = {"X-BB-APIKEY": api_key}
if method == 'GET':
# GET请求:手动构建query string,确保与签名时顺序一致
url_with_params = f"{url}?{query_string}"
response = requests.get(url_with_params, headers=headers)
else:
# POST请求:使用手动构建的query string作为body,确保顺序一致
headers["Content-Type"] = "application/x-www-form-urlencoded"
response = requests.post(url, headers=headers, data=query_string)
return response.json()
# 使用示例
if __name__ == "__main__":
api_key = "your-api-key"
secret_key = "your-secret-key"
# 示例1:查询账户余额(GET请求)
params = {
"recvWindow": 5000
}
result = call_signed_api("/api/v1/futures/balance", params, api_key, secret_key, method='GET')
print(result)
# 示例2:下单(POST请求)
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完整示例
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) {
// 构建query string(与签名和请求提交时使用相同的顺序)
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 {
// 添加时间戳
params.put("timestamp", String.valueOf(System.currentTimeMillis()));
// 添加recvWindow(如果不存在)
if (!params.containsKey("recvWindow")) {
params.put("recvWindow", "5000");
}
// 生成签名(使用buildQueryString确保顺序一致)
String queryStringForSign = buildQueryString(params);
String signature = SignatureUtil.generateSignature(secretKey, queryStringForSign);
params.put("signature", signature);
// 构建query string(与签名时使用相同的顺序)
String queryString = buildQueryString(params);
// 构建URL
String urlString = baseUrl + endpoint;
if ("GET".equals(method)) {
urlString += "?" + queryString;
}
// 发送请求
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请求:使用与签名时相同的query string作为body
try (OutputStream os = conn.getOutputStream()) {
os.write(queryString.getBytes(StandardCharsets.UTF_8));
}
}
// 读取响应
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();
}
// 使用示例
public static void main(String[] args) throws Exception {
ApiClient client = new ApiClient("your-api-key", "your-secret-key");
// 示例1:查询账户余额(GET请求)
Map<String, String> params1 = new LinkedHashMap<>();
params1.put("recvWindow", "5000");
String result1 = client.callSignedApi("/api/v1/futures/balance", params1, "GET");
System.out.println(result1);
// 示例2:下单(POST请求)
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完整示例
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) {
// 构建query string(与签名和请求提交时使用相同的顺序)
const keys = Object.keys(params);
return keys
.map(key => `${key}=${params[key]}`)
.join('&');
}
generateSignature(params) {
// 使用buildQueryString确保顺序一致
const queryString = this.buildQueryString(params);
return crypto
.createHmac('sha256', this.secretKey)
.update(queryString)
.digest('hex');
}
async callSignedApi(endpoint, params, method = 'POST') {
// 添加时间戳
params.timestamp = Date.now();
// 添加recvWindow(如果不存在)
if (!params.recvWindow) {
params.recvWindow = 5000;
}
// 生成签名
const signature = this.generateSignature(params);
params.signature = signature;
// 构建query string(与签名时使用相同的顺序)
const queryString = this.buildQueryString(params);
// 构建请求
const url = new URL(this.baseUrl + endpoint);
const options = {
method: method,
headers: {
'X-BB-APIKEY': this.apiKey
}
};
if (method === 'GET') {
// GET请求:使用与签名时相同的query string
url.search = queryString;
} else {
// POST请求:使用与签名时相同的query string作为body
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请求:使用与签名时相同的query string
req.write(queryString);
}
req.end();
});
}
}
// 使用示例
async function main() {
const client = new ApiClient('your-api-key', 'your-secret-key');
// 示例1:查询账户余额(GET请求)
try {
const result1 = await client.callSignedApi(
'/api/v1/futures/balance',
{ recvWindow: 5000 },
'GET'
);
console.log(result1);
} catch (error) {
console.error('Error:', error);
}
// 示例2:下单(POST请求)
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完整示例
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 {
// 构建query string(使用传入的keys顺序,确保签名和请求一致)
// 注意:Go的map遍历顺序是随机的,所以必须使用相同的keys slice来构建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 {
// 获取参数的键列表(顺序固定,用于确保签名和请求一致)
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 {
// 构建query string(使用传入的keys顺序,确保与请求提交时一致)
queryString := c.buildQueryString(params, keys)
// 生成HMAC SHA256签名
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) {
// 添加时间戳
params["timestamp"] = strconv.FormatInt(time.Now().UnixMilli(), 10)
// 添加recvWindow(如果不存在)
if _, exists := params["recvWindow"]; !exists {
params["recvWindow"] = "5000"
}
// 获取参数的键列表(用于确保签名和请求使用相同的顺序)
keys := c.getParamKeys(params)
// 生成签名(使用keys顺序)
signature := c.generateSignature(params, keys)
params["signature"] = signature
// 在原有keys后追加signature,保持顺序一致(Go的map遍历顺序随机,不能重新getParamKeys)
keysForRequest := append(keys, "signature")
// 构建query string(与签名时使用相同的keys顺序)
queryString := c.buildQueryString(params, keysForRequest)
var req *http.Request
var err error
url := c.baseUrl + endpoint
if method == "GET" {
// GET请求:使用与签名时相同的query string
url += "?" + queryString
req, err = http.NewRequest(method, url, nil)
} else {
// POST请求:使用与签名时相同的query string作为body
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
}
// 使用示例
func main() {
client := NewApiClient("your-api-key", "your-secret-key")
// 示例1:查询账户余额(GET请求)
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)
}
// 示例2:下单(POST请求)
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示例
bash
#!/bin/bash
API_KEY="your-api-key"
SECRET_KEY="your-secret-key"
BASE_URL="https://api.toobit.com"
# 生成签名函数
generate_signature() {
local params="$1"
echo -n "$params" | openssl dgst -sha256 -hmac "$SECRET_KEY" | awk '{print $2}'
}
# 调用需要签名的API
call_signed_api() {
local endpoint="$1"
local method="${2:-POST}"
local params="$3"
# 添加时间戳
local timestamp=$(date +%s)000
if [ -z "$params" ]; then
params="timestamp=${timestamp}"
else
params="${params}×tamp=${timestamp}"
fi
# 添加recvWindow(如果不存在)
if [[ ! "$params" =~ recvWindow ]]; then
params="${params}&recvWindow=5000"
fi
# 生成签名
local signature=$(generate_signature "$params")
params="${params}&signature=${signature}"
# 发送请求
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
}
# 使用示例
# 示例1:查询账户余额(GET请求)
call_signed_api "/api/v1/futures/balance" "GET" ""
# 示例2:下单(POST请求)
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 请求体验签示例(API v2)
/api/v2/ 下 Content-Type: application/json 的接口(如下单、批量下单)采用以下验签方式:
签名字符串拼接规则:
待签名字符串 = query string(不含 signature)+ 原始 JSON 请求体两部分之间无任何分隔符直接拼接。
请求结构:
| 位置 | 参数 |
|---|---|
| URL 查询参数 | timestamp、recvWindow(可选)、signature 及其他公共参数(如 category) |
| 请求头 | X-BB-APIKEY、Content-Type: application/json |
| 请求体 | 原始 JSON 字符串(与参与签名的内容字符完全一致,必须为紧凑格式,不含换行;服务端按行读取 body 时会丢弃换行符,格式化 JSON 会导致验签失败) |
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 签名请求。
签名字符串 = query string(不含 signature)+ 原始 JSON body(无分隔符)
timestamp / recvWindow / signature 放 URL 查询参数,业务参数放 JSON body。
"""
timestamp = int(time.time() * 1000)
query = {"timestamp": timestamp, "recvWindow": 5000}
if extra_query:
query.update(extra_query)
# 序列化 JSON body,必须与实际发送的请求体完全一致
json_body = json.dumps(body, separators=(',', ':'))
# 签名字符串 = query string + JSON body(无分隔符)
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()
# 示例 1:下普通单(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))
# 示例 2:批量下单(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 签名请求。
* 签名字符串 = query string(不含 signature)+ 原始 JSON body(无分隔符)。
*
* @param endpoint 接口路径,如 /api/v2/futures/order
* @param jsonBody 原始 JSON 字符串(必须与实际发送的请求体完全一致)
* @param extraQuery 额外查询参数,如 "category=USDT",不需要时传 null
*/
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 : "");
// 签名字符串 = query string + JSON body(无分隔符)
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 {
// 示例 1:下普通单
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));
// 示例 2:批量下单(body 为 JSON 数组)
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 签名请求。
* 签名字符串 = query string(不含 signature)+ 原始 JSON body(无分隔符)
*/
function v2JsonRequest(endpoint, body, extraQuery = {}) {
return new Promise((resolve, reject) => {
const timestamp = Date.now();
const query = { timestamp, recvWindow: 5000, ...extraQuery };
// 序列化 JSON body,必须与实际发送的请求体完全一致
const jsonBody = JSON.stringify(body);
const queryStr = Object.entries(query).map(([k, v]) => `${k}=${v}`).join('&');
const signStr = queryStr + jsonBody;
const signature = crypto.createHmac('sha256', SECRET).update(signStr).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();
});
}
// 示例 1:下普通单
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);
// 示例 2:批量下单(body 为 JSON 数组)
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 发起 V2 JSON body 签名请求。
// 签名字符串 = query string(不含 signature)+ 原始 JSON body(无分隔符)。
// body 可为 map(单笔)或 []map(批量),extraQuery 为额外查询参数如 "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
}
// 签名字符串 = query string + JSON body(无分隔符)
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() {
// 示例 1:下普通单
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)
}
// 示例 2:批量下单(body 为 JSON 数组)
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 签名:签名字符串 = query string(不含 signature)+ 原始 JSON body(无分隔符)
sign_v2_json() {
local query_str="$1" # 如 "timestamp=xxx&recvWindow=5000"
local json_body="$2" # 原始 JSON 字符串
echo -n "${query_str}${json_body}" | openssl dgst -sha256 -hmac "$SECRET_KEY" | awk '{print $2}'
}
TIMESTAMP=$(date +%s)000
# 示例 1:下普通单
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 ""
# 示例 2:批量下单(body 为 JSON 数组)
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 ""注意事项
- 时间戳:必须使用毫秒级Unix时间戳
- 签名格式:签名必须为小写十六进制字符串,区分大小写,验签时请勿使用大写
- 签名不包含signature字段:生成签名时,参数字典中不应包含
signature字段 - 混合参数:当同时使用query string和request body时,签名输入为query string在前,request body在后,中间没有
&分隔符 - API v2 + JSON 请求体:对
/api/v2/下Content-Type: application/json的POST(如POST /api/v2/futures/batch-orders),签名字符串为 query string(含timestamp等)与原始 JSON 请求体直接拼接,中间无&;JSON body 必须使用紧凑格式(不含换行符),服务端按行读取 body 会丢弃换行,发送格式化 JSON 将导致验签失败;参与签名的 JSON 字符串必须与实际发送的请求体字符完全一致。详见基础信息 中 JSON 请求体验签(API v2) 小节
更多信息
- 详细的签名说明请参考基础信息 - 需要签名的接口
- 接口的具体参数和响应格式请参考对应的接口文档