Skip to content

e签宝电子签约对接文档

一、e签宝简介

1.1 服务商信息

1.2 产品优势

  • 市场占有率最高,法律效力最强
  • 个人开发者支持完善
  • 提供测试环境
  • API文档详细
  • 技术支持响应快

二、接入准备

2.1 注册账号

  1. 访问 https://open.esign.cn/
  2. 注册开发者账号
  3. 完成实名认证(企业认证,需营业执照)
  4. 创建应用

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

最后更新于:

基于 MIT 许可发布