You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

相同参数调用DocuSign createClickwrap返回404问题咨询

DocuSign调用createClickwrap方法返回404问题排查

问题现象

  • 调用createClickwrap方法时收到404 Not Found错误
  • DocuSign官方Quickstart生成的演示项目调用同方法可正常执行,无报错
  • 自研项目调用该方法必现上述错误
  • 调试对比两个项目传入方法的参数,包括accountId在内的所有入参完全一致

相关实现代码

docusign controller 层代码

const docuSignService = require('./docusign_esign_service');
const demoDocumentsPath = path.resolve(__dirname, '../demo_documents');
const { createClickwrap } = require('./createClickWrap');

async getDocusignRecieptService() {
    const authResponse = await docuSignService.authenticate();
    if (authResponse) {
      const docTermsPdf = 'Term_Of_Service.pdf';
      const docFile = path.resolve(demoDocumentsPath, docTermsPdf);
      const { basePath, accessToken, apiAccountId } = authResponse;
      const { clickwrapId } = await createClickwrap({ docFile, basePath, accessToken, accountId: apiAccountId });
      const res = await activateClickwrap({ clickwrapId, basePath, accessToken, accountId: apiAccountId });
          console.log({ res });
    }
 }

docuSignService.js 认证模块代码

const SCOPES = ['signature', 'impersonation', 'openid', 'click.manage', 'click.send'];
const fs = require('fs');

const docusign = require('docusign-esign');

class DocusingService {
  async authenticate() {
    const jwtLifeSec = 10 * 60, // 申请的JWT有效期为10分钟
      dsApi = new docusign.ApiClient();
    dsApi.setOAuthBasePath(process.env.dsOauthServer.replace('https://', '')); // 此处仅需传入域名
    let rsaKey = fs.readFileSync(process.env.privateKeyLocation);
    try {
      const results = await dsApi.requestJWTUserToken(
        process.env.dsJWTClientId,
        process.env.impersonatedUserGuid,
        SCOPES,
        rsaKey,
        jwtLifeSec
      );
      const accessToken = results.body.access_token;
      // 获取用户信息
      const userInfoResults = await dsApi.getUserInfo(accessToken);
      // 取默认账号信息
      let userInfo = userInfoResults.accounts.find((account) => account.isDefault === 'true');
      return {
        accessToken: results.body.access_token,
        apiAccountId: userInfo.accountId,
        basePath: `${userInfo.baseUri}/restapi`
      };
    } catch (e) {
      let body = e.response && e.response.body;
      // 判断错误来源
      if (body) {
        // 应用未获得用户授权
        if (body.error && body.error === 'consent_required') {
          if (this.getConsent()) {
            return this.authenticate();
          }
        } else {
          // 已获得授权,打印接口错误状态码和返回内容
          this._debug_log(`\nAPI problem: Status code ${e.response.status}, message body:
            ${JSON.stringify(body, null, 4)}\n\n`);
        }
      }
    }
  }

  getConsent() {
    var urlScopes = SCOPES.join('+');
    // 构造授权跳转URL
    var redirectUri = 'https://developers.docusign.com/platform/auth/consent';
    var consentUrl =
      `${process.env.dsOauthServer}/oauth/auth?response_type=code&` +
      `scope=${urlScopes}&client_id=${process.env.dsJWTClientId}&` +
      `redirect_uri=${redirectUri}`;
    throw new Error(`Open the following URL in your browser to grant consent to the application: ${consentUrl}`);
  }

  getArgs(apiAccountId, accessToken, basePath, signerEmail, signerName, id, agreementData, redirect_uri) {
    const envelopeArgs = {
      signerEmail: signerEmail,
      signerName: signerName,
      status: 'sent',
      signerClientId: id,
      dsReturnUrl: redirect_uri,
      agreement: agreementData
    };
    const args = {
      accessToken: accessToken,
      basePath: basePath,
      accountId: apiAccountId,
      envelopeArgs: envelopeArgs
    };
    return args;
  }
}

module.exports = new DocusingService();

createClickWrap.js 点击包装创建逻辑代码

const createClickwrap = async ({ docFile, clickwrapName = 'clickwrapName', basePath, accessToken, accountId }) => {
  // 构造请求体
  // 创建设置项模型
  const displaySettings = docusignClick.DisplaySettings.constructFromObject({
    consentButtonText: 'I Agree',
    displayName: 'Terms of Service',
    downloadable: true,
    format: 'modal',
    hasAccept: true,
    mustRead: true,
    requireAccept: true,
    documentDisplay: 'document'
  });

  // 创建文档模型
  // 读取文件并做base64编码,存入文档实体,文件不存在时会抛出异常
  const documentPdfExample = fs.readFileSync(docFile);
  const encodedExampleDocument = Buffer.from(documentPdfExample).toString('base64');
  const document = docusignClick.Document.constructFromObject({
    documentBase64: encodedExampleDocument,
    documentName: 'Terms of Service',
    fileExtension: 'pdf',
    order: 0
  });

  // 创建clickwrap请求模型
  const clickwrapRequest = docusignClick.ClickwrapRequest.constructFromObject({
    displaySettings,
    documents: [document],
    name: clickwrapName,
    requireReacceptance: true
  });

  // 初始化Click API客户端
  const dsApiClient = new docusignClick.ApiClient();
  dsApiClient.setBasePath(basePath);
  dsApiClient.addDefaultHeader('Authorization', 'Bearer ' + accessToken);
  const accountApi = new docusignClick.AccountsApi(dsApiClient);

  // 发起创建clickwrap请求
  let result = null;
  try {
    result = await accountApi.createClickwrap(accountId, {
      clickwrapRequest
    });
  } catch (e) {
    debugger;
    console.log(e);
  }
  debugger;
  console.log(`Clickwrap was created. ClickwrapId ${result.clickwrapId}`);
  return result;
};

module.exports = { createClickwrap };

参数对比截图

  • 正常运行的演示项目入参:
    演示应用参数截图
  • 自研项目入参:
    自有应用参数截图

完整报错堆栈

"Error: Not Found
    at Request.callback (/Users/and/test/node_modules/docusign-click/node_modules/superagent/lib/node/index.js:696:15)
    at IncomingMessage.<anonymous> (/Users/and/test/node_modules/docusign-click/node_modules/superagent/lib/node/index.js:906:18)
    at IncomingMessage.emit (node:events:539:35)
    at IncomingMessage.emit (node:domain:475:12)
    at endReadableNT (node:internal/streams/readable:1345:12)
    at processTicksAndRejections (node:internal/process/task_queues:83:21)"

问题根因与修复方案

核心问题是Click API的basePath配置错误。
当前认证逻辑中拼接的${userInfo.baseUri}/restapi是DocuSign eSignature(电子签名)API专用的路径前缀,Click API不使用/restapi后缀,直接使用userInfo.baseUri作为根路径即可。
你当前把eSignature专用的basePath传给了Click API客户端,最终请求会被拼成错误路径:{baseUri}/restapi/clickapi/v1/accounts/...,而Click API的正确路径格式为{baseUri}/clickapi/v1/accounts/...,多余的/restapi路径段会导致服务端无法匹配对应路由,直接返回404 Not Found。这也是为什么参数看起来完全一致但调用失败——实际请求的接口地址根本不对。
官方Quickstart项目内部会对不同API模块使用对应的basePath:eSignature接口用带/restapi的路径,Click、Monitor等其他API直接使用根baseUri,自研项目里复用了eSignature的basePath给Click模块,才会触发这个问题。
修复方式:给Click API客户端设置basePath时去掉/restapi后缀即可,示例修改如下:

// createClickWrap.js 中修改setBasePath逻辑
dsApiClient.setBasePath(basePath.replace('/restapi', ''));

修改后请求路径会匹配Click API的路由规则,404错误即可解决。


内容的提问来源于stack exchange,提问作者Matt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 09:30:48