Sign in
简介/身份验证

身份验证与请求签名

使用 project UUID 和 API key 通过 HMAC-SHA256 对 API 请求签名。

每个 API 请求(接收方向的 webhook 除外)都必须带上 project UUID 和请求签名。签名用于证明请求来自您本人,并且在传输过程中没有被篡改。

API 密钥

2328.io 使用 两套密钥,签名算法完全相同,但作用范围不同:

密钥使用范围
API key支付、静态钱包、余额、汇率端点,以及支付 / 静态钱包 webhook 的签名校验
Payout API key所有 /v1/payout/* 端点,以及提现 webhook 的签名校验

两套密钥都在 2328.io 的项目设置中创建。下文示例中的 "API key" 是通用占位 — 请根据所调用端点替换为正确的密钥。

切勿混用 两套密钥:用普通 API key 签名提现请求(或反之)会返回签名错误。

必需的请求头

请求头类型必需描述
Content-Typestring始终为 application/json
projectstring您的项目 UUID
signstring使用 API key 计算的请求 HMAC-SHA256 签名
User-Agentstring标识您的应用(例如 MyShop/1.4 (+https://myshop.example))。不带 User-Agent 的请求可能会被拦截。

签名是如何生成的

可以把签名理解为请求体的「指纹」。生成步骤:

  1. 将请求体序列化为 JSON(紧凑格式,无多余空白)。
  2. 对该 JSON 进行 Base64 编码。这一步把输入归一化为纯 ASCII — 这样不同语言计算出的 HMAC 才能保持一致。
  3. 使用您的 API key 对 Base64 字符串计算 HMAC-SHA256,结果以小写十六进制表示。

对于 GET 等没有请求体的请求,对空字符串进行签名即可。

对于给定的 API key,空字符串签名始终相同。如果有大量 GET 调用,可以将其缓存。

各语言实现

PHP
<?php
function apiSign(array $data, string $apiKey): string {
    $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    $base64 = base64_encode($json);
    return hash_hmac('sha256', $base64, $apiKey);
}

无请求体的请求(GET)

对于没有请求体的请求(例如 GET /v1/payout/status/{uuid}),对空字符串签名:

Shell
SIGN=$(printf '' | openssl dgst -sha256 -hmac "$API_KEY" -hex | awk '{print $NF}')

完整请求示例

Shell
curl -X POST https://api.2328.io/api/v1/payment \
  -H "Content-Type: application/json" \
  -H "User-Agent: MyShop/1.0 (+https://myshop.example)" \
  -H "project: YOUR_PROJECT_UUID" \
  -H "sign: YOUR_HMAC_SIGNATURE" \
  -d '{"amount":"100.00","currency":"USD","order_id":"ORDER-123"}'

切勿在客户端代码中暴露您的 API key。 所有签名必须在您的后端完成。一旦 API key 泄露,任何人都可以完全访问您的商户账户。

校验 webhook 签名

当 2328.io 发送 webhook 给您时,使用相同的算法反向校验:

  1. 从载荷中取出 sign 字段。
  2. 将剩余字段编码为紧凑 JSON(无空白)。
  3. 对结果做 Base64 编码。
  4. 使用对应的密钥计算 HMAC-SHA256
  5. 使用 恒定时间 比较函数(hash_equalscrypto.timingSafeEqualhmac.compare_digestsubtle.ConstantTimeCompareOpenSSL.fixed_length_secure_compare)与收到的 sign 比较。

签名密钥根据 webhook 来源不同而不同:

Webhook校验密钥
支付 / 静态钱包 webhook(/v1/payment/v1/static-walletAPI key
提现 webhook(/v1/payoutPayout API key

常见的校验陷阱。 您的 JSON 编码器必须产生与发送方完全相同的字节 — 否则 Base64 会不同,签名也无法匹配。

  • Go:使用 json.NewEncoder 并设置 SetEscapeHTML(false)。默认的 json.Marshal 会将 <>& 转义为 <,从而破坏签名。
  • Python:在 json.dumps 中传入 ensure_ascii=False。否则非 ASCII 字符(西里尔字母、中文等)会被转义为 \uXXXX
  • 紧凑 JSON:字段之间不能有空白(在 Python 中使用 separators=(",", ":"))。
  • 字段顺序(Go):普通的 map[string]any 在重新编码时会随机化键的顺序。请使用 json.RawMessage、有序的 struct,或从原始字节中剥离 sign

如果校验始终失败,请自己对载荷运行 apiSign — 它必须产生与收到的 sign 相同的十六进制字符串。

有效的签名并不能防止重放。 它只能证明 webhook 来自 2328.io — 并不能阻止攻击者稍后重新发送已捕获的 webhook。在记账之前,请始终通过 uuid(或对静态钱包使用 txid)检查幂等性。如果签名缺失或错误,请以 HTTP 401 拒绝。

完整的代码示例请见 Webhook 通知。重试处理与幂等性规则见 最佳实践