PAYATHON 2026

Tableau 与 DocuSign Integration 的 OAuth Token 认证

支付老李

结论

不建议把 DocuSign OAuth Token 直接保存在 Tableau 工作簿中。更稳妥的做法是增加中间数据服务或定时 ETL 任务:

  1. 后台任务通过 DocuSign OAuth 获取 Access Token,并在需要时自动更新。
  2. 调用 /oauth/userinfo,查询当前用户有权访问的 DocuSign Accounts。
  3. 根据各账户返回的 account_id 和 base_uri,分别调用 Billing API。
  4. 整理结果并写入数据库、数据仓库或 Tableau 可以读取的数据文件。
  5. Tableau 连接处理后的数据源,并按计划刷新。

如果必须让 Tableau 直接访问 DocuSign API,就需要开发 Tableau Web Data Connector 或自定义连接器,并在连接器中实现完整的 OAuth 流程。只在 Tableau 中填写 Access Token 通常无法长期运行,因为 Token 会过期。

推荐架构

DocuSign OAuth
      │
      ▼
定时 ETL / 中间 API
      │
      ├── 获取可访问的账户列表
      ├── 逐账户调用 Billing API
      ├── 处理分页、限流和错误
      └── 统一账户、发票及用量字段
      │
      ▼
数据库 / 数据仓库 / Hyper Extract
      │
      ▼
Tableau Dashboard

采用这种架构,可以避免把 Integration Key、Client Secret、RSA 私钥或 Refresh Token 暴露在 Tableau 工作簿中。多个账户、Token 续期和定时刷新也更容易集中处理。

选择 OAuth 授权方式

DocuSign 后台集成通常有两种授权方式。

Authorization Code Grant

适用于需要管理员或用户通过浏览器登录并授权的场景。完成首次授权后,后台保存 Refresh Token,再用它换取新的 Access Token。

使用时需要注意:

  • Refresh Token 必须保存在服务端的密钥管理系统中。
  • 后台任务需要在 Refresh Token 仍然有效时完成轮换。
  • 不要把 Client Secret 或 Refresh Token 放入 Tableau 计算字段、参数或工作簿。
  • 生产环境和演示环境使用不同的 OAuth 地址。

生产环境的 Token Endpoint:

https://account.docusign.com/oauth/token

演示环境的 Token Endpoint:

https://account-d.docusign.com/oauth/token

使用 Refresh Token 获取 Access Token 的示例:

curl --request POST \
  --url https://account.docusign.com/oauth/token \
  --user "${DOCUSIGN_INTEGRATION_KEY}:${DOCUSIGN_CLIENT_SECRET}" \
  --header "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=refresh_token" \
  --data-urlencode "refresh_token=${DOCUSIGN_REFRESH_TOKEN}"

JWT Grant

适用于无人值守的服务端定时任务。程序使用 Integration Key 和 RSA 私钥,代表指定用户申请短期 Access Token。

使用 JWT Grant 通常需要满足以下条件:

  • 已创建 DocuSign Integration Key。
  • 已配置 RSA 公钥。
  • 被模拟用户已授予所需的 OAuth consent。
  • 该用户有权访问目标账户及 Billing API。
  • 后台服务能够安全保存 RSA 私钥。

通过 JWT Grant 获得的 Access Token 仍然是短期令牌。任务必须在 Token 过期前重新申请,不能获取一次后长期写死在 Tableau 中。

获取所有可访问账户

获得 Access Token 后,先调用 DocuSign UserInfo Endpoint:

curl --request GET \
  --url https://account.docusign.com/oauth/userinfo \
  --header "Authorization: Bearer ${DOCUSIGN_ACCESS_TOKEN}"

响应中的 accounts 数组通常包含以下信息:

{
  "accounts": [
    {
      "account_id": "11111111-2222-3333-4444-555555555555",
      "account_name": "Account A",
      "base_uri": "https://na4.docusign.net",
      "is_default": true
    },
    {
      "account_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
      "account_name": "Account B",
      "base_uri": "https://eu.docusign.net",
      "is_default": false
    }
  ]
}

不能假设所有账户都使用同一个 API 主机。构造请求地址时,应使用每个账户对应的 base_uri:

{base_uri}/restapi/v2.1/accounts/{account_id}/...

账户出现在 accounts 数组中,只能说明该 OAuth 用户与这个账户有关联,不代表用户有权调用所有 Billing Endpoint。账单接口还可能受到用户权限、账户类型和合同方式的限制。

调用 Billing API

下面以 Invoices API 为例。请求应使用 UserInfo 返回的 base_uri 和对应的 account_id。具体路径、必填参数和返回字段,需要以当前 DocuSign API Reference 为准。

调用形式如下:

curl --request GET \
  --url "${BASE_URI}/restapi/v2.1/accounts/${ACCOUNT_ID}/billing_invoices/${INVOICE_ID}" \
  --header "Authorization: Bearer ${DOCUSIGN_ACCESS_TOKEN}" \
  --header "Accept: application/json"

如果要构建历史账单或用量看板,通常需要先调用相应的列表接口取得 Invoice ID,再逐条获取详细信息。还要确认接口是否支持日期过滤和分页,不能默认一次请求会返回全部历史数据。

Python ETL 示例

下面的示例包含几个主要步骤:使用 Refresh Token 获取 Access Token,查询账户列表,再按账户发起请求。billing_path 需要根据实际使用的 Billing Endpoint 调整。

import os
import requests
from requests.auth import HTTPBasicAuth

OAUTH_BASE_URL = os.environ.get(
    "DOCUSIGN_OAUTH_BASE_URL",
    "https://account.docusign.com"
)

INTEGRATION_KEY = os.environ["DOCUSIGN_INTEGRATION_KEY"]
CLIENT_SECRET = os.environ["DOCUSIGN_CLIENT_SECRET"]
REFRESH_TOKEN = os.environ["DOCUSIGN_REFRESH_TOKEN"]


def refresh_access_token():
    response = requests.post(
        f"{OAUTH_BASE_URL}/oauth/token",
        auth=HTTPBasicAuth(INTEGRATION_KEY, CLIENT_SECRET),
        headers={
            "Content-Type": "application/x-www-form-urlencoded"
        },
        data={
            "grant_type": "refresh_token",
            "refresh_token": REFRESH_TOKEN
        },
        timeout=30
    )
    response.raise_for_status()
    return response.json()


def get_accounts(access_token):
    response = requests.get(
        f"{OAUTH_BASE_URL}/oauth/userinfo",
        headers={
            "Authorization": f"Bearer {access_token}",
            "Accept": "application/json"
        },
        timeout=30
    )
    response.raise_for_status()
    return response.json().get("accounts", [])


def call_account_api(access_token, base_uri, account_id, billing_path):
    url = (
        f"{base_uri}/restapi/v2.1/accounts/"
        f"{account_id}/{billing_path.lstrip('/')}"
    )

    response = requests.get(
        url,
        headers={
            "Authorization": f"Bearer {access_token}",
            "Accept": "application/json"
        },
        timeout=60
    )
    response.raise_for_status()
    return response.json()


def main():
    token_result = refresh_access_token()
    access_token = token_result["access_token"]

    accounts = get_accounts(access_token)

    rows = []

    for account in accounts:
        account_id = account["account_id"]
        base_uri = account["base_uri"].rstrip("/")

        # 替换为实际需要调用的 Billing Endpoint。
        billing_path = "billing_invoices/{invoice_id}"

        # 只有取得真实 invoice_id 后才能执行该请求。
        # data = call_account_api(
        #     access_token,
        #     base_uri,
        #     account_id,
        #     billing_path.format(invoice_id=invoice_id)
        # )

        rows.append({
            "account_id": account_id,
            "account_name": account.get("account_name"),
            "base_uri": base_uri
        })

    return rows


if __name__ == "__main__":
    print(main())

生产环境还需要处理以下事项:

  • 循环读取分页数据;
  • 遇到 HTTP 429 时进行退避重试;
  • 遇到 401 时更新 Token;
  • 遇到 403 时记录对应的账户权限问题;
  • 设置请求超时和有限次数的重试;
  • 支持增量同步并处理重复数据;
  • 使用 Secret Manager 或同类服务存储凭据;
  • 对日志进行脱敏,不得记录 Access Token 和 Refresh Token。

Tableau 接入方式

方式一:连接数据库或数据仓库

这是优先考虑的接入方式。ETL 可以把数据写入以下表:

docusign_account
docusign_invoice
docusign_usage_daily
docusign_sync_status

每张业务表至少保留这些字段:

account_id
account_name
source_base_uri
extracted_at

Tableau 直接连接数据库,再通过 Tableau Server 或 Tableau Cloud 的刷新计划更新 Extract。

方式二:生成 Tableau Hyper Extract

后台任务可以把统一处理后的数据写入 .hyper 文件,再发布或替换 Tableau 数据源。OAuth 仍由后台任务管理,Tableau 只负责读取结果。

方式三:开发 Web Data Connector 或自定义连接器

如果业务要求 Tableau 直接访问 DocuSign,连接器需要完成:

  • OAuth 登录跳转;
  • Redirect URI 处理;
  • Token 安全存储;
  • Access Token 更新;
  • 账户选择或多账户循环;
  • API 分页和错误处理;
  • Tableau Server 或 Tableau Cloud 上的连接器部署与刷新配置。

这种方式的开发和运维成本较高,还需要确认 Tableau 的版本、部署形态,以及环境是否允许使用自定义连接器。

多账户场景的限制

OAuth 用户必须已加入目标账户,并且具备相应权限,才能以该用户身份访问这些账户。如果组织内的账户彼此独立,可能需要:

  • 创建或指定一个集成用户,并把它加入所有目标账户;
  • 为该用户授予所需的账单权限;
  • 或者分别维护各账户的授权,再由 ETL 合并数据。

DocuSign Organization 管理权限不一定等同于具体账户中的 API 和 Billing 权限。即使用户是组织管理员,也要逐个账户验证接口是否返回 200,并妥善记录 403 Forbidden 等结果。

安全与上线检查

上线前至少要确认:

  • OAuth 应用已经从演示环境迁移,并满足生产环境要求。
  • Redirect URI 与实际回调地址完全一致。
  • Scope 只包含业务需要的权限。
  • Integration Key、Client Secret、私钥和 Refresh Token 均未写入工作簿。
  • 每个账户都使用 UserInfo 返回的 base_uri。
  • 集成用户对所有目标账户都有所需权限。
  • API 调用能够处理分页、限流和 Token 过期。
  • Tableau 中的数据按 account_id 区分,避免混合不同账户的数据。
  • 日志和错误信息不会输出任何 OAuth 凭据。

配置工作的重点不是在 Tableau 中粘贴 OAuth Token,而是建立一个集成层。这个集成层负责安全维护 DocuSign OAuth 会话、遍历账户,并为 Tableau 提供稳定的数据源。

备注:内容仅供参考。