e签宝电子签约对接文档
一、e签宝简介
1.1 服务商信息
- 官网: https://www.esign.cn/
- 开放平台: https://open.esign.cn/
- 开发文档: https://open.esign.cn/doc/opendoc
- 客服电话: 400-8816-365
1.2 产品优势
- 市场占有率最高,法律效力最强
- 个人开发者支持完善
- 提供测试环境
- API文档详细
- 技术支持响应快
二、接入准备
2.1 注册账号
- 访问 https://open.esign.cn/
- 注册开发者账号
- 完成实名认证(企业认证,需营业执照)
- 创建应用
2.2 获取密钥
进入开放平台控制台:
- App ID: 应用唯一标识
- App Secret: 应用密钥
2.3 环境说明
测试环境
- API地址:
https://smlopenapi.esign.cn - 免费使用
- 仅用于开发测试
生产环境
- API地址:
https://openapi.esign.cn - 按次收费
- 需充值后使用
三、核心API
3.1 鉴权方式
Access Token获取
接口: POST /v3/access-token
请求头:
Content-Type: application/json
X-Tsign-Open-App-Id: {app_id}
X-Tsign-Open-App-Secret: {app_secret}响应:
json
{
"code": 0,
"message": "成功",
"data": {
"token": "xxx",
"expiresIn": 7200 // 有效期(秒)
}
}说明: Access Token有效期2小时,需缓存使用
3.2 个人账号创建
按三方用户ID创建
接口: POST /v3/persons/create-by-thirdparty-id
请求头:
Content-Type: application/json
X-Tsign-Open-Auth-Mode: Signature
X-Tsign-Open-App-Id: {app_id}
X-Tsign-Open-Ca-Signature: {signature}
X-Tsign-Open-Ca-Timestamp: {timestamp}
Authorization: Bearer {access_token}请求参数:
json
{
"thirdPartyUserId": "user_123", // 我方用户ID
"name": "张三", // 真实姓名
"idType": "CRED_PSN_CH_IDCARD", // 证件类型(身份证)
"idNumber": "110101199001011234", // 证件号码
"mobile": "13800138000", // 手机号
"email": "zhangsan@example.com" // 邮箱(可选)
}响应:
json
{
"code": 0,
"message": "成功",
"data": {
"accountId": "xxx" // e签宝账号ID
}
}说明:
- 需保存accountId到我方数据库(users.esign_account_id)
- 同一个thirdPartyUserId只能创建一次
3.3 创建签署流程
文件模板签署流程
接口: POST /v3/sign-flow/create-by-file-template
请求参数:
json
{
"businessScene": "租房合同", // 业务场景
"docs": [
{
"fileTemplateId": "xxx", // 文件模板ID
"fileName": "租房合同.pdf",
"formFields": [ // 填充模板变量
{
"posKey": "landlord_name",
"value": "张三"
},
{
"posKey": "tenant_name",
"value": "李四"
},
{
"posKey": "property_address",
"value": "青岛市市北区..."
},
{
"posKey": "rent_price",
"value": "3500"
}
]
}
],
"signFlowConfig": {
"signPlatform": "2", // 签署平台: 1-PC端, 2-移动端
"autoFinish": true, // 自动完成
"noticeDeveloperUrl": "https://yourdomain.com/api/contracts/callback"
}
}响应:
json
{
"code": 0,
"message": "成功",
"data": {
"signFlowId": "xxx" // 签署流程ID
}
}3.4 添加签署方
接口: POST /v3/sign-flow/add-signatory
请求参数:
json
{
"signFlowId": "xxx", // 签署流程ID
"signatories": [
{
"accountId": "xxx", // 签署人e签宝账号ID
"signOrder": 1, // 签署顺序
"signType": 0 // 签署类型: 0-单页签署
},
{
"accountId": "yyy",
"signOrder": 2,
"signType": 0
},
{
"accountId": "zzz",
"signOrder": 3,
"signType": 0
}
]
}响应:
json
{
"code": 0,
"message": "成功"
}3.5 获取签署链接
接口: GET /v3/sign-flow/sign-url
请求参数:
?signFlowId=xxx
&accountId=yyy
&organizeId= // 企业ID(个人签署为空)响应:
json
{
"code": 0,
"message": "成功",
"data": {
"shortUrl": "https://h5sign.esign.cn/sign?token=xxx"
}
}说明: 签署链接有效期30分钟
3.6 查询签署流程状态
接口: GET /v3/sign-flow/detail
请求参数:
?signFlowId=xxx响应:
json
{
"code": 0,
"message": "成功",
"data": {
"signFlowId": "xxx",
"businessScene": "租房合同",
"signFlowStatus": 2, // 流程状态: 0-草稿, 1-签署中, 2-完成, 3-撤销
"docs": [
{
"fileId": "xxx",
"fileName": "租房合同.pdf"
}
],
"signatories": [
{
"accountId": "xxx",
"signStatus": 2, // 签署状态: 0-待签, 1-签署中, 2-已签, 3-拒签
"signTime": "2025-11-01 10:00:00"
}
]
}
}3.7 下载已签署文件
接口: GET /v3/sign-flow/download-url
请求参数:
?signFlowId=xxx
&fileId=yyy响应:
json
{
"code": 0,
"message": "成功",
"data": {
"url": "https://esign-oss.oss-cn-hangzhou.aliyuncs.com/xxx.pdf?Expires=xxx"
}
}说明: 下载链接有效期30分钟
3.8 签署回调通知
回调地址: 创建签署流程时配置的noticeDeveloperUrl
回调内容:
json
{
"action": "SIGN_FLOW_FINISH", // 事件类型
"signFlowId": "xxx",
"accountId": "yyy", // 操作人账号ID
"signResult": "success", // 签署结果: success/fail
"timestamp": 1698566400000
}响应要求: 返回HTTP 200即可
四、完整签署流程示例
4.1 流程图
1. 用户发起签署请求
↓
2. 检查是否有e签宝账号
- 没有 → 创建e签宝账号
- 有 → 跳过
↓
3. 创建签署流程(使用合同模板)
↓
4. 添加三个签署方
- 房东
- 租客
- 经纪人
↓
5. 获取各自签署链接
↓
6. 用户点击链接进入H5页面签署
↓
7. e签宝回调通知签署完成
↓
8. 查询流程状态
- 全部签署完成 → 下载PDF
↓
9. 保存PDF到服务器4.2 代码示例
ContractService.php
php
namespace app\service;
use think\facade\Cache;
use think\facade\Http;
class ContractService
{
private $appId;
private $appSecret;
private $apiUrl;
public function __construct()
{
$this->appId = config('esign.app_id');
$this->appSecret = config('esign.secret');
$this->apiUrl = config('esign.api_url');
}
/**
* 获取Access Token
*/
public function getAccessToken()
{
// 从缓存读取
$cacheKey = 'esign_access_token';
$token = Cache::get($cacheKey);
if ($token) {
return $token;
}
// 请求新Token
$response = Http::withHeaders([
'Content-Type' => 'application/json',
'X-Tsign-Open-App-Id' => $this->appId,
'X-Tsign-Open-App-Secret' => $this->appSecret
])->post($this->apiUrl . '/v3/access-token');
$result = $response->json();
if ($result['code'] === 0) {
$token = $result['data']['token'];
// 缓存1小时50分钟(提前10分钟过期)
Cache::set($cacheKey, $token, 6600);
return $token;
}
throw new \Exception('获取Access Token失败: ' . $result['message']);
}
/**
* 创建个人账号
*/
public function createPersonAccount($user)
{
$token = $this->getAccessToken();
$data = [
'thirdPartyUserId' => 'user_' . $user->id,
'name' => $user->real_name,
'idType' => 'CRED_PSN_CH_IDCARD',
'idNumber' => $user->id_card,
'mobile' => $user->phone
];
$timestamp = time() * 1000;
$signature = $this->generateSignature($data, $timestamp);
$response = Http::withHeaders([
'Content-Type' => 'application/json',
'X-Tsign-Open-Auth-Mode' => 'Signature',
'X-Tsign-Open-App-Id' => $this->appId,
'X-Tsign-Open-Ca-Signature' => $signature,
'X-Tsign-Open-Ca-Timestamp' => $timestamp,
'Authorization' => 'Bearer ' . $token
])->post($this->apiUrl . '/v3/persons/create-by-thirdparty-id', $data);
$result = $response->json();
if ($result['code'] === 0) {
return $result['data']['accountId'];
}
throw new \Exception('创建e签宝账号失败: ' . $result['message']);
}
/**
* 创建签署流程
*/
public function createSignFlow($contract, $template)
{
$token = $this->getAccessToken();
// 填充合同变量
$formFields = $this->buildFormFields($contract);
$data = [
'businessScene' => $contract->contract_type === 'rent' ? '租房合同' : '售房合同',
'docs' => [
[
'fileTemplateId' => $template->esign_template_id,
'fileName' => $contract->contract_no . '.pdf',
'formFields' => $formFields
]
],
'signFlowConfig' => [
'signPlatform' => '2',
'autoFinish' => true,
'noticeDeveloperUrl' => url('/api/contracts/callback', [], true, true)
]
];
$response = Http::withToken($token)
->post($this->apiUrl . '/v3/sign-flow/create-by-file-template', $data);
$result = $response->json();
if ($result['code'] === 0) {
return $result['data']['signFlowId'];
}
throw new \Exception('创建签署流程失败: ' . $result['message']);
}
/**
* 添加签署方
*/
public function addSignatories($signFlowId, $accounts)
{
$token = $this->getAccessToken();
$signatories = [];
$order = 1;
foreach ($accounts as $accountId) {
$signatories[] = [
'accountId' => $accountId,
'signOrder' => $order++,
'signType' => 0
];
}
$data = [
'signFlowId' => $signFlowId,
'signatories' => $signatories
];
$response = Http::withToken($token)
->post($this->apiUrl . '/v3/sign-flow/add-signatory', $data);
$result = $response->json();
if ($result['code'] !== 0) {
throw new \Exception('添加签署方失败: ' . $result['message']);
}
}
/**
* 获取签署链接
*/
public function getSignUrl($signFlowId, $accountId)
{
$token = $this->getAccessToken();
$response = Http::withToken($token)
->get($this->apiUrl . '/v3/sign-flow/sign-url', [
'signFlowId' => $signFlowId,
'accountId' => $accountId,
'organizeId' => ''
]);
$result = $response->json();
if ($result['code'] === 0) {
return $result['data']['shortUrl'];
}
throw new \Exception('获取签署链接失败: ' . $result['message']);
}
/**
* 下载已签署文件
*/
public function downloadSignedFile($signFlowId, $fileId)
{
$token = $this->getAccessToken();
// 获取下载链接
$response = Http::withToken($token)
->get($this->apiUrl . '/v3/sign-flow/download-url', [
'signFlowId' => $signFlowId,
'fileId' => $fileId
]);
$result = $response->json();
if ($result['code'] !== 0) {
throw new \Exception('获取下载链接失败: ' . $result['message']);
}
$downloadUrl = $result['data']['url'];
// 下载文件
$fileContent = Http::get($downloadUrl)->body();
// 保存到本地
$fileName = date('YmdHis') . '_' . $signFlowId . '.pdf';
$filePath = '/uploads/contracts/' . date('Y/m/');
$fullPath = public_path() . $filePath;
if (!is_dir($fullPath)) {
mkdir($fullPath, 0755, true);
}
file_put_contents($fullPath . $fileName, $fileContent);
return $filePath . $fileName;
}
/**
* 构建表单字段
*/
private function buildFormFields($contract)
{
return [
['posKey' => 'landlord_name', 'value' => $contract->landlord->real_name],
['posKey' => 'landlord_phone', 'value' => $contract->landlord->phone],
['posKey' => 'landlord_id_card', 'value' => $contract->landlord->id_card],
['posKey' => 'tenant_name', 'value' => $contract->tenant->real_name],
['posKey' => 'tenant_phone', 'value' => $contract->tenant->phone],
['posKey' => 'tenant_id_card', 'value' => $contract->tenant->id_card],
['posKey' => 'agent_name', 'value' => $contract->agent->real_name],
['posKey' => 'agent_phone', 'value' => $contract->agent->phone],
['posKey' => 'property_address', 'value' => $contract->property->full_address],
['posKey' => 'property_area', 'value' => $contract->property->area],
['posKey' => 'rent_price', 'value' => $contract->property->price],
['posKey' => 'sign_date', 'value' => date('Y年m月d日')]
];
}
/**
* 生成签名
*/
private function generateSignature($data, $timestamp)
{
$contentMd5 = base64_encode(md5(json_encode($data), true));
$stringToSign = "POST\napplication/json\n{$contentMd5}\n\n{$timestamp}";
return base64_encode(hash_hmac('sha256', $stringToSign, $this->appSecret, true));
}
}五、费用说明
5.1 测试环境
- 费用: 免费
- 限制: 仅用于开发测试,签署的合同无法律效力
5.2 生产环境
计费方式
- 实名签署: 约1-3元/次(具体价格咨询官方)
- 合同存证: 约0.5元/份
- 短信通知: 约0.1元/条
套餐价格(参考)
| 套餐 | 签署次数 | 价格 | 单价 |
|---|---|---|---|
| 基础版 | 100次 | 150元 | 1.5元/次 |
| 标准版 | 500次 | 600元 | 1.2元/次 |
| 高级版 | 2000次 | 2000元 | 1元/次 |
5.3 其他费用
- 实名认证: 个人0.5元/次,企业2元/次
- 存储费: 免费
- 带宽费: 免费
六、常见问题
6.1 签署链接过期怎么办?
- 签署链接有效期30分钟
- 过期后需重新调用
获取签署链接接口
6.2 用户拒签怎么处理?
- e签宝会回调通知
signResult: fail - 更新合同状态为"已拒签"
- 需重新发起签署流程
6.3 如何撤销签署流程?
- 调用
POST /v3/sign-flow/revoke接口 - 仅限流程未完成时可撤销
6.4 合同有效期多久?
- e签宝默认保存5年
- 可通过API永久存储
6.5 如何验证签署真伪?
- 每份合同都有唯一的signFlowId
- 可通过e签宝官网验证
七、对接检查清单
7.1 开发阶段
- [ ] 注册e签宝开发者账号
- [ ] 完成企业实名认证
- [ ] 获取App ID和Secret
- [ ] 配置测试环境
- [ ] 测试个人账号创建
- [ ] 测试签署流程
- [ ] 配置回调地址
- [ ] 测试回调接收
7.2 上线前
- [ ] 切换到生产环境
- [ ] 充值账户余额
- [ ] 配置生产回调地址
- [ ] 进行完整流程测试
- [ ] 准备合同模板
- [ ] 培训客服人员
7.3 上线后
- [ ] 监控签署成功率
- [ ] 监控回调失败率
- [ ] 定期检查账户余额
- [ ] 收集用户反馈
文档版本:v1.0
更新日期:2025年11月3日
官方文档:https://open.esign.cn/doc/opendoc