鉴权
HMAC-SHA256 签名规范
所有 /v1/* 接口必须用 HMAC-SHA256 签名。下面是签名规范、参考实现和常见错误对照。
必须的 Header
| Header | 必填 | 说明 |
|---|---|---|
X-Api-Key | 是 | 凭证生成时颁发 |
X-Timestamp | 是 | 毫秒时间戳。容差 ±5 分钟 |
X-Service-Code | 是 | payments / activation / miniprogram |
X-Signature | 是 | HMAC-SHA256 hex(见下文算法) |
签名算法
pseudo
signingKey = sha256_hex(apiSecret)
payload = timestamp + lower(serviceCode) + JSON.stringify(body || {})
signature = hmac_sha256_hex(signingKey, payload)- signingKey 不是 apiSecret 本身,是它的 sha256 hex,因为 SMP 服务端只存 hash。
- serviceCode 大小写不敏感,签名前先 toLowerCase。
- body 为 null/undefined 时按
{}序列化。 - 请求方序列化后的字节序就是签名的字节序,接收方原样转发即可。不要在中间层重新排序对象 key。
参考实现
Node.js / TypeScript
ts
import crypto from "crypto";
export function sign(apiSecret: string, serviceCode: string, body: unknown) {
const key = crypto.createHash("sha256").update(apiSecret).digest("hex");
const ts = Date.now().toString();
const payload = ts + serviceCode.toLowerCase() + JSON.stringify(body ?? {});
const sig = crypto.createHmac("sha256", key).update(payload).digest("hex");
return { ts, sig };
}Python
python
import hashlib, hmac, json, time
def sign(api_secret: str, service_code: str, body):
key = hashlib.sha256(api_secret.encode()).hexdigest()
ts = str(int(time.time() * 1000))
payload = ts + service_code.lower() + json.dumps(body or {}, separators=(",", ":"))
sig = hmac.new(key.encode(), payload.encode(), hashlib.sha256).hexdigest()
return ts, sig⚠️ Python 默认 json.dumps 加空格,要用 separators=(",", ":") 与 Node 的 JSON.stringify 字节对齐。
Java
java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
static String[] sign(String apiSecret, String serviceCode, String bodyJson) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] keyBytes = md.digest(apiSecret.getBytes());
String key = bytesToHex(keyBytes);
String ts = String.valueOf(System.currentTimeMillis());
String payload = ts + serviceCode.toLowerCase() + (bodyJson == null ? "{}" : bodyJson);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key.getBytes(), "HmacSHA256"));
String sig = bytesToHex(mac.doFinal(payload.getBytes()));
return new String[]{ts, sig};
}常见错误码
| code | HTTP | 原因 |
|---|---|---|
SMP_SIGNATURE_INVALID | 401 | 检查 secret / serviceCode / body 字节 |
SMP_REQUEST_EXPIRED | 401 | 时间戳偏差 > 5 分钟 |
SMP_API_KEY_INACTIVE | 401 | credential 已停用 / 过期 |
SMP_SCOPE_INSUFFICIENT | 403 | credential scope 缺该 service |
SMP_SERVICE_DISABLED | 403 | platform 没开通该 service |
SMP_IP_NOT_ALLOWED | 403 | IP 白名单拒绝 |
出向通知(SMP → 你)
SMP 转发支付成功 / 退款 / 小程序审核结果时,会用该 platform credential 的 hash 作为签名 key(即 sha256(apiSecret)),算法与入向一致。接收端用同一 sign() 工具验证即可。详见异步通知章节。