host
正式环境:https://isv.lingxiaodata.com
响应格式
所有接口返回 JSON 结构如下:
{
"code": 0, // `0` 表示成功;非 `0` 表示失败
"message": "请求成功",
"data": {
// 返回业务数据结构;失败时为null
},
"traceId": "abc123..." //链路 ID,反馈问题时请一并提供
}
请求头签名认证
每个接口请求都必须携带以下 4 个 HTTP Header:
| Header | 说明 |
|---|---|
X-App-Key |
BFS系统下发的 appKey |
X-Timestamp |
当前毫秒时间戳(与服务器差值需在 ±5 分钟内) |
X-Nonce |
随机字符串(如 UUID),确保系统内唯一即可 |
X-Sign |
请求签名,小写十六进制(签名需要用到 appSecret) |
X-Sign签名计算步骤描述
1. 准备 method(大写,如 POST、GET)
2. 准备 path(完整路径,不含域名与 query,如 /api/isv/v1/org/trial)
3. 准备 sortedQuery(query 参数按 key 字典序拼接 k1=v1&k2=v2,无 query 则为空串;
**value 使用解码后的原始字符串,不要 percent-encode**;中文等字符按 UTF-8 原文参与签名)
4. 计算 bodySha256 = SHA256(原始请求体字符串) 的小写 hex;无 body 时对 "" 做 SHA256
5. 拼接 canonicalString(6 行,用换行符 \n 连接):
method + "\n" + path + "\n" + sortedQuery + "\n" + bodySha256 + "\n" + timestamp + "\n" + nonce
6. X-Sign = HMAC-SHA256(appSecret, canonicalString) 的小写 hex- GET、DELETE 等无body内容时,请使用空字符串
"",其SHA256(小写 hex)值为:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
X-Sign签名示例数据和代码
以 注册账号 接口为例,有如下参数:
| 项 | 值 |
|---|---|
| appKey | xaq7xbwswzcap1ka2tylajeje2jl0czg |
| appSecret | q4zs3qlzs7wig6wqt9eewbv7y10kbwrs |
| method | POST |
| path | /api/isv/v1/org/trial |
| sortedQuery | (空串,本接口无 query) |
| body | {"thirdOrgName":"示例公司","thirdUserName":"张三","thirdRootUserId":"your-user-001"} |
| bodySha256 | 9da29681b7c7c7b35c3f86c6723bb8f62b0298f55de42d67b9f514c8d8ab10e5 |
| timestamp | 1788588527954 |
| nonce | nvbrt0mj16vjljzbgmlyp8t6yepun4q6 |
最终X-Sign |
0c1301cb88a6eea4aa34faf5936e2955c69353872a650b1775b687901ac29712 |
curl请求代码
curl -X POST 'https://isv.lingxiaodata.com/api/isv/v1/org/trial' \
-H 'Content-Type: application/json' \
-H 'X-App-Key: xaq7xbwswzcap1ka2tylajeje2jl0czg' \
-H 'X-Timestamp: 1788588527954' \
-H 'X-Nonce: nvbrt0mj16vjljzbgmlyp8t6yepun4q6' \
-H 'X-Sign: 0c1301cb88a6eea4aa34faf5936e2955c69353872a650b1775b687901ac29712' \
-d '{"thirdOrgName":"示例公司","thirdUserName":"张三","thirdRootUserId":"your-user-001"}'
- 发起请求时注意: 每次请求使用新的
X-Nonce和当前X-Timestamp(上文示例值为固定演示,实际调用请替换)
Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
public class IsvSignDemo {
public static String sha256Hex(String body) throws Exception {
if (body == null) body = "";
MessageDigest md = MessageDigest.getInstance("SHA-256");
return toHex(md.digest(body.getBytes(StandardCharsets.UTF_8)));
}
public static String sign(String appSecret, String method, String path,
String sortedQuery, String bodySha256,
String timestamp, String nonce) throws Exception {
String canonical = method + "\n" + path + "\n" + sortedQuery + "\n"
+ bodySha256 + "\n" + timestamp + "\n" + nonce;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
return toHex(mac.doFinal(canonical.getBytes(StandardCharsets.UTF_8)));
}
/** query 参数按 key 字典序拼接 */
public static String sortedQuery(Map<String, String> params) {
if (params == null || params.isEmpty()) return "";
return new TreeMap<>(params).entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
}
private static String toHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public static void main(String[] args) throws Exception {
String body = "{\"thirdOrgName\":\"示例公司\",\"thirdUserName\":\"张三\",\"thirdRootUserId\":\"your-user-001\"}";
String sign = sign("q4zs3qlzs7wig6wqt9eewbv7y10kbwrs", "POST", "/api/isv/v1/org/trial",
"", sha256Hex(body), "1788588527954", "nvbrt0mj16vjljzbgmlyp8t6yepun4q6");
System.out.println(sign);
// 0c1301cb88a6eea4aa34faf5936e2955c69353872a650b1775b687901ac29712
}
}
Python
import hashlib
import hmac
def sha256_hex(body: str | None) -> str:
data = (body or "").encode("utf-8")
return hashlib.sha256(data).hexdigest()
def sorted_query(params: dict[str, str] | None) -> str:
"""按 key 字典序拼接;value 用原始字符串,不要 urlencode。"""
if not params:
return ""
return "&".join(f"{k}={v}" for k, v in sorted(params.items()))
def sign(app_secret: str, method: str, path: str, sorted_query: str,
body_sha256: str, timestamp: str, nonce: str) -> str:
canonical = "\n".join([method, path, sorted_query, body_sha256, timestamp, nonce])
return hmac.new(
app_secret.encode("utf-8"),
canonical.encode("utf-8"),
hashlib.sha256,
).hexdigest()
if __name__ == "__main__":
body = '{"thirdOrgName":"示例公司","thirdUserName":"张三","thirdRootUserId":"your-user-001"}'
s = sign(
"q4zs3qlzs7wig6wqt9eewbv7y10kbwrs", "POST", "/api/isv/v1/org/trial",
"", sha256_hex(body), "1788588527954", "nvbrt0mj16vjljzbgmlyp8t6yepun4q6",
)
print(s)
# 0c1301cb88a6eea4aa34faf5936e2955c69353872a650b1775b687901ac29712
Node.js
const crypto = require("crypto");
function sha256Hex(body) {
return crypto.createHash("sha256").update(body || "", "utf8").digest("hex");
}
function sortedQuery(params) {
if (!params || Object.keys(params).length === 0) return "";
return Object.keys(params)
.sort()
.map((k) => `${k}=${params[k]}`)
.join("&");
}
function sign(appSecret, method, path, sortedQueryStr, bodySha256, timestamp, nonce) {
const canonical = [method, path, sortedQueryStr, bodySha256, timestamp, nonce].join("\n");
return crypto.createHmac("sha256", appSecret).update(canonical, "utf8").digest("hex");
}
const body = '{"thirdOrgName":"示例公司","thirdUserName":"张三","thirdRootUserId":"your-user-001"}';
const xSign = sign(
"q4zs3qlzs7wig6wqt9eewbv7y10kbwrs", "POST", "/api/isv/v1/org/trial",
"", sha256Hex(body), "1788588527954", "nvbrt0mj16vjljzbgmlyp8t6yepun4q6"
);
console.log(xSign);
// 0c1301cb88a6eea4aa34faf5936e2955c69353872a650b1775b687901ac29712
PHP
<?php
function sha256_hex(?string $body): string {
return hash('sha256', $body ?? '');
}
function sorted_query(array $params): string {
if (empty($params)) {
return '';
}
ksort($params);
$parts = [];
foreach ($params as $k => $v) {
$parts[] = $k . '=' . $v;
}
return implode('&', $parts);
}
function sign(string $appSecret, string $method, string $path, string $sortedQuery,
string $bodySha256, string $timestamp, string $nonce): string {
$canonical = implode("\n", [$method, $path, $sortedQuery, $bodySha256, $timestamp, $nonce]);
return hash_hmac('sha256', $canonical, $appSecret);
}
$body = '{"thirdOrgName":"示例公司","thirdUserName":"张三","thirdRootUserId":"your-user-001"}';
$xSign = sign('q4zs3qlzs7wig6wqt9eewbv7y10kbwrs', 'POST', '/api/isv/v1/org/trial',
'', sha256_hex($body), '1788588527954', 'nvbrt0mj16vjljzbgmlyp8t6yepun4q6');
echo $xSign . PHP_EOL;
// 0c1301cb88a6eea4aa34faf5936e2955c69353872a650b1775b687901ac29712
Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"sort"
"strings"
)
func sha256Hex(body string) string {
h := sha256.Sum256([]byte(body))
return hex.EncodeToString(h[:])
}
func sortedQuery(params map[string]string) string {
if len(params) == 0 {
return ""
}
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
parts := make([]string, 0, len(keys))
for _, k := range keys {
parts = append(parts, k+"="+params[k])
}
return strings.Join(parts, "&")
}
func sign(appSecret, method, path, sortedQuery, bodySha256, timestamp, nonce string) string {
canonical := strings.Join([]string{method, path, sortedQuery, bodySha256, timestamp, nonce}, "\n")
m := hmac.New(sha256.New, []byte(appSecret))
m.Write([]byte(canonical))
return hex.EncodeToString(m.Sum(nil))
}
func main() {
body := `{"thirdOrgName":"示例公司","thirdUserName":"张三","thirdRootUserId":"your-user-001"}`
xSign := sign("q4zs3qlzs7wig6wqt9eewbv7y10kbwrs", "POST", "/api/isv/v1/org/trial",
"", sha256Hex(body), "1788588527954", "nvbrt0mj16vjljzbgmlyp8t6yepun4q6")
fmt.Println(xSign)
// 0c1301cb88a6eea4aa34faf5936e2955c69353872a650b1775b687901ac29712
}
常见错误码
认证类(41xxx)
| code | 含义 | 建议处理 |
|---|---|---|
| 41001 | 缺少 ISV 认证头 | 检查是否携带 X-App-Key / X-Timestamp / X-Nonce / X-Sign |
| 41002 | AppKey 不存在或代理商已禁用 | 核对 appKey,或联系平台确认代理商状态 |
| 41003 | 签名校验失败 | 对照本文签名说明检查 path、query 排序、body 是否与发送一致 |
| 41004 | 请求时间戳超出容差范围 | 同步服务器时间或使用 NTP |
| 41005 | Nonce 重复 | 每次请求使用新的随机 Nonce |
| 41006 | 客户端 IP 不在白名单 | 联系平台添加出口 IP 白名单 |
业务类(42xxx)
| code | 含义 | 建议处理 |
|---|---|---|
| 42001 | 企业不存在或未绑定 | 检查 oid(登录用户在我们系统的企业账号 ID)是否正确 |
| 42002 | 用户不存在或未绑定 | 检查 uid(登录用户在我们系统的登录用户 ID)是否正确 |
| 42004 | 参数不合法 | 检查接口参数必填项与格式 |
| 42005 | 工商企业不存在 | 工商信息查询 keyword 未精准匹配到企业 |
系统 / 内部异常类(43xxx)
| code | 含义 | 建议处理 |
|---|---|---|
| 43001 | 内部系统暂时不可用 | 稍后重试 |
| 43002 | 内部业务异常 | 根据 message 排查(常见:工号不足、账号已存在、用户已授权等)或 联系平台(提供详尽参数 和 traceId) |
文档更新时间: 2026-09-16 23:42 作者:004