认证与安全
📋 目录
🔐 请求鉴权要求
创建 API
在使用 API 前,您需前往 Bittap 官网-个人中心-API 管理 或者 BittapApp-个人中心-API 管理 进行创建 创建完成后,请妥善保管API key 和 API secret 为了安全起见,请在创建 API 时同时设置白名单 请勿将 API key / Secret key 泄露给第三方;若不慎泄露,请立即生成新的 API 进行使用
权限设置
新创建的 API 默认没有任何权限,需要您手动开启现货 / 合约的 API 权限
必需请求头
所有需要认证的接口都必须包含以下请求头:
| 请求头 | 类型 | 必需 | 说明 |
|---|---|---|---|
| X-BT-APIKEY | String | 是 | 您的 API Key |
| X-BT-SIGN | String | 是 | 请求签名 |
| X-BT-TS | Long | 是 | 请求时间戳(毫秒) |
| X-BT-NONCE | String | 是 | 随机字符串 |
签名参数
签名计算需要以下参数:
- timestamp: 当前时间戳(毫秒)
- nonce: 随机字符串,建议使用 UUID
- appSecret: 您的 API Secret
🔑 签名规则
1. 参数排序规则
所有参数按照以下规则进行排序:
- 按 key 的字典序升序排列
- 相同 key 的参数按 value 的字典序升序排列
- 数组参数按索引顺序排列
2. 参数拼接规则
URL参数
- 包含问号(?)后的所有参数。
- 将key进行字典排序然后进行拼接。
- 过滤空数组、null、空字符串。
- 示例:param1=value1¶m2=value2。
Body参数
- JSON对象需扁平化为查询字符串格式。
- 数组必须转换为key[index]=value格式。
- 嵌套对象使用点号(.)表示层级。
- 将key进行字典排序然后进行拼接。
- 过滤空数组、null、空字符串。
注意事项
- GET请求只取urlQuery参数,POST请求取body参数,忽略url参数。
- 必须包含timestamp和nonce参数。
- 参数之间用&连接。
- 对于没有参数的GET请求,签名的原始字符串以&开头。
×tamp=1752647583398&nonce=e4c5e38c57a741f6a4658713- 有参数的请求,签名的原始字符串以参数开头
name=andy×tamp=1752647583398&nonce=e4c5e38c57a741f6a46587133. 特殊处理规则
- 嵌套对象: 使用点号分隔,如
user.name=张三 - 数组: 使用方括号索引,如
hobbies[0]=阅读 - 空值: 跳过 null 和空字符串
- 布尔值: 转换为字符串
4. 签名计算
使用 HMAC-SHA256 算法计算签名:
signature = HMAC-SHA256(data, appSecret)其中 data 是排序拼接后的参数字符串。
📝 签名示例
示例 1: 简单参数
请求参数:
{
"a": 2,
"b": 1,
"c": 3
}排序拼接:
a=2&b=1&c=3最终签名串:
a=2&b=1&c=3×tamp=1752647583398&nonce=e4c5e38c57a741f6a4658713示例 2: 嵌套对象
请求参数:
{
"a": [
{"b": 4, "c": 3},
{"x": 8, "y": 9}
],
"b": {
"data": {
"aa": [3, 2, 1]
},
"a": 2,
"z": 1
}
}排序拼接后:
a[0].b=4&a[0].c=3&a[3].x=8&a[3].y=9&b.a=2&b.data.aa[0]=3&b.data.aa[1]=2&b.data.aa[2]=1&b.z=1×tamp=1752647583398&nonce=e4c5e38c57a741f6a4658713示例 3: GET 请求
URL 参数:
categories=homeConfig,appConfig&a=2&a=1&c=1&d=123排序后:
a[0]=1&a[1]=2&c=1&categories=homeConfig,appConfig&d=123示例 4: 数组请求体
请求体:
[{"key1":"xxx","key2":"xx"}]转换后:
[0].key1=xxx&[0].key2=xx⚙️ 签名计算步骤
步骤 1: 准备参数
const timestamp = Date.now();
const nonce = generateNonce();
const params = {
symbol: "BTC-USDT",
quantity: 0.001,
price: 50000
};步骤 2: 扁平化对象
function flattenObject(obj, parentKey = '', result = {}) {
for (const [key, value] of Object.entries(obj)) {
const currentKey = parentKey ? `${parentKey}.${key}` : key;
if (value == null) continue;
if (Array.isArray(value)) {
value.forEach((item, index) => {
if (item != null) {
const arrayKey = `${currentKey}[${index}]`;
if (typeof item === 'object') {
flattenObject(item, arrayKey, result);
} else {
result[arrayKey] = item.toString();
}
}
});
} else if (typeof value === 'object') {
flattenObject(value, currentKey, result);
} else {
result[currentKey] = value.toString();
}
}
return result;
}步骤 3: 排序参数
const flattened = flattenObject(params);
const sortedParams = Object.entries(flattened)
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([key, value]) => `${key}=${value}`)
.join('&');步骤 4: 添加必需参数
const signData = `${sortedParams}×tamp=${timestamp}&nonce=${nonce}`;步骤 5: 计算签名
const crypto = require('crypto');
function generateSignature(data, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(data);
return hmac.digest('hex');
}
const signature = generateSignature(signData, appSecret);步骤 6: 发送请求
const response = await fetch('/api/endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-BT-APIKEY': apiKey,
'X-BT-SIGN': signature,
'X-BT-TS': timestamp,
'X-BT-NONCE': nonce
},
body: JSON.stringify(params)
});⚠️ 注意事项
- 时间戳: 服务器时间与客户端时间差异不能超过 5 分钟
- 随机字符串: 每次请求必须使用不同的 nonce
- 参数编码: 所有参数值需要进行 URL 编码
- 签名验证: 服务器会验证签名的正确性和时效性
- 安全存储: 请妥善保管您的 API Secret,不要泄露给他人
🔍 常见问题
Q: 为什么签名验证失败?
A: 请检查以下几点:
- 参数排序是否正确
- 时间戳是否在有效期内
- nonce 是否重复使用
- API Secret 是否正确
Q: 如何处理数组参数?
A: 数组参数需要按照索引顺序展开,如 items[0].price=100&items[1].price=200
Q: 嵌套对象如何表示?
A: 使用点号分隔,如 user.profile.name=张三
最后更新于: