Skip to content

HMAC 认证

本页详细介绍用于 SlaunchX API 请求认证的完整 HMAC-SHA256 签名计算流程。所有对 /api/** 端点的请求都必须使用此流程进行签名。

必需的请求头

每个 API 请求必须包含以下请求头:

请求头必需说明
X-Api-Key您的 API 密钥标识符(例如 sk_live_abc123
X-Timestamp当前 Unix 时间戳(秒,UTC)。必须与服务器时间相差不超过 60 秒
X-Nonce唯一请求标识符。建议使用 UUID v4 以确保唯一性。每个 nonce 在时间戳窗口内只能使用一次
X-Signature规范消息的 HMAC-SHA256 签名,Base64 编码。详见下方 签名计算
X-LOCALE首选响应语言(例如 en-USzh-CN)。影响本地化错误消息
Content-Type按需application/json — 对于包含请求体的请求(POSTPUTPATCH)必需

签名计算

签名分三步计算:对请求体进行哈希、构建规范消息字符串、计算 HMAC。

第一步:计算请求体哈希

计算原始请求体的 SHA-256 哈希值,并将其编码为小写十六进制字符串。

bodyHash = lowercase_hex(SHA-256(requestBody))

对于包含请求体的请求(POSTPUTPATCH),对您将发送的完整 JSON 字符串进行哈希:

bash
echo -n '{"sourceWalletId":"w_123","amount":"100.00"}' | openssl dgst -sha256 -hex
# 输出: a1fce4363854ff888cff4b8e7875d600c...

对于无请求体的请求(GETDELETE),使用空字符串的 SHA-256 哈希值:

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

:::caution 请求体编码至关重要 请求体哈希是基于 HTTP 请求体中发送的 精确字节序列 计算的。如果您的 JSON 序列化器生成 {"a": 1}(冒号后有空格),但您哈希的是 {"a":1}(没有空格),签名将不匹配。请务必对传递给 HTTP 客户端的相同字符串进行哈希。 :::

第二步:构建规范消息

将五个组件用换行符(\n)连接:

canonicalMessage = method + "\n" + path + "\n" + timestamp + "\n" + nonce + "\n" + bodyHash
组件说明示例
methodHTTP 方法,大写POST
path仅请求路径——不包含协议、主机或查询字符串/api/v1/transfers
timestampUnix 时间戳(秒,UTC),与 X-Timestamp 请求头的值相同1709337600
nonce唯一标识符,与 X-Nonce 请求头的值相同(建议使用 UUID v4)550e8400-e29b-41d4-a716-446655440000
bodyHash第一步中的 SHA-256 十六进制摘要a1fce436...

对于 POST /api/v1/transfers 请求,规范消息如下所示:

POST
/api/v1/transfers
1709337600
550e8400-e29b-41d4-a716-446655440000
a1fce4363854ff888cff4b8e7875d600c...

:::caution 路径不能包含查询字符串 对于带有查询参数的 GET 请求(例如 /api/v1/wallets?page=0&size=20),在规范消息中只包含 路径 部分:/api/v1/wallets。查询参数不参与签名计算。 :::

第三步:计算 HMAC 签名

使用 HMAC-SHA256 和您的 API 密钥对规范消息进行签名,然后对结果进行 Base64 编码:

signature = Base64(HMAC-SHA256(canonicalMessage, apiSecret))

生成的 Base64 字符串放入 X-Signature 请求头。


完整示例

Bash + curl

以下脚本演示了一个完整的签名 API 请求:

bash
#!/bin/bash
# SlaunchX API 集成 -- HMAC-SHA256 认证示例

# ─── 配置 ────────────────────────────────────────────
API_KEY="sk_live_abc123def456"
API_SECRET="your-api-secret-here"
BASE_URL="https://api.slaunchx.cc"  # 品牌专属:替换为您的 API 主机地址

# ─── 请求参数 ──────────────────────────────────────
METHOD="POST"
PATH_URI="/api/v1/transfer/command/create"
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')

# 请求体(必须是通过网络发送的精确字符串)
BODY='{"sourceWalletId":"w_123","targetWalletId":"w_456","amount":"100.00","currency":"USD"}'

# ─── 第一步:请求体哈希 ───────────────────────────────────
BODY_HASH=$(echo -n "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')

# ─── 第二步:规范消息 ────────────────────────────────
CANONICAL="${METHOD}\n${PATH_URI}\n${TIMESTAMP}\n${NONCE}\n${BODY_HASH}"

# ─── 第三步:HMAC-SHA256 签名 ────────────────────────
SIGNATURE=$(echo -ne "$CANONICAL" | openssl dgst -sha256 -hmac "$API_SECRET" -binary | openssl base64 -A)

# ─── 发送请求 ─────────────────────────────────────────
curl -X "$METHOD" "${BASE_URL}${PATH_URI}" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $API_KEY" \
  -H "X-Signature: $SIGNATURE" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -d "$BODY"

对于 GET 请求,省略 -d 参数并使用空字符串的请求体哈希:

bash
METHOD="GET"
PATH_URI="/api/v1/wallets"
BODY=""
BODY_HASH="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"

CANONICAL="${METHOD}\n${PATH_URI}\n${TIMESTAMP}\n${NONCE}\n${BODY_HASH}"
SIGNATURE=$(echo -ne "$CANONICAL" | openssl dgst -sha256 -hmac "$API_SECRET" -binary | openssl base64 -A)

curl -X "$METHOD" "${BASE_URL}${PATH_URI}" \
  -H "X-Api-Key: $API_KEY" \
  -H "X-Signature: $SIGNATURE" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE"

Node.js

用于签名和发送 API 请求的可复用函数:

javascript
import crypto from 'crypto';

/**
 * 向 SlaunchX API 发送经过认证的请求。
 *
 * @param {string} method   - HTTP 方法(GET、POST、PUT、DELETE)
 * @param {string} path     - 请求路径(例如 /api/v1/wallets)
 * @param {object|null} body - 请求体(GET/DELETE 时为 null)
 * @param {string} apiKey   - 您的 API 密钥(sk_live_xxx)
 * @param {string} apiSecret - 您的 API 密钥
 * @returns {Promise<object>} 解析后的 JSON 响应
 */
async function apiRequest(method, path, body, apiKey, apiSecret) {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const nonce = crypto.randomUUID();

  // 第一步:请求体哈希
  const bodyStr = body ? JSON.stringify(body) : '';
  const bodyHash = crypto.createHash('sha256').update(bodyStr).digest('hex');

  // 第二步:规范消息
  const canonical = [method, path, timestamp, nonce, bodyHash].join('\n');

  // 第三步:HMAC-SHA256 签名
  const signature = crypto
    .createHmac('sha256', apiSecret)
    .update(canonical)
    .digest('base64');

  // 发送请求
  // 发送请求(将 api.slaunchx.cc 替换为您的 API 主机地址)
  const res = await fetch(`https://api.slaunchx.cc${path}`, {
    method,
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': apiKey,
      'X-Signature': signature,
      'X-Timestamp': timestamp,
      'X-Nonce': nonce,
    },
    body: bodyStr || undefined,
  });

  return res.json();
}

// ─── 使用示例 ──────────────────────────────────────────

// POST:创建转账
const transfer = await apiRequest(
  'POST',
  '/api/v1/transfer/command/create',
  {
    sourceWalletId: 'w_123',
    targetWalletId: 'w_456',
    amount: '100.00',
    currency: 'USD',
  },
  'sk_live_abc123def456',
  'your-api-secret-here'
);

// GET:列出钱包(无请求体)
const wallets = await apiRequest(
  'GET',
  '/api/v1/wallets',
  null,
  'sk_live_abc123def456',
  'your-api-secret-here'
);

服务器验证流水线

当服务器收到 API 请求时,会通过多步流水线验证请求。任何一步失败都会短路处理并返回相应的错误码。

┌─────────────────────────────────────────────────────────────────┐
│                          验证流水线                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. API 密钥查找                                                 │
│     ├── 缺少 X-Api-Key 请求头      →  GA2001 (401)               │
│     └── 未找到密钥                  →  GA2011 (401)               │
│                                                                 │
│  2. 密钥状态检查                                                  │
│     └── 管理员已禁用密钥             →  GA2021 (403)               │
│                                                                 │
│  3. 工作区解析                                                    │
│     ├── 未找到工作区                 →  GA2032 (404)               │
│     └── 非工作区成员                 →  GA2034 (403)               │
│                                                                 │
│  4. 时间戳验证                                                    │
│     └── |server_time - timestamp| > 60s →  GA2013 (401)          │
│                                                                 │
│  5. Nonce 唯一性                                                  │
│     └── Nonce 已被使用              →  GA2013 (401)               │
│                                                                 │
│  6. HMAC 签名验证                                                 │
│     └── 计算的 HMAC ≠ X-Signature   →  GA2012 (401)               │
│                                                                 │
│  7. IP 白名单(可选)                                              │
│     └── 客户端 IP 不在白名单中       →  GA2022 (403)               │
│                                                                 │
│  8. 权限范围检查                                                   │
│     └── 缺少所需权限范围             →  GA2024 (403)               │
│                                                                 │
│  ✓  所有检查通过 → 处理请求                                        │
└─────────────────────────────────────────────────────────────────┘

验证顺序很重要

流水线按固定顺序验证。例如,如果您的 API 密钥有效但时间戳已过期,您将收到 GA2013(时间戳过期)而非签名错误。请使用错误码来准确诊断哪一步失败了。


API 权限范围

API 密钥是 工作区级别 的——每个密钥属于一个工作区,只能访问该工作区内的资源。通过 API 密钥无法进行跨工作区访问。

每个 API 密钥被分配一个或多个权限范围,用于控制其可以访问哪些端点:

权限范围说明
wallet:read查询工作区内的钱包
transfer:read查询工作区内的转账记录
transfer:create在工作区内的钱包之间创建转账订单

可访问端点

权限范围方法端点说明
wallet:readGET/api/v1/wallets列出钱包
wallet:readGET/api/v1/wallets/{id}获取钱包详情
wallet:readGET/api/v1/wallets/{id}/balance获取钱包余额
wallet:readGET/api/v1/wallets/{id}/flows列出钱包流水
transfer:readGET/api/v1/transfer/query/orders列出转账订单
transfer:readGET/api/v1/transfer/query/orders/wallet/{walletId}按钱包列出转账
transfer:readGET/api/v1/transfer/query/orders/{bizId}获取转账订单
transfer:readGET/api/v1/transfer/query/orders/{bizId}/completed检查转账是否完成
transfer:createPOST/api/v1/transfer/command/create创建转账订单

未声明 API 权限范围的端点通过 API 链 不可访问(默认拒绝)。对此类端点的请求,或缺少所需权限范围的请求,将收到包含错误码 50090201(API_ACCESS_DENIED)的 403 Forbidden 响应。

工作区隔离

所有 API 响应自动限定在与 API 密钥绑定的工作区范围内。转账查询端点会强制执行工作区所有权验证——如果转账订单属于另一个工作区,请求将返回错误。即使使用有效的订单 ID,也能防止跨工作区数据泄露。


响应示例

成功的 API 响应:

转账成功200
{
  "version": "1.3.0",
  "timestamp": 1709337600000,
  "success": true,
  "code": "2000",
  "message": "SUCCESS",
  "data": {
    "transferId": "txn_789xyz",
    "status": "COMPLETED",
    "amount": "100.00",
    "currency": "USD"
  }
}

认证失败:

签名验证失败401
{
  "version": "1.3.0",
  "timestamp": 1709337600000,
  "success": false,
  "code": "GA2012",
  "message": "SIGNATURE_INVALID",
  "data": null
}

常见陷阱

:::caution 时间戳时钟偏移 服务器会拒绝时间戳与服务器时间相差超过 60 秒 的请求。如果遇到 GA2013 错误,请检查您的服务器时钟是否已通过 NTP 同步。在本地调试时,可以将您机器上的 date +%s 与 NTP 服务器进行比较。 :::

:::caution 空请求体的请求体哈希 GETDELETE 请求没有请求体。但您仍必须在规范消息中包含请求体哈希——使用空字符串的 SHA-256 值:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855。省略它或使用其他值将导致 GA2012。 :::

:::caution JSON 序列化一致性 请求体哈希覆盖请求体的 精确字节。如果您对一个 JSON 字符串计算哈希,但 HTTP 库重新序列化了请求体(改变了键顺序、空格或编码),签名将不匹配。最佳实践:序列化一次,对该字符串计算哈希,然后将该同一字符串作为请求体发送。 :::

:::caution Nonce 重复使用 每个 nonce 在 60 秒的时间戳窗口内必须全局唯一。重复使用 nonce(即使请求体不同)会导致 GA2013。使用 UUID v4 以确保唯一性。 :::

后续步骤

Elaypay API 文档