接口文档

环境信息

接口地址

生产环境 正式
https://api.huihanhc.com
所有接口正式生效并计费。
测试环境 测试
https://api-test.huihanhc.com
面向开发调试,不计费。
注意:测试环境与生产环境使用独立的商户配置,切勿将生产密钥用于测试环境。

接入流程

步骤说明预计耗时
1. 申请开通联系我方商务对接,获取商户号(MerchantId)、AppKey、AppSecret1 个工作日
2. 开发对接依据本文档完成签名算法实现和接口对接-

鉴权机制

所有业务接口均需通过 HMAC-SHA256 签名认证,签名由客户端计算后置于 HTTP 请求头中。

请求头说明

Header 名称类型必选说明示例
X-Merchant-Id string *是 商户号,由平台分配 -
X-App-Key string *是 应用公钥,由平台分配 -
X-Timestamp string *是 Unix 秒级时间戳,允许与网关时间 ±300 秒偏差 1755587400
X-Nonce string *是 随机字符串,每个请求唯一,用于防止重放攻击 a4588f2db8b8
X-Signature string *是 HMAC-SHA256 签名,Base64 编码,计算方式见下文 U2FsdGVkX1...
安全提示:AppSecret 是签名密钥,严禁暴露在客户端代码中,必须在服务端使用。

签名算法

1
计算 PathHash
PathHash = SHA256(HTTP方法 + 请求路径)
HTTP 方法使用全大写(如 POST、GET);请求路径不含域名
2
计算 BodyHash
BodyHash = SHA256(请求体原始内容)
请求体必须为原始 JSON 字符串,UTF-8 编码,不可修改空格、换行等字符
3
构造签名原串 SignStr
SignStr = MerchantId + Timestamp + Nonce + PathHash + BodyHash
按顺序直接拼接,无分隔符。SignStr 仅包含字母和数字
4
生成签名 Signature
Signature = Base64(HMAC-SHA256(AppSecret, SignStr))
HMAC-SHA256 的密钥为 AppSecret,消息为 SignStr,结果使用 Base64 编码

签名计算示例

以下为一组固定值的完整计算过程,可用于验证签名算法的正确性:

参数值
MerchantIdXXT20240001
AppSecretsk_xxt_live_7k9m2x4q6r8t0v3w5y7z9b1d3f5h8j
HTTP 方法POST
请求路径/v1/id_verify
请求体{"id_card":"123456789987654321","name":"测试"}
Timestamp1755587400
Noncea4588f2db8b8
PathHash (结果)3f08e4be594a9bd397473492c089a43905bfd06d57c656cc86fc360c457755ec
BodyHash (结果)be22ced812fa1f598e708f819a4717607fd5d72c9b328d4be98c348d79be135a

Node.js 签名示例

JavaScript / Node.js
// 签名算法示例(Node.js)
const crypto = require('crypto');

const merchantId = 'XXT20240001';
const appSecret  = 'sk_xxt_live_7k9m2x4q6r8t0v3w5y7z9b1d3f5h8j';
const method     = 'POST';
const path       = '/v1/id_verify';
const body       = '{"id_card":"123456789987654321","name":"测试"}';
const timestamp  = 1755587400;
const nonce      = 'a4588f2db8b8';

// 1. 计算 PathHash 和 BodyHash (SHA256 → Hex 小写)
const pathHash = crypto.createHash('sha256')
  .update(method + path, 'utf8').digest('hex');
const bodyHash = crypto.createHash('sha256')
  .update(body, 'utf8').digest('hex');

// 2. 构造签名原串 (只含字母和数字)
const signStr = merchantId + timestamp + nonce + pathHash + bodyHash;

// 3. 生成签名 (HMAC-SHA256 → Base64)
const signature = crypto.createHmac('sha256', appSecret)
  .update(signStr, 'utf8').digest('base64');

console.log({ signStr, signature });

API 接口

身份证二要素核验

POST /v1/id_verify
功能说明核验身份证号码与姓名是否匹配
鉴权要求是,需携带完整签名头

请求参数

参数名类型必选说明示例
id_card string *是 18 位身份证号码 123456789987654321
name string *是 真实姓名 测试
请求示例(cURL)
curl -X POST https://api.example.com/v1/id_verify \
  -H "Content-Type: application/json" \
  -H "X-Merchant-Id: XXT20240001" \
  -H "X-App-Key: pk_xxt_live_a3f8b2c1d9e0" \
  -H "X-Timestamp: 1755587400" \
  -H "X-Nonce: a4588f2db8b8" \
  -H "X-Signature: U2FsdGVkX1..." \
  -d '{"id_card":"123456789987654321","name":"测试"}'

响应参数

字段类型说明
codestring业务状态码,成功为 "OK"
msgstring结果描述
request_idstring请求唯一标识,排查问题时使用
channel_idstring本次调用使用的上游通道 ID
cost_timeinteger网关处理总耗时,单位毫秒
data.verifiedboolean是否完成核验
data.matchboolean身份证号与姓名是否匹配
data.messagestring核验结果文字说明
响应示例(成功)
{
  "code": "OK",
  "msg": "成功",
  "request_id": "abc123def456",
  "channel_id": "-",
  "cost_time": 256,
  "data": {
    "verified": true,
    "match": true,
    "message": "身份核验通过"
  }
}

身份证三要素核验(人脸比对)

POST /v1/id_verify_3factor
功能说明核验身份证号、姓名、人脸照片三者是否一致,返回相似度与判定结果
鉴权要求是,需携带完整签名头

请求参数

参数名类型必选说明示例
id_card string *是 18 位身份证号码 123456789987654321
name string *是 真实姓名 测试
photo string *是 人脸照片 Base64 编码(不含 data:image 前缀),详见下方图片要求 /9j/4AAQSkZJRg...
bio boolean 否 是否启用活体检测。启用后耗时更长但安全性更高,默认 false false
图片要求: 格式:JPG / PNG;大小 ≤ 30KB(建议 10KB-30KB);推荐分辨率 480×640,最大不超过 1080P; 内容为正面清晰头像,无多人脸,无遮挡,光线自然,面部轮廓清晰,双眼间距建议不小于 90 像素。

响应参数

字段类型说明
codestring业务状态码
msgstring结果描述
request_idstring请求唯一标识
channel_idstring上游通道 ID
cost_timeinteger总耗时,单位毫秒
data.similarityfloat相似度,范围 0-100
data.ref_thresharray参考阈值,依次为 [宽松 1e-3, 标准 1e-4, 严格 1e-5],一般取标准阈值判定
data.passboolean是否通过,相似度 ≥ 标准阈值(83) 即为 true
响应示例(比对通过)
{
  "code": "OK",
  "msg": "成功",
  "request_id": "abc123def456",
  "channel_id": "-",
  "cost_time": 756,
  "data": {
    "similarity": 95.23,
    "ref_thresh": [75, 83, 88],
    "pass": true
  }
}
响应示例(相似度不足)
{
  "code": "UPSTREAM_FAILED",
  "msg": "人脸比对相似度低于阈值",
  "request_id": "abc123",
  "channel_id": "-",
  "cost_time": 756,
  "data": {
    "similarity": 45.12,
    "ref_thresh": [75, 83, 88],
    "pass": false
  }
}

错误码说明

网关错误码

HTTP 状态业务码说明处理建议
401401缺少签名认证头检查是否携带了全部 5 个鉴权头字段
401401签名无效核查签名算法是否正确,AppSecret 是否匹配,Body 与签名时是否一致
401401时间戳过期检查服务器时钟是否同步,网关允许 ±300 秒偏差
401401Nonce 已使用确保每次请求使用唯一随机值,Nonce 在 10 分钟内不可重复
401401商户已禁用联系我方商务确认商户状态
404404接口不存在检查请求路径是否正确,注意是否与文档一致
429429请求过于频繁请控制请求频率在商户额度范围内,可申请提升 QPS

业务错误码

业务码说明处理建议
20000成功—
204002身份信息不存在检查身份证号和姓名是否真实有效
204003身份信息格式不正确身份证号需通过校验位算法,姓名不可为空或超长
204004人脸图片质量不达标更换符合要求的高清正面照
204005活体检测失败请正对镜头按要求完成活体动作
204006人脸与身份证照不匹配请使用本人照片

完整代码示例

C# (.NET) 完整示例

C# / .NET 8
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

// 配置
var merchantId = "XXT20240001";
var appKey = "pk_xxt_live_a3f8b2c1d9e0";
var appSecret = "sk_xxt_live_7k9m2x4q6r8t0v3w5y7z9b1d3f5h8j";
var baseUrl = "https://api.example.com";

// 请求体
var payload = new
{
    id_card = "123456789987654321",
    name = "测试"
};
var body = JsonSerializer.Serialize(payload);
var method = "POST";
var path = "/v1/id_verify";

// 签名参数
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
var nonce = Convert.ToHexString(RandomNumberGenerator.GetBytes(8)).ToLowerInvariant();
var pathHash = ComputeHash(method + path);
var bodyHash = ComputeHash(body);
var signStr = $"{merchantId}{timestamp}{nonce}{pathHash}{bodyHash}";
var signature = ComputeSignature(appSecret, signStr);

// 发送请求
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("X-Merchant-Id", merchantId);
http.DefaultRequestHeaders.Add("X-App-Key", appKey);
http.DefaultRequestHeaders.Add("X-Timestamp", timestamp);
http.DefaultRequestHeaders.Add("X-Nonce", nonce);
http.DefaultRequestHeaders.Add("X-Signature", signature);

var res = await http.PostAsync(baseUrl + path,
    new StringContent(body, Encoding.UTF8, "application/json"));
var resBody = await res.Content.ReadAsStringAsync();

// —— 工具函数 ——
static string ComputeHash(string input)
{
    using var sha = SHA256.Create();
    return Convert.ToHexString(sha.ComputeHash(Encoding.UTF8.GetBytes(input)))
        .ToLowerInvariant();
}
static string ComputeSignature(string key, string data)
{
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
    return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(data)));
}

Java 完整示例

Java / JDK 11+
import java.net.URI;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class GatewaySignDemo {
    static final String MERCHANT_ID  = "XXT20240001";
    static final String APP_KEY      = "pk_xxt_live_a3f8b2c1d9e0";
    static final String APP_SECRET   = "sk_xxt_live_7k9m2x4q6r8t0v3w5y7z9b1d3f5h8j";
    static final String BASE_URL     = "https://api.example.com";

    static final MessageDigest SHA;
    static {
        try { SHA = MessageDigest.getInstance("SHA-256"); }
        catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); }
    }

    public static String hex(byte[] b) {
        StringBuilder sb = new StringBuilder();
        for (byte x : b) sb.append(String.format("%02x", x));
        return sb.toString();
    }

    public static String sign(String key, String data) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(key.getBytes(), "HmacSHA256"));
        return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes()));
    }

    public static void main(String[] args) throws Exception {
        String method = "POST";
        String path   = "/v1/id_verify";
        String body   = "{\"id_card\":\"123456789987654321\",\"name\":\"测试\"}";

        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        SecureRandom r   = new SecureRandom();
        String nonce     = String.format("%016x", new java.math.BigInteger(64, r));

        String pathHash = hex(SHA.digest((method + path).getBytes(StandardCharsets.UTF_8)));
        String bodyHash = hex(SHA.digest(body.getBytes(StandardCharsets.UTF_8)));
        String signStr  = MERCHANT_ID + timestamp + nonce + pathHash + bodyHash;
        String signature = sign(APP_SECRET, signStr);

        HttpClient client = HttpClient.newHttpClient();
        HttpRequest req = HttpRequest.newBuilder()
            .uri(URI.create(BASE_URL + path))
            .header("Content-Type", "application/json")
            .header("X-Merchant-Id", MERCHANT_ID)
            .header("X-App-Key", APP_KEY)
            .header("X-Timestamp", timestamp)
            .header("X-Nonce", nonce)
            .header("X-Signature", signature)
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        HttpResponse resp = client.send(req, HttpResponse.BodyHandlers.ofString());
        System.out.println(resp.body());
    }
}