关于 sumsub-integrate-travel-rule
面向开发者的 Sumsub Travel Rule 集成技能,用于构建 VASP 转账流程。内容重点涵盖三种交换流程、处理传入请求,以及完成或取消交换。
主要功能
- 01三种交换流程指引
- 02识别交易对手 VASP
- 03提交转账并处理结果
- 04完成或取消交换
- 05分两步响应传入请求
适用场景
- 01构建 VASP 提现检查流程
- 02处理缺少转账数据的入账
- 03自动响应传入的 Travel Rule 请求
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ sumsub/ agent-skills --skill sumsub-integrate-travel-rule安装后在 Claude Code 中输入/sumsub-integrate-travel-rule确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples/answer-incoming-request.jsdedupe-by-address-window.jsondedupe-by-hash.jsondedupe-search.jsondeposit-after-settlement.jsontravel-rule-recent.jsonwithdrawal-before-settlement.jsonreferences/find-transactions.mdquery-syntax.mdsandbox-testing.mdstatuses.mdunhosted-wallet-flow.mdunhosted-wallets.mdscripts/attach_applicant.shbuild_query.pycancel_txn.shconfirm_ownership.shfinalize_txn.shfind_transactions.shget_action.shget_applicant_actions.shget_latest_check.shget_transaction.shmanual_verify.shsumsub_curl.shtr_sdk_init.sh详细介绍
Sumsub — Travel Rule 集成
Travel Rule 是两个 VASP 之间针对一笔转账交换参与方数据的机制。Sumsub 是中枢:你与 Sumsub 通信,Sumsub 则通过双方共同支持的协议与交易对手通信。
集成本身很小。导致它在生产环境中失败的原因是,大多数集成只实现了一半——它们会发起交换,却从不完成交换;或者只回答第一个问题,不回答第二个问题,而 API 不会对此发出任何提示。本指南围绕这些遗漏展开。
⚠️ 仅限沙盒令牌
此处不要接受或使用生产环境的 App Token。Travel Rule 会处理真实转账和真实的交易对手 VASP,发送给在线交易对手的测试交换会在真实的合规队列中创建真实请求。请务必从 https://cockpit.sumsub.com/checkus/home?sbx=true 获取一组沙盒令牌——Connect Sumsub to your AI agent -> Build & configure -> Generate token。令牌和密钥只会显示一次;关闭对话框前请将两者复制保存。
深入了解身份验证机制:sumsub-api-auth。
三种流程
一切都从一个问题开始:谁创建了交换?转账是否已在链上结算?
who creates the exchange?
┌────────────────────┬────────────────────┐
│ you │ the counterparty │
┌──────────────┼────────────────────┼────────────────────┤
│ not settled │ ① before │ │
│ yet │ settlement │ ③ you answer │
├──────────────┼────────────────────┤ their request │
│ already │ ② after │ │
│ settled │ settlement │ │
└──────────────┴────────────────────┴────────────────────┘
| ① 结算前 | ② 结算后 | ③ 回应请求 | |
|---|---|---|---|
| 典型情况 | 你的用户正在提现 | 收到了一笔没有数据的存款 | 任何涉及你用户的情况 |
info.paymentTxnId | 为空 | 必填 | 由对方设置 |
| 是否阻塞 | 是 — 提现会等待 | 否 — 资金已转出 | 对方的转账会等待你的回复 |
| 结束状态 | 关联哈希后变为 finished | 自动变为 finished | 自动变为 finished |
📘 不要根据 info.direction 来决定集成逻辑。 它表示资金对于你所查看账户的流向,并且在同一笔交换的两端会相反。真正会影响代码逻辑的区别是由谁创建了交换;方向并不能说明这一点。
⚠️ 流程 ③ 不是可选项。每个发起交换的 VASP 也会收到交换请求,未回复的请求意味着你的用户转账会延迟,或被交易对手的规则拒绝。只实现流程 ①/② 的集成是 Travel Rule 失败的最大来源。
阶段 0 — 前提条件
编写代码前请检查以下事项;否则之后每一项都可能导致难以理解的故障。
- 租户已获得 Travel Rule 权限 — 使用
sumsub-check-permissions进行确认。 - 你的组织已关联到 VASP。 如果没有关联,任何操作都无法启动,错误信息为
Your organization is not yet linked to a VASP. Please contact your Customer Success Manager。这不是权限问题,也无法通过重试解决 — 请转达此信息并停止操作。关联工作由 Customer Success Manager 完成,不能通过 API 办理。 - 已安装并启用 Travel Rule 规则包。 规则包决定每种交换结果对应的交易审核答复;平台对此没有默认设定。请参阅
sumsub-create-kyt-rules。 - 已配置 Travel Rule 设置:确认超时时间、所有权验证模式、参与方数据设置。
- 已订阅 Webhook,且事件能送达你这边 — 请参阅
sumsub-manage-webhooks。这里相关的事件包括:applicantKytTxnCreated、applicantKytTxnApproved、applicantKytTxnRejected、applicantKytOnHold、applicantKytTxnAwaitingUser、applicantKytTxnDataChanged。 - 钱包地址簿中已登记你的地址 — 包括接收资金的地址和发送资金的地址。请参阅
sumsub-manage-wallet-address-book。
阶段 1 — 识别交易对手 VASP
在整个集成中,这一步对成功率的提升最大,却也最常被跳过。
选项 A — 让用户选择(推荐)
在提现界面添加目的地选择器,并将所选 ID 传入
counterparty.institutionInfo.internalId。这样会完全跳过归属识别,
请求直接发送给交易对手。
# search the directory as the user types
GET /resources/vasps/-?q=binance&limit=20
- 使用
q搜索——用户熟悉品牌名称,而不是法律实体名称。 - 如果用户选择的是某个集团,而非其某个区域实体,也没有问题: Sumsub 会将该集团解析为其下能够实际接收请求的实体。不要强迫用户在子公司之间做出选择。
- 默认排除隐藏的 VASP;测试 VASP 仅存在于 Sandbox 中,因此无需进行生产环境筛选。
按 ID 查询单个 VASP:GET /resources/api/vasps/{id}/one。
选项 B — 仅发送地址
Sumsub 会根据钱包地址数据库识别归属,接着查询 Travel Rule 协议,最后回退到区块链分析服务提供商。当用户无法说出目的地时,这是合适的回退方案,但仍有相当比例的地址无法识别归属。
两种方式并不互斥:提供选择器,并允许用户不选择而继续。
阶段 2 — 提交转账
使用 sumsub-create-transaction 构建并提交交易,
其中 type: travelRule。区分这两种流程的只有两个字段:
| 结算前 | 结算后 | |
|---|---|---|
info.paymentTxnId | 省略 | 链上哈希 |
counterparty.institutionInfo.internalId | 阶段 1 获取的 VASP ID(如已知) | 相同 |
📘 在流程 ② 中,先检查该笔转账是否已存在交换记录——重复记录会使两条请求进入交易对手的队列。调用 POST /resources/api/tr/txns/search,传入哈希、两个钱包地址、金额、资产、链、结算时间和方向——服务端会先匹配哈希,随后回退到地址 + 资产 + 金额/时间——仅当没有匹配结果时才创建交换记录。请求体字段和辅助脚本见:
references/find-transactions.md。
🚧 在流程 ① 中发送哈希,会将其变成结算后交换——交易对手会被要求授权一笔已经发生的转账。在流程 ② 中省略哈希,则会产生相反的结果。这个字段决定了流程语义。
货币和链
有两个货币相关事实会改变 Travel Rule 的行为,而不仅仅影响精度。
符号不等于资产。 在 Sumsub 的目录中,USDT 和 USDC 各自对应十几种不同的资产,且有一千多个符号同时存在于多条链上。对于这类资产,如果发送 currencyCode 时不带 cryptoParams.cryptoChain,查询将完全无法匹配,因此资产无法解析,也不会发生转换。
阈值比较依据 amountInDefaultCurrency。 如果缺少该字段,Travel Rule 阈值检查会被完全跳过,流程无论金额多少都会继续;非托管钱包阈值也一样。创建交易时该值也会被固定,因此转换错误需要通过回填修正,而不是重新读取。只要知道 amountInDefaultCurrency 和 defaultCurrencyCode,就应明确传入。
📘 交易对手的协议有自己的资产词汇,目录条目中只有少数带有针对特定协议的映射。没有映射的特殊代币无法在出站消息中表达,交换会在发送前以 notEnoughCounterpartyData 结束。排查载荷问题之前,请先检查词汇覆盖情况。
解析符号、链和别名:
sumsub-resolve-currency。
阶段 3 — 查看结果
Sumsub 会分配 Travel Rule 状态,然后你的规则会分配审核结果。
这是两种不同的状态,读者常常会把它们混为一谈:一笔交易可以根据你的规则获批,但其交换状态仍为 expired。根据审核结果决定是否放行提现,并记录交换状态以供审计。
使用 ${CLAUDE_SKILL_DIR}/scripts/get_transaction.sh <txnId> 读取交易(这是经过签名的 curl 命令——请使用这些脚本,不要自行拼装 HTTP 请求,以确保签名和编码正确)。
提交后,交换会立即进入等待状态,也可能已经有最终结果:
| 结果 | 含义 |
|---|---|
awaitingCounterparty | 已送达;正在等待对方答复 |
counterpartyVaspNotFound | 没有可联系的对象——按非托管钱包处理。通常是按数量计算占比最高的单一结果;请参阅 references/unhosted-wallets.md |
counterpartyVaspNotReachable | 已识别,但没有共同协议 |
notEnoughCounterpartyData | 你的载荷未通过协议验证——不是对方拒绝 |
notApplicable | 你的配置跳过了此项 |
完整状态列表、状态转换及哪些状态为最终状态:
references/statuses.md。
阶段 4 — 完成或取消
只有流程 ① 需要在此处编写代码。流程 ② 和 ③ 会自行进入 finished 状态。
# after you broadcast
${CLAUDE_SKILL_DIR}/scripts/finalize_txn.sh <txnId> <on-chain hash>
# → PATCH /resources/kyt/txns/{id}/data/info { "paymentTxnId": "<on-chain hash>" }
# if the user abandoned the withdrawal instead
${CLAUDE_SKILL_DIR}/scripts/cancel_txn.sh <txnId>
# → POST /resources/api/tr/{id}/cancel
🚧 **这是整个集成中最容易被漏掉的调用。**如果没有这一步,交换会停留在 completed,对方永远无法将其持有的数据与链上信息核对,而且双方都没有完整的审计记录。广播交易后立即发送哈希。
取消也很重要:被遗弃且未关闭的交换会一直占用对方的队列,直到过期,并留下你未响应的记录。
阶段 5 — 回复收到的请求
当交易满足 你的用户是 data.applicant,且要确认的钱包位于 data.applicant.paymentMethod.accountId 时,请求会以 applicantKytTxnCreated 的形式到达。使用 ${CLAUDE_SKILL_DIR}/scripts/get_transaction.sh <txnId> 获取交易。
回复时需要按顺序发送两条响应。交易会告诉你仍需发送哪条:
| 标记 | true | false |
|---|---|---|
needMasking | 发送第一条响应 | 已回复——跳过 |
travelRuleInfo.needApplicantOwnershipConfirmation | 发送第二条响应 | 已回复——跳过 |
# response one — is this wallet ours?
${CLAUDE_SKILL_DIR}/scripts/confirm_ownership.sh <txnId> confirmed # or unconfirmed
# → POST /resources/kyt/txns/{id}/ownership/confirmed
# response two — whose is it?
${CLAUDE_SKILL_DIR}/scripts/attach_applicant.sh <txnId> <applicantId>
# → POST /resources/kyt/txns/{id}/travelRuleOwnership { "applicantId": "..." }
# an { "applicantParticipant": { ... } } body instead: attach_applicant.sh <txnId> - < body.json
🚧 **顺序至关重要,顺序错误会悄无声息地失败。**单独发送第二条响应会返回 200——但只要 needMasking 为 true,无论发送什么数据,交换都会停留在 onHold,对方永远收不到答复,且请求最终会过期。确认地址也会解除对方数据的屏蔽,因此在第一条响应生效前,没有任何数据可供核对。
📘 两条响应之间无需等待。两次调用都会返回更新后的交易,因此无需重新读取,也无需等待 webhook。
如果你看到自己的交易停留在 onHold,说明你发送了第一条响应,却没有发送第二条。
你有多长时间
**以秒计,而非分钟。**对方会在自己的设置中选择确认超时时间,目前可选的最短时间是 10 秒——这是完整往返所需的时间,包括 webhook 送达和两次调用,不是你的思考时间。交易中不会显示此时间,而且不同对方的设置也各不相同,因此应按最短时限设计,而不是按常见时限设计。
某些协议完全不会等待:对方的系统发出请求时就要求立即得到答复,因此 Sumsub 在那个时刻能从你存储的配置中确定的内容,就是答复。没有等待窗口,也没有手动回复的机会。
🚧 因此,自动化在这里不是一种优化,而是必需的。手动流程无法处理所有流量,而且你无法预先判断收到的是哪种请求。
自动化两种响应
自动化不会取代两种响应,而是预先回答,因此请求到达时,标志已设为 false。
| 注册此项 | 预先回答 |
|---|---|
| 钱包地址簿中的地址 | 第一种响应 |
| 作为申请人付款方式的地址 | 两种响应 |
除非所有权验证模式允许复用,否则两种方式都不起作用——此设置需在控制面板中选择。请参阅
确认所有权模式
和 sumsub-manage-wallet-address-book。
阶段 6 — 在 Sandbox 中测试
Sandbox 提供一个测试 VASP,其响应方式与真实交易对手相同。从成功案例开始逐步测试,这样出错时你就能知道是哪项更改导致的;还要专门演练最终确认步骤——生产环境中的集成经常会漏掉这一步。
操作流程、触发值以及 Sandbox 中无法复现的内容:
references/sandbox-testing.md。
上线检查清单
- 组织已关联 VASP;Travel Rule 权限已启用
- 规则集已安装并激活,且已针对每种交换结果作出明确决策
- 已将确认超时设置为你实际需要的值
- Webhook 端点能够接收事件,并且具备幂等性——一次交换会发出多个事件
- 提现:已接入 VASP 选择器,或已明确决定依赖归属判定
- 提现:每次广播后都会发送哈希;放弃时会发送取消通知
- 入金:已按顺序实现两种响应,并遵守跳过标志
- 入金:尽可能自动回答;手动处理应作为例外
- 钱包地址簿已载入入金和提现地址
- 系统能够区分未结束状态和最终状态,并在最终状态时关闭记录
- Webhook 投递失败时,你的系统会触发警报
另请参阅
references/statuses.md— 所有状态、状态变更条件,以及哪些状态为最终状态。references/sandbox-testing.md— Sandbox 操作流程和触发值。examples/withdrawal-before-settlement.json、examples/deposit-after-settlement.json—sumsub-create-transaction的交易规范。examples/answer-incoming-request.js— 实现阶段 5 的 Webhook 处理程序,包含标志和处理顺序。references/unhosted-wallets.md— 非托管钱包流程:POST /resources/tr/sdk/init、读取所有权检查结果、手动验证;操作顺序详见references/unhosted-wallet-flow.md。references/find-transactions.md— 搜索端点和去重检查。sumsub-resolve-currency— 应发送哪个currencyCode/cryptoChain,以及为什么amountInDefaultCurrency在此处很重要。- Sumsub 文档:Travel Rule 数据交换流程、Travel Rule 设置、钱包地址簿、非托管钱包验证。