接口列表

方法路径说明鉴权
POST/api/open/v1/verify提交核验HMAC 签名
GET/api/open/v1/query查询核验结果HMAC 签名
GET/api/open/v1/balance查询剩余次数HMAC 签名

接入流程

本平台为无法自行申请支付宝实名认证资质的网站提供核验底座:支付宝侧的全部配置(AppID、密钥、回跳地址)由平台持有,接入方无需向支付宝登记任何域名。

#步骤说明
1注册并实名在用户中心注册账号,完成经营者本人实名认证
2购买套餐按次计费;调用核验前需有可用套餐次数(不足返回 1004)
3创建应用获得 AppKey / AppSecret(Secret 仅创建时明文显示一次);可填写回调地址,留空则用查询接口取结果,随时可改
4提交核验调用 POST /api/open/v1/verify,响应含 certify_url
5用户完成人脸核验引导用户打开 certify_url 完成认证
6获取结果用户完成后平台主动 POST 回调(约 5 秒内,见「结果获取时效」);也可轮询 GET /api/open/v1/query,查询会即时向支付宝核实

签名规则

所有请求必须携带以下四个请求头。签名串按固定顺序拼接,空值参数不参与。

待签串 = METHOD + "\n"
       + PATH + "\n"
       + "k1=v1&k2=v2&..." + "\n"     # 参数按 key 字典序,每项后都带 &
       + TIMESTAMP + "\n"
       + NONCE

sign = hex( HMAC-SHA256(app_secret, 待签串) )   # 小写十六进制
请求头说明
X-App-Key应用 AppKey
X-Timestamp秒级时间戳,与服务器相差不超过 5 分钟
X-Nonce随机串,5 分钟内不可重复(防重放)
X-Sign上述算法算出的签名

请求示例

提交核验的请求体为表单编码(application/x-www-form-urlencoded,不是 JSON)。参与签名的业务参数:out_order_no、type、name、idcard、phone(phone 仅三要素需要),空值不参与签名,签名使用解码后的参数值。

POST /api/open/v1/verify
Content-Type: application/x-www-form-urlencoded
X-App-Key: ak_xxxxxxxxxxxx
X-Timestamp: 1789801118
X-Nonce: 8f3a9c2e5b1d
X-Sign: <按签名规则计算>

out_order_no=ORDER_001&type=face&name=张三&idcard=110101199003074258

type 取值:face 人脸核验 / two 二要素 / three 三要素(必须携带 phone)。成功响应 data.certify_url 即人脸核验跳转地址,引导用户打开完成认证。

参数类型必填说明
out_order_nostring是商户订单号,同一应用内唯一,重复返回 1008
typestring否核验类型:face 人脸(默认)/ two 二要素 / three 三要素
namestring是真实姓名,至少 2 个字符
idcardstring是身份证号,需通过校验位校验
phonestring三要素必填手机号,仅 three 类型需要

服务端请求示例(Python / PHP / Go)

以下三种语言可直接运行的最小实现,签名算法完全一致,任选其一接入。要点:签名用解码后的参数值按 key 字典序拼接(空值不参与);提交核验的请求体是表单编码(不是 JSON);签名结果为小写十六进制。

① Python(pip install requests)

import hmac, hashlib, time, uuid, requests

APP_KEY    = "ak_xxxxxxxx"
APP_SECRET = "你的应用Secret"
BASE       = "https://平台域名"

def signed_headers(method, path, params):
    ts    = str(int(time.time()))
    nonce = uuid.uuid4().hex                       # 每次请求唯一
    kv  = "".join(f"{k}={params[k]}&" for k in sorted(params) if params[k])
    raw = method + "\n" + path + "\n" + kv + "\n" + ts + "\n" + nonce
    sign = hmac.new(APP_SECRET.encode(), raw.encode(),
                    hashlib.sha256).hexdigest()
    return {"X-App-Key": APP_KEY, "X-Timestamp": ts,
            "X-Nonce": nonce, "X-Sign": sign}

# 1. 提交核验(表单编码)
params = {"out_order_no": "ORDER_001", "type": "face",
          "name": "张三", "idcard": "110101199003074258"}
r = requests.post(BASE + "/api/open/v1/verify",
                  data=params,
                  headers=signed_headers("POST", "/api/open/v1/verify", params))
certify_url = r.json()["data"]["certify_url"]   # 引导用户打开完成人脸核验

# 2. 查询结果(轮询)
q = {"out_order_no": "ORDER_001"}
r = requests.get(BASE + "/api/open/v1/query", params=q,
                 headers=signed_headers("GET", "/api/open/v1/query", q))
print(r.json()["data"]["passed"])

② PHP(依赖 curl 扩展)

<?php
// 签名算法与 Python / Go 完全一致
$appKey    = "ak_xxxxxxxx";
$appSecret = "你的应用Secret";
$base      = "https://平台域名";

// 生成四个鉴权请求头
function signed_headers($method, $path, $params) {
    global $appKey, $appSecret;
    $ts    = (string)time();
    $nonce = bin2hex(random_bytes(8));           // 每次请求唯一
    ksort($params);                              // 按 key 字典序
    $kv = "";
    foreach ($params as $k => $v) {
        if (trim((string)$v) === "") continue;   // 空值不参与
        $kv .= $k . "=" . $v . "&";             // 每项后都带 &
    }
    $raw  = strtoupper($method) . "\n" . $path . "\n" . $kv . "\n" . $ts . "\n" . $nonce;
    $sign = hash_hmac("sha256", $raw, $appSecret);   // 小写 hex
    return [
        "X-App-Key: $appKey",
        "X-Timestamp: $ts",
        "X-Nonce: $nonce",
        "X-Sign: $sign",
    ];
}

// 统一调用:GET 拼 query,POST 用表单编码
function api_call($method, $path, $params) {
    global $base;
    $ch  = curl_init();
    $url = $base . $path;
    if (strtoupper($method) === "GET") {
        $url .= "?" . http_build_query($params);
    } else {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
    }
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, signed_headers($method, $path, $params));
    $resp = curl_exec($ch);
    curl_close($ch);
    return json_decode($resp, true);
}

// 1. 提交核验(表单编码)
$r = api_call("POST", "/api/open/v1/verify", [
    "out_order_no" => "ORDER_001",
    "type"         => "face",
    "name"         => "张三",
    "idcard"       => "110101199003074258",
]);
$certifyUrl = $r["data"]["certify_url"];   // 引导用户打开完成人脸核验

// 2. 查询结果(轮询)
$q = api_call("GET", "/api/open/v1/query", ["out_order_no" => "ORDER_001"]);
echo $q["data"]["passed"] ? "通过" : "未通过或处理中";

③ Go(标准库,零第三方依赖)

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "sort"
    "strconv"
    "strings"
    "time"
)

const (
    appKey    = "ak_xxxxxxxx"
    appSecret = "你的应用Secret"
    base      = "https://平台域名"
)

// buildSignString 构造待签串:METHOD\nPATH\nk1=v1&k2=v2&\nTIMESTAMP\nNONCE
func buildSignString(method, path string, params map[string]string, ts, nonce string) string {
    keys := make([]string, 0, len(params))
    for k, v := range params {
        if strings.TrimSpace(v) == "" {   // 空值不参与签名
            continue
        }
        keys = append(keys, k)
    }
    sort.Strings(keys)                    // 按 key 字典序

    var sb strings.Builder
    sb.WriteString(strings.ToUpper(method))
    sb.WriteString("\n")
    sb.WriteString(path)
    sb.WriteString("\n")
    for _, k := range keys {
        sb.WriteString(k)
        sb.WriteString("=")
        sb.WriteString(params[k])
        sb.WriteString("&")             // 每项后都带 &
    }
    sb.WriteString("\n")
    sb.WriteString(ts)
    sb.WriteString("\n")
    sb.WriteString(nonce)
    return sb.String()
}

// calcSign 计算 HMAC-SHA256 签名(小写十六进制)
func calcSign(secret, signStr string) string {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(signStr))
    return hex.EncodeToString(mac.Sum(nil))
}

// signedHeaders 生成四个鉴权请求头
func signedHeaders(method, path string, params map[string]string) map[string]string {
    ts    := strconv.FormatInt(time.Now().Unix(), 10)
    nonce := strconv.FormatInt(time.Now().UnixNano(), 36)   // 每次请求唯一
    return map[string]string{
        "X-App-Key":   appKey,
        "X-Timestamp": ts,
        "X-Nonce":     nonce,
        "X-Sign":      calcSign(appSecret, buildSignString(method, path, params, ts, nonce)),
    }
}

// do 发起请求:GET 拼 query,POST 用表单编码
func do(method, path string, params map[string]string) (map[string]interface{}, error) {
    full := base + path
    var body io.Reader
    if strings.ToUpper(method) == "GET" {
        q := url.Values{}
        for k, v := range params {
            q.Set(k, v)
        }
        full += "?" + q.Encode()
    } else {
        form := url.Values{}
        for k, v := range params {
            form.Set(k, v)
        }
        body = strings.NewReader(form.Encode())
    }

    req, err := http.NewRequest(strings.ToUpper(method), full, body)
    if err != nil {
        return nil, err
    }
    if strings.ToUpper(method) == "POST" {
        req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    }
    for k, v := range signedHeaders(method, path, params) {
        req.Header.Set(k, v)
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    raw, _ := io.ReadAll(resp.Body)

    var out map[string]interface{}
    if err := json.Unmarshal(raw, &out); err != nil {
        return nil, err
    }
    return out, nil
}

func main() {
    // 1. 提交核验
    r, err := do("POST", "/api/open/v1/verify", map[string]string{
        "out_order_no": "ORDER_001",
        "type":         "face",
        "name":         "张三",
        "idcard":       "110101199003074258",
    })
    if err != nil {
        panic(err)
    }
    data := r["data"].(map[string]interface{})
    fmt.Println("核验跳转地址:", data["certify_url"])   // 引导用户打开完成人脸核验

    // 2. 查询结果(轮询)
    q, _ := do("GET", "/api/open/v1/query", map[string]string{"out_order_no": "ORDER_001"})
    fmt.Printf("核验结果: %v\n", q["data"].(map[string]interface{})["passed"])
}

查询余额示例

GET /api/open/v1/balance 无业务参数(仅需签名头),返回当前应用所属账户的可用人脸核验次数与余额。

# Python
r = requests.get(BASE + "/api/open/v1/balance",
                 headers=signed_headers("GET", "/api/open/v1/balance", {}))
print(r.json()["data"])
# {"app_key":"ak_xxx","face_remain":100,"balance_free":0,"balance_yuan":"0.00"}

响应格式

{
  "code": 200,
  "msg": "success",
  "data": {
    "order_no": "V20260919145429z83zLrrS",
    "out_order_no": "ORDER_001",
    "status": 0,
    "certify_url": "https://...",
    "qr_image": "data:image/png;base64,iVBORw0KGgo..."
  }
}

支付宝扫码认证(推荐):data.qr_image 是 certify_url 的二维码(PNG 的 data URI),可直接 <img src="data.qr_image"> 渲染。用户在 PC 端用手机支付宝「扫一扫」扫描即可进入人脸核验,无需跳转。若场景更适合跳转,仍可用 certify_url;qr_image 生成失败时该字段为空串,退回使用 certify_url。

查询结果响应

GET /api/open/v1/query?out_order_no=ORDER_001
{
  "code": 200,
  "msg": "success",
  "data": {
    "order_no": "V2026...",
    "out_order_no": "ORDER_001",
    "type": "face",
    "status": 1,
    "passed": true,
    "message": "核验通过",
    "create_time": "2026-10-06 11:56:40",
    "finish_time": "2026-10-06 12:01:12"
  }
}

查询参数:out_order_no(你的商户订单号)或 order_no(平台核验单号)二选一;两者都传时以 order_no 为准。

status 取值:0 处理中 / 1 通过 / 2 未通过。结果获取时效:查询接口每次被调用都会即时向支付宝核实并回写,用户完成后你下一次查询立即拿到终态;回调由平台结果同步任务驱动(见下节),用户完成后约 5 秒内自动推送,无需你先发起查询。两点注意:①「未通过」终态判定有 15 分钟宽限期——用户刚发起时上游返回的「未通过」只代表「尚未完成」,宽限期内 status 保持 0,防止误判;②核验单有效期有限,超期未完成上游将返回「认证已失效」,平台会把该单置为未通过并回调通知你。

核验结果回调

创建应用时可填写回调地址,也可在「用户中心 → 应用管理 → 设置回调」随时修改或清空。核验出结果后平台主动 POST 到该地址(Content-Type: application/json;用户完成核验后约 5 秒内推送,由平台同步任务驱动,无需你先轮询),失败按退避策略重试(最多 6 次)。你的服务应返回 2xx(建议响应正文为 success)。

未配置回调地址时,请通过 GET /api/open/v1/query 轮询获取结果(支付宝官方流程即为查询制:认证结果以服务端查询为准,无需向支付宝登记任何域名)。

POST {你的回调地址}
X-Sign: <签名>
X-Timestamp: 1789801118

{"order_no":"V2026...","out_order_no":"ORDER_001",
 "status":1,"passed":true,"message":"核验通过"}
字段类型说明
order_nostring平台核验单号
out_order_nostring你提交的商户订单号
typestring核验类型 face / two / three
statusint0 处理中 / 1 通过 / 2 未通过
passedbool是否通过(status==1)
messagestring结果说明(未通过时为原因)
finish_timestring核验完成时间
timestampint秒级时间戳(与 X-Timestamp 一致,用于验签)

回调验签(注意:签名输入是原始 body 字节,不是重新序列化的 JSON;时间戳与服务器相差超过 5 分钟应拒绝,防重放):

sign = HMAC-SHA256(app_secret, timestamp + "\n" + 原始body字节)

错误码

错误码说明
200成功
1001签名错误
1002时间戳过期或 nonce 重复(防重放)
1004余额不足
1005参数错误
1006频率超限
1007IP 不允许
1008重复订单号
403应用不存在或已停用(AppKey 无效)