跳转到主要内容
Didit SDK 可以直接在终端用户的设备上提交受监控交易,包括Travel Rule转账和加密货币筛查。 您的后端生成一个短期有效、限定范围的令牌,其余工作由 SDK 完成。 SDK 会自动附加设备信息。当 Travel Rule 转账需要钱包控制权证明时,SDK 会立即自动启动该流程。 必需的验证会话永远不会被自动启动。它会在响应中返回,由您的应用使用自有的 Didit 验证集成来启动。详见下方的必需的用户操作。

概述

客户端提交基于三项保障:
  • 防伪造的主体绑定。 令牌在生成时即限定于一个 vendor_data(您的用户 ID)。使用该令牌提交的每笔交易,其主体身份都由服务器端根据此绑定强制执行。因此,被篡改的客户端无法代表其他用户提交交易,而且 payload 中无需包含任何凭证。
  • 自动设备信息。 SDK 会采集设备指纹并随提交一同发送,Didit 则在服务器端推导出地址和用户代理。组合后的设备上下文会附加到交易的主体上,无需额外的库、单独的设备令牌,也无需您自行实现。它可用于基于设备的规则,以及跨会话和交易的设备关联分析。
  • 自动启动的钱包所有权证明。 当已创建的交易要求终端用户证明其控制某个钱包时,响应会包含类型为 wallet_ownership 的 action_required 块,SDK 会立即打开该流程,并在完成后刷新交易。您无需自行构建轮询循环或链接分发。必需的 verification_session 操作则不同:它永远不会被自动启动,而是在同一个块中返回,由您的应用使用自有的 Didit 验证集成来启动。
当没有终端用户参与时,服务器端的 POST /v3/transactions/ 仍是正确的选择,例如后台转账、批量导入,或由您自有系统上报的事件。 两种路径会创建相同的交易记录,运行相同的规则,并发出相同的 Webhook。

流程

1

生成交易令牌

您的后端调用 POST /v3/transactions/sdk-token/,并提供您的 API 密钥和用户的 vendor_data,然后将返回的 sdk_token 交给您的应用。
2

从设备提交

应用在 SDK 上调用 submitTransaction,该调用会携带 X-Transaction-Token 请求头,并自动补充设备信息,将 payload 发送到 POST /v1/transactions/。
3

处理任何必需的操作

如果 action_required.type 为 wallet_ownership,SDK 会自动启动组件,然后通过 GET /v1/transactions/{transaction_id}/ 刷新交易。 如果为 verification_session,SDK 不会启动任何流程。请读取 action_required,并使用其 session_token(在 Web 端则为 url)启动您自有的验证流程。

生成交易令牌

请在您的后端使用 POST /v3/transactions/sdk-token/ 生成令牌。认证方式与其他 /v3/ 交易接口相同,均使用 API 密钥。
vendorData、ttlSeconds 和 maxUses 这几个驼峰命名别名同样可用,与 /v3/ API 的其他接口一致。
切勿将您的 API 密钥放在应用中。 API 密钥只保留在您的后端,用于生成令牌。只有短期有效的 sdk_token 会发送到设备上。 请为每个用户会话生成新令牌,而不是重复使用长期有效的令牌。如果已知预期的提交次数,请将 max_uses 设置得尽量严格。

从设备提交

SDK 会将请求发送到 POST /v1/transactions/,并在 X-Transaction-Token 请求头中携带 token。 传输载荷采用 camelCase 格式。submitTransaction 方法会为您构建该载荷,因此只有在您使用自己的 HTTP 客户端直接调用该接口时,才需要自行处理。
每个顶层字段都对应 POST /v3/transactions/ 中的等效字段: 响应与 /v3/ 接口返回的 snake_case 交易详情相同,并额外包含 action_required 块:

声明自托管钱包

上面的示例将目标声明为用户自己的自托管钱包:交易对手的 paymentMethod.type 为 unhosted_wallet。 此声明会改变 Travel Rule 信息交换 的运行方式:
  • Didit 会完全跳过交易对手 VASP 路由,因为自托管钱包背后不存在 VASP,并直接以 UNCONFIRMED_OWNERSHIP 状态创建转账。
  • 当您的 Travel Rule 设置 中的 auto_wallet_verification 开启时(默认开启),系统会自动生成 钱包所有权组件 的会话,并如上方响应所示,在 action_required 中返回。
  • SDK 会自动拉起该组件。用户证明对钱包的控制权后,转账会执行姓名匹配,并推进至 COMPLETED;若姓名与钱包地址簿持有人不一致,则推进至 COUNTERPARTY_MISMATCHED_DATA。
若未做此声明,Didit 会按顺序尝试各个通道,只有在找不到交易对手 VASP 时才会回退到所有权验证流程。 只要您的产品已经知道提现目标是用户自己的钱包,就应当做此声明。这样可以省去一次失败的路由尝试,并让用户立即进入证明流程。

主体约束

以下三项始终由 token 决定,而非由载荷决定:
  • 创建交易所属的应用。
  • 主体的身份:载荷中的 subject.externalUserId 会被忽略,并替换为 token 的 vendor_data。
  • 主体的设备上下文(见下文);服务端推导的字段在与 subject.device 中提供的内容冲突时以服务端为准。
该接口返回 401 表示 token 被拒绝:缺失、未知、已撤销、超出其 max_uses,或已过期。 过期 token 的响应错误详情中始终包含 expired 一词,SDK 正是据此区分 expired_token 与 invalid_token 错误。

读取交易详情

GET /v1/transactions/{transaction_id}/ 使用相同的 X-Transaction-Token 认证方式,返回相同的详情。 SDK 会在已拉起的操作流程完成或其窗口关闭后自动调用该接口,因此最终状态不依赖于单个事件或通知。 只要 token 仍然有效,您随时都可以自行调用此接口,以重新检查 status 和 action_required;在您的后端,GET /v3/transactions/{transaction_id}/ 会返回相同的记录。

自动设备情报

每次提交都会携带设备情报,您无需进行任何额外集成:
  • SDK 会收集一份注重隐私的设备指纹(didit-fp-v2 结构:平台与 navigator 信号、持久设备 ID、机器人分数以及复合哈希),并随请求一同发送。
  • Didit 在服务端根据连接本身推导 IP 地址、用户代理和 accept-language,因此无法通过载荷伪造这些信息。
合并后的结果会被规范化为主体参与方的设备上下文,并将其插入或更新到设备图谱中(与 KYC 会话写入的是同一图谱)。 由此,交易可以在您的全部用户之间按设备进行关联(“按设备关联”),设备与网络信号会输入到规则和 IP 信息补全中,并显示在控制台中的交易详情里。 服务端提交的 /v3/ 交易仍可以为每个参与方传入显式的 device_context 对象;在 SDK 路径下,您无需额外操作,即可获得同类数据的更强版本。

用户所需操作

有些交易需要终端用户先完成某些操作才能结算。规则可能要求进行验证会话,或者 Travel Rule 转账可能需要用户证明目标钱包确实属于其本人。 这两种情况都会在同一位置显示:提交响应中的 action_required 块(以及之后对该交易的每次读取):
如果两种情况可能同时适用于同一笔交易,则以 wallet_ownership 为准,因为缺少所有权证明会阻止 Travel Rule 信息交换本身。 待处理操作解决后,action_required 会变为 null。 现有的 remediation 字段为向后兼容而保留;action_required 是标准的数据块。

自动启动约定

autoLaunchAction(默认为 true)仅自动启动 wallet_ownership 操作:
  • Web:在 SDK 自带的浮层弹窗中打开,通过经过来源校验的消息传递,并结合有上限的交易轮询来检测完成状态(绝不依赖单一事件)。
  • iOS / Android(以及委托给原生 SDK 的 React Native 和 Flutter 封装):当 useNativeWalletOwnership 为 true(默认值)时,以 SDK 的原生钱包所有权体验(SwiftUI / Jetpack Compose)呈现;如果无法使用原生钱包所有权体验,则回退到 SFSafariViewController / Chrome Custom Tab,最后使用默认浏览器。流程关闭或应用回到前台后,SDK 会自动重新读取交易。
verification_session 操作从不自动启动:它始终在 action_required 中返回,由您的应用使用自己的 Didit 验证集成来发起。
钱包所有权组件绝不能在嵌入式移动端 webview 中渲染:钱包深层链接无法逃逸出该容器,证明流程会因此中断。 SDK 在自动启动时已使用正确的浏览器界面;如果您自行处理 url,请保持这一规则。 详情参见钱包所有权组件指南。
SDK 自动启动的钱包所有权流程完成或关闭后,会重新读取交易,并将刷新后的结果传递给您的 onActionCompleted 回调。 verification_session 操作不属于这一循环,因为它由您的应用发起,详见下一节。

启动验证会话

当 action_required.type 为 verification_session 时,从 result.actionRequired 读取该块,并将其交给您现有的 Didit 验证集成:
验证流程完成后,调用 GET /v1/transactions/{transaction_id}/(或等待交易 Webhook)以获取该交易的最新状态。

示例:为高额提现进行生物识别升级验证

这是该流程最典型的应用场景:对风险较高的交易(大额交易、新收款人、新设备)设置门槛,要求与您在入驻时验证过的同一个人再次进行生物识别复核。 账户接管看起来正是这样发生的:密码正确,2FA 验证码正确(但已被钓鱼获取或 SIM 卡被调换),并向一个您从未见过的钱包提现。 到这一步,所有基于凭证的防御都已失效。 而人脸识别则不然:攻击者无法通过活体检测自拍,因为自拍必须与合法所有者在 KYC 入驻时采集的人脸一致。
1

在控制台中创建一次规则

Transactions → Rules → Create Rule:条件为 amount gte 5000,操作为 Change status → AWAITING_USER,并在该操作上选择您的 生物识别认证 工作流。 您还可以添加其他风险条件,例如首次交易对手、新设备指纹、IP 国家变更,可作为额外条件或额外规则添加。
2

照常提交提现请求

提交代码无需任何改动。规则触发后,响应会返回 status: "AWAITING_USER",以及类型为 verification_session 的 action_required 块:
3

启动复核

将 session_token(或 url)交给您现有的 Didit 验证集成,操作方式与启动验证会话中所述一致。 由于该会话是为与交易主体相同的 vendor_data 创建的,Didit 已保存该用户入驻时验证过的参考人脸:即已通过的活体自拍、护照芯片照片或证件人像。 用户只需拍摄一张实时自拍;Didit 会执行活体检测,并与存储的参考人脸进行 1:1 人脸比对。无需重新扫描证件,也无需重新入驻,只会在真正需要的交易上带来几秒钟的额外操作。
4

根据结果完成结算

复核会话完成后,交易会自动重新评估:复核通过则为 APPROVED,失败则为 DECLINED,无论哪种结果都会发送 transaction.status.updated Webhook(交易时间线中的 reason: remediation_completed)。 您的应用可通过类似 onActionCompleted 的回调、GET /v1/transactions/{transaction_id}/ 或 Webhook 获取结果。
这之所以可行,是因为 KYC 入驻、生物识别再验证和交易决策都运行在同一个平台上:入驻时采集的人脸是升级验证的参考基准,设备图谱将会话与交易关联起来,规则引擎把它们串联在一起。无需第三方厂商对接,不导出人脸图像,也无需单独签订生物识别合同。 只有在实际执行时,再验证才会按标准的生物识别认证功能计费($0.10)。

Web SDK

submitTransaction 目前已在 Web SDK 单例上可用:
结果为 { transactionId, status, travelRuleStatus?, actionRequired? }。 在 SDK 层的 payload 中,您需要填写 category、details 和 customProperties,SDK 会自动将它们映射为传输层别名(type、info、props)。 错误类型通过 DiditTransactionError 表示:

原生 SDK

原生 submitTransaction API 随 iOS 和 Android SDK 4.1.0 发布(以及锁定该版本的 React Native 和 Flutter 版本)。 与 Web SDK 不同,原生 payload 直接使用传输层字段名:type、info 和 props。
每个 SDK 还提供 getTransaction(transactionToken, transactionId) 方法,用同一个 token 重新读取交易。启动操作流程后,SDK 会自动为您调用该方法,您也可以随时自行调用。 在移动端,设备指纹传输已应用于所有 SDK 网络调用,因此交易提交会自动携带相同的设备信息。

不使用 SDK

直接使用 HTTPS 即可完成 SDK 的全部功能,无需依赖 SDK。 提供两种等效的集成方式: 仅服务端(无需任何 token)。 使用您的 API 密钥,从后端调用 POST /v3/transactions/ 提交交易。响应以及之后每次 GET /v3/transactions/{transaction_id}/ 的返回都会包含相同的 action_required 块。由于没有 SDK 为您收集设备信息,您还可以为每个参与方显式传入 device 上下文。 客户端使用自有 HTTP 栈。 先通过 POST /v3/transactions/sdk-token/ 生成 token,然后在设备端使用 X-Transaction-Token 请求头调用 POST /v1/transactions/ 和 GET /v1/transactions/{transaction_id}/。主体绑定以及服务端的 IP 与 User-Agent 推导同样适用。 然后,请自行处理 action_required: 无需任何客户端回调即可检测完成状态:
  • Webhook(推荐)。 待处理操作使交易得出结果时,会触发 transaction.status.updated;每次 Travel Rule 交换状态发生变化时,会触发 travel_rule.status.updated。两者都包含足够的信息,可在无需轮询的情况下更新您的记录。
  • 轮询。 重新读取交易(后端使用 /v3/,或在 token 有效期内使用 /v1/ 配合 token)。操作完成后,action_required 会恢复为 null,status 和 travel_rule.status 则会给出结果。
该设计中不存在只能单次使用的依赖:组件、验证会话和您的后端最终都汇聚到同一份持久化的交易状态上,因此关闭的标签页或丢失的 webhook 总能通过重新读取交易来恢复。

Webhook

您的后端无需依赖应用来回传状态。 提交的交易需要用户操作时,交易 webhook(transaction.created 或 transaction.status.updated)会包含相同的 action_required 块。因此,即使用户在流程完成前关闭了应用,您的系统也能获知待处理的操作。 verification_session 操作会使交易状态变为 AWAITING_USER,但 wallet_ownership 操作本身不会改变交易状态,因此请读取 action_required,而不要只看状态。 Travel Rule 状态变化还会触发 travel_rule.status.updated。

后续步骤

提交交易

服务端 Create Transaction API 及完整 payload 参考。

Travel Rule

托管式汇款人与收款人交换、设置及生命周期。

钱包所有权组件

wallet_ownership 操作背后的托管式控制权证明流程。

Webhook

transaction.created、transaction.status.updated 以及签名验证。

规则

规则操作,包括用于创建验证会话的操作。

状态

AWAITING_USER 在交易生命周期中的含义。
最后修改于 2026年11月7日