1. 开发前必读
签盾开放平台-电子签约服务
  • 接入流程介绍
  • 应用创建流程
  • 前端接入文档
  • 开发前必读
    • 常用接口清单和关键流程
    • 公共请求格式
  • 帮助文档
    • 印章指定位置
    • 静默签使用说明
  • 实名认证
    • 个人认证
      • 个人认证项
        • 个人身份二要素核验
        • 个人运营商三要素核验
        • 个人人脸核验(H5适配)
        • 个人人脸核验查询
        • 发送短信验证码
        • 校验短信验证码
        • 个人银行卡四要素核验
      • 个人实名认证地址(页面)
    • 企业认证
      • 企业认证项
        • 企业四要素核验
        • 企业三要素核验
        • 发起打款认证(正向打款)
        • 验证打款金额(正向打款)
        • 查询打款进度(正向打款)
        • 企业经办人授权书认证-上传
        • 企业经办人授权书认证-查询
        • 查询打款银行信息
      • 企业实名认证地址(页面)
    • 查询认证信息
      GET
  • 用户管理
    • 个人用户
      • 修改个人用户
      • 创建已认证个人用户
      • 查询个人用户信息
    • 企业用户
      • 查询企业用户信息
      • 创建已认证企业用户
    • 创建未认证用户(暂不支持企业)
      POST
    • 创建未认证用户
      POST
  • 文件相关
    • 单文件 - 上传文件
      POST
    • 单文件 - 基于模板生成文件
      POST
    • 获取拖章定位
      GET
    • 获取拖章定位页面
      POST
    • 多文件 - 基于模板生成文件
      POST
    • 多文件 - 上传文件
      POST
    • 复制模版
      POST
  • 合同相关
    • 通过文件发起合同
      POST
    • 查询签署方信息
      GET
    • 下载合同文件及附件
      GET
    • 合同查询
      GET
    • 获取合同签署链接(页面)
      POST
    • 无感签署(申请开放)
      POST
    • 合同撤回
      POST
  • 印章管理
    • 查询印章列表
    • 查询印章详情
    • 印章静默签授权地址(H5页面)
  • 接口回调
    • 回调接入文档
    • 获取回调信息
    • 回调成功确认
  • 计费相关
    • 获取账户积分余额
  • 开放接口
    • 短信验证码
    • 通知短信发送
    • 身份证OCR
    • 营业执照OCR
    • 银行卡OCR
  1. 开发前必读

公共请求格式

请求域名#

测试环境域名:#

api.pre-qiandun365.com

生产环境域名:#

api.qiandun365.com
提示
测试环境下的接口均属于沙箱模拟环境,此环境仅用于开发调试,所签合同文件及认证等操作均不具备法律效力。正式上线使用时请务必切换到正式生产环境调用相关接口。
沙箱模拟环境中所创建的数据不可以直接使用到正式生产环境中,正式上线使用时请注意重新创建相关数据。
📌
本页末尾附JavaDemo,其他语言请联系客服获取

#

API调用#

公共入参#

公共请求参数是指每个接口都需要使用到的请求参数。
参数名称位置必须描述
X-Ca-KeyHeader是Appkey,调用API的身份标识,可以到签盾企业控制台申请
X-Ca-SignatureHeader是通过签名计算规则计算的请求签名串,参照:签名计算规则
X-Ca-TimestampHeader否API 调用者传递时间戳,值为当前时间的毫秒数,也就是从1970年1月1日起至今的时间转换为毫秒,时间戳有效时间为15分钟
X-Ca-NonceHeader否API请求的唯一标识符,15分钟内同一X-Ca-Nonce不能重复使用,建议使用 UUID,结合时间戳防重放
Content-MD5Header否当请求 Body 非 Form 表单时,需要计算 Body 的 MD5 值传递给云网关进行 Body MD5 校验
X-Ca-Signature-HeadersHeader否指定哪些Header参与签名,支持多值以","分割,默认只有X-Ca-Key参与签名,为安全需要也请将X-Ca-Timestamp、X-Ca-Nonce进行签名,例如:X-Ca-Signature-Headers:X-Ca-Key,X-Ca-Nonce,X-Ca-Timestamp

签名计算规则#

请求签名,是基于请求内容计算的数字签名,用于API识别用户身份。客户端调用API时,需要在请求中添加计算的签名(X-Ca-Signature)。

签名计算流程#


准备APPkey → 构造待签名字符串stringToSign → 使用Secret计算签名

1.准备APPKey#
Appkey,调用API的身份标识,可以到签盾申请
2.构造待签名字符串stringToSign#
HTTPMethod#
为全大写,如 POST。#

Accept、Content-MD5、Content-Type、Date 如果为空也需要添加换行符”\n”,Headers如果为空不需要添加”\n”。
Content-MD5#
Content-MD5 是指 Body 的 MD5 值,只有当 Body 非 Form 表单时才计算 MD5,计算方式为:
String content-MD5 = Base64.encodeBase64(MD5(bodyStream.getbytes("UTF-8"))); bodyStream 为字节数组。
Headers#
Headers 是指参与 Headers 签名计算的 Header 的 Key、Value 拼接的字符串,建议对 X-Ca 开头以及自定义 Header 计算签名,注意如下参数不参与 Headers 签名计算:X-Ca-Signature、X-Ca-Signature-Headers、Accept、Content-MD5、Content-Type、Date。
Headers 组织方法:#
先对参与 Headers 签名计算的 Header的Key 按照字典排序后使用如下方式拼接,如果某个 Header 的 Value 为空,则使用 HeaderKey + “:” + “\n”参与签名,需要保留 Key 和英文冒号。

String headers =

HeaderKey1 + ":" + HeaderValue1 + "\n"\+

HeaderKey2 + ":" + HeaderValue2 + "\n"\+

...

HeaderKeyN + ":" + HeaderValueN + "\n"
将 Headers 签名中 Header 的 Key 使用英文逗号分割放到 Request 的 Header 中,Key为:X-Ca-Signature-Headers。
Url#
Url 指 Path + Query + Body 中 Form 参数,组织方法:对 Query+Form 参数按照字典对 Key 进行排序后按照如下方法拼接,如果 Query 或 Form 参数为空,则 Url = Path,不需要添加 ?,如果某个参数的 Value 为空只保留 Key 参与签名,等号不需要再加入签名。

String url =

Path +

"?" +

Key1 + "=" + Value1 +

"&" + Key2 + "=" + Value2 +

...

"&" + KeyN + "=" + ValueN
注意这里 Query 或 Form 参数的 Value 可能有多个,多个的时候只取第一个 Value 参与签名计算。
3.使用Secret计算签名#

Mac hmacSha256 = Mac.getInstance("HmacSHA256");

byte[] keyBytes = secret.getBytes("UTF-8");

hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, "HmacSHA256"));

String sign = new String(Base64.encodeBase64(Sha256.doFinal(stringToSign.getBytes("UTF-8")),"UTF-8"));
Secret 为 APP 的密钥,请在应用管理中获取。

签名两种方式
1 参考示例代码
2 调用sdk(联系客服获取demo实例 立即接入)
java 示例代码
package com.qiandun.openapi.test;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.qiandun.openapi.demo.utils.JsonUtils;
import okhttp3.*;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.stream.Collectors;


/**
 * @BelongsProject: saas-manage
 * @BelongsPackage: com.qiandun.generator
 * @Author: SQQ
 * @CreateTime: 2026/1/2312:47
 * @Description: TODO
 * @Version: 1.0
 */
public class Test {
    // 签盾APP凭证 - 需替换为你的实际值
    private static final String APP_KEY = "签盾官网开发平台申请";
    private static final String APP_SECRET = "在应用管理中获取";

//    private static final String APP_KEY = "";
//
//    private static final String APP_SECRET = "";

    // 默认算法
    private static final String SIGNATURE_METHOD = "HmacSHA256";

    public static void main(String[] args) {
        try {
            // 示例:调用一个查询接口  pre 测试环境
            String url = "https://api.pre-qiandun365.com/api/open/psnCert/2element";
            
            //生产环境
           // String url = "https://api.qiandun365.com/api/open/psnCert/2element";

//            // GET请求示例
//            String getResult = sendGetRequest(url);
//            System.out.println("GET响应结果: " + getResult);


            // POST请求示例(JSON格式)
            Map<String, Object> postData = new HashMap<>();
            postData.put("psnName", "姓名");
            postData.put("psnIDCardNum", "身份证号");

            String postResult = sendPostRequest(url, postData);
            System.out.println("POST响应结果: " + JsonUtils.getMapper().readTree(postResult).get("result"));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送GET请求
     */
    public static String sendGetRequest(String apiUrl) throws Exception {
        return sendRequest(apiUrl, "GET", null, null);
    }

    /**
     * 发送POST请求(JSON格式)
     */
    public static String sendPostRequest(String apiUrl, Map<String, Object> bodyParams) throws Exception {
        // 设置请求头
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json; charset=utf-8");
        headers.put("Accept", "application/json");

        // 将请求体转换为JSON字符串
        String bodyJson = mapToJson(bodyParams);

        return sendRequest(apiUrl, "POST", bodyJson, headers);
    }

    /**
     * 通用请求方法
     */
    public static String sendRequest(String apiUrl, String httpMethod,
                                     String requestBody, Map<String, String> customHeaders) throws Exception {

        // 1. 准备基础请求头
        Map<String, String> allHeaders = new HashMap<>();

        // 必需头信息
        String timestamp = String.valueOf(System.currentTimeMillis());
        String nonce = UUID.randomUUID().toString();

//        allHeaders.put("Date", getGMTTime());
        allHeaders.put("Accept", "application/json");
        allHeaders.put("X-Ca-Key", APP_KEY);
        allHeaders.put("X-Ca-Timestamp", timestamp);
        allHeaders.put("X-Ca-Nonce", nonce);
        allHeaders.put("X-Ca-Signature-Method", SIGNATURE_METHOD);

        // 添加自定义头
        if (customHeaders != null) {
            allHeaders.putAll(customHeaders);
        }

        // 2. 计算Content-MD5(如果有请求体)
        String contentMD5 = "";
        if (requestBody != null && !requestBody.isEmpty()) {
            contentMD5 = calculateContentMD5(requestBody);
            allHeaders.put("Content-MD5", contentMD5);
        }

        // 3. 解析URL,获取路径和查询参数
        URL url = new URL(apiUrl);
        String path = url.getPath();
        String query = url.getQuery();

        // 4. 构建参数映射(查询参数 + 表单参数)
        Map<String, String> paramMap = new HashMap<>();

        // 解析查询参数
        if (query != null && !query.isEmpty()) {
            String[] pairs = query.split("&");
            for (String pair : pairs) {
                String[] kv = pair.split("=");
                if (kv.length == 2) {
                    paramMap.put(kv[0], kv[1]);
                } else if (kv.length == 1) {
                    paramMap.put(kv[0], "");
                }
            }
        }

        // 5. 构造签名串
        String stringToSign = buildStringToSign(httpMethod, allHeaders, path, paramMap);
//        System.out.println("待签名字符串:\n" + stringToSign);

        // 6. 计算签名
        String signature = calculateSignature(stringToSign, APP_SECRET);
        allHeaders.put("X-Ca-Signature", signature);

        // 7. 设置参与签名的Header列表
        List<String> signedHeaders = allHeaders.keySet().stream()
                .filter(key -> key.startsWith("X-Ca-"))
                .sorted()
                .collect(Collectors.toList());
        signedHeaders.remove("X-Ca-Signature");
        if (!signedHeaders.isEmpty()) {
            String signatureHeaders = String.join(",", signedHeaders);
            allHeaders.put("X-Ca-Signature-Headers", signatureHeaders);
        }

        // 8. 发送HTTP请求
        return executeHttpRequest(apiUrl, httpMethod, requestBody, allHeaders);
    }

    /**
     * 构造待签名字符串
     */
    private static String buildStringToSign(String httpMethod, Map<String, String> headers,
                                            String path, Map<String, String> paramMap) {

        StringBuilder sb = new StringBuilder();

        // 1. HTTP方法
        sb.append(httpMethod.toUpperCase()).append("\n");

        // 2. Accept头
        sb.append(headers.getOrDefault("Accept", "")).append("\n");

//         3. Content-MD5
        sb.append(headers.getOrDefault("Content-MD5", "")).append("\n");

        // 4. Content-Type
        sb.append(headers.getOrDefault("Content-Type", "")).append("\n");

        // 5. Date头
        sb.append(headers.getOrDefault("Date", "")).append("\n");

        // 6. Headers部分(以X-Ca-开头的自定义头)
        List<String> headerKeys = headers.keySet().stream()
                .filter(key -> key.startsWith("X-Ca-"))
                .filter(key -> !"X-Ca-Signature".equals(key))
                .filter(key -> !"X-Ca-Signature-Headers".equals(key))
                .sorted()
                .collect(Collectors.toList());

        for (String key : headerKeys) {
            sb.append(key).append(":").append(headers.get(key)).append("\n");
        }

        // 7. PathAndParameters
        sb.append(buildPathAndParameters(path, paramMap));

        return sb.toString();
    }

    /**
     * 构建PathAndParameters部分
     */
    private static String buildPathAndParameters(String path, Map<String, String> paramMap) {
        StringBuilder sb = new StringBuilder();
        sb.append(path);

        if (!paramMap.isEmpty()) {
            // 对参数Key进行字典序排序
            List<String> sortedKeys = new ArrayList<>(paramMap.keySet());
            Collections.sort(sortedKeys);

            sb.append("?");
            for (int i = 0; i < sortedKeys.size(); i++) {
                String key = sortedKeys.get(i);
                String value = paramMap.get(key);

                if (i > 0) {
                    sb.append("&");
                }

                sb.append(key);
                if (value != null && !value.isEmpty()) {
                    sb.append("=").append(value);
                }
            }
        }

        return sb.toString();
    }

    /**
     * 计算签名
     */
    private static String calculateSignature(String stringToSign, String appSecret) throws Exception {
        Mac mac = Mac.getInstance(SIGNATURE_METHOD);
        byte[] secretBytes = appSecret.getBytes(StandardCharsets.UTF_8);
        SecretKeySpec secretKey = new SecretKeySpec(secretBytes, SIGNATURE_METHOD);
        mac.init(secretKey);

        byte[] signBytes = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
        return Base64.encodeBase64String(signBytes);
    }

    /**
     * 计算Content-MD5
     */
    private static String calculateContentMD5(String content) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] digest = md.digest(content.getBytes(StandardCharsets.UTF_8));
        return Base64.encodeBase64String(digest);
    }

    /**
     * 获取GMT格式时间
     */
    private static String getGMTTime() {
        // 简化实现,实际使用时建议使用更精确的GMT时间格式
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
        return String.format("%1$tA, %1$td %1$tb %1$tY %1$tT GMT", calendar);
    }

    /**
     * 执行HTTP请求
     */
    private static String executeHttpRequest(String urlStr, String method,
                                             String body, Map<String, String> headers) throws Exception {

        HttpURLConnection conn = null;
        try {
            URL url = new URL(urlStr);
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod(method);
            conn.setConnectTimeout(15000);
            conn.setReadTimeout(30000);

            // 设置请求头
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                conn.setRequestProperty(entry.getKey(), entry.getValue());
            }

            // 发送请求体(POST/PUT等)
            if (body != null && !body.isEmpty() &&
                    ("POST".equals(method) || "PUT".equals(method) || "PATCH".equals(method))) {
                conn.setDoOutput(true);
                try (OutputStream os = conn.getOutputStream()) {
                    byte[] input = body.getBytes(StandardCharsets.UTF_8);
                    os.write(input, 0, input.length);
                }
            }

            // 获取响应
            int responseCode = conn.getResponseCode();
//            System.out.println("响应码: " + responseCode);

            // 读取响应头(可用于调试)
            Map<String, List<String>> responseHeaders = conn.getHeaderFields();
            for (Map.Entry<String, List<String>> entry : responseHeaders.entrySet()) {
                if (entry.getKey() != null) {
//                    System.out.println(entry.getKey() + ": " + entry.getValue());
                }
            }

            // 读取响应体
            StringBuilder response = new StringBuilder();
            try (BufferedReader br = new BufferedReader(
                    new InputStreamReader(
                            (responseCode >= 200 && responseCode < 300) ?
                                    conn.getInputStream() : conn.getErrorStream(),
                            StandardCharsets.UTF_8))) {

                String line;
                while ((line = br.readLine()) != null) {
                    response.append(line);
                }
            }

            return response.toString();

        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
    }

    /**
     * 将Map转换为JSON字符串(简单实现)
     */
    private static String mapToJson(Map<String, Object> map) {
        if (map == null || map.isEmpty()) {
            return "{}";
        }

        StringBuilder sb = new StringBuilder();
        sb.append("{");

        List<String> entries = map.entrySet().stream()
                .map(entry -> {
                    String key = "\"" + entry.getKey() + "\"";
                    Object value = entry.getValue();

                    if (value instanceof String) {
                        return key + ":\"" + value + "\"";
                    } else if (value instanceof Number || value instanceof Boolean) {
                        return key + ":" + value;
                    } else {
                        return key + ":\"" + value.toString() + "\"";
                    }
                })
                .collect(Collectors.toList());

        sb.append(String.join(",", entries));
        sb.append("}");

        return sb.toString();
    }
}

公共相应参数格式#

参数名称参数类型必选参数说明
codestring是业务码,200表示成功
msgstring是业务信息
successboolean是请求是否成功true/false 同code=200
resultobject否业务数据
注意
success=false时代表业务失败或异常,开发者可使用业务码code 进行业务逻辑判断。
接口调用出现报错时,开发者可通过常见错误码查找错误排查方法。
修改于 2026-04-08 06:52:07
上一页
常用接口清单和关键流程
下一页
印章指定位置
Built with