代码示例
本文档提供不同编程语言的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"
"strings"
)
// GenerateSignature 生成HMAC SHA256签名
func GenerateSignature(secretKey string, params map[string]string) string {
// 拼接query string
var parts []string
for k, v := range params {
parts = append(parts, fmt.Sprintf("%s=%s", k, v))
}
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.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) {
// 构建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 HashMap<>();
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 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完整示例
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)
keys = c.getParamKeys(params)
// 构建query string(与签名时使用相同的keys顺序)
queryString := c.buildQueryString(params, keys)
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
params="${params}×tamp=${timestamp}"
# 添加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"注意事项
- 时间戳:必须使用毫秒级Unix时间戳
- 签名不包含signature字段:生成签名时,参数字典中不应包含
signature字段 - 混合参数:当同时使用query string和request body时,签名输入为query string在前,request body在后,中间没有
&分隔符
更多信息
- 详细的签名说明请参考基础信息 - 需要签名的接口
- 接口的具体参数和响应格式请参考对应的接口文档