sumsub-integrate-travel-rule について
VASP の送金フローを構築する開発者向けの、Sumsub Travel Rule 統合スキルです。3 種類の交換フロー、受信リクエストへの対応、交換の完了やキャンセルに重点を置いています。
主な機能
- 013 種類の交換フロー案内
- 02相手 VASP の特定
- 03送金の送信と結果処理
- 04交換の完了またはキャンセル
- 05受信リクエストへの 2 段階応答
活用シーン
- 01VASP 出金確認フローの構築
- 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 は、1 件の送金について、2 つの VASP 間で参加者データを交換する仕組みです。Sumsub がハブとなり、Sumsub とやり取りすることで、双方が対応しているプロトコルを使って Sumsub が相手方と通信します。
連携自体は小規模です。本番環境で失敗する原因は、連携の片側しか実装しないケースが多いことです。たとえば、交換を開始しても完了させない、最初の質問には回答しても次の質問には回答しない、といった問題があります。しかも、API はこうした問題を通知しません。この手順では、こうした見落としを中心に説明します。
⚠️ サンドボックストークンのみ
ここで本番用の App Token を受け入れたり使用したりしないでください。Travel Rule は実際の送金と実際の相手方 VASP に対して動作するため、本番環境の相手方にテスト交換を送ると、実際のコンプライアンスキューにリクエストが登録されます。https://cockpit.sumsub.com/checkus/home?sbx=true からサンドボックス用のペアを必ず取得してください — Sumsub を AI エージェントに接続 -> ビルドと設定 -> トークンを生成。トークンとシークレットは一度だけ表示されます。ダイアログを閉じる前に、両方をコピーしてください。
認証の詳しい仕組みについては、sumsub-api-auth を参照してください。
3 つのフロー
すべては、次の 1 つの問いから始まります。誰が交換を作成するのか、また送金はすでにオンチェーンで決済されているのか?
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です。 - Wallet Address Book に自分のアドレスが登録されていること。 受信に使うアドレスと送信元のアドレスの両方が必要です。
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 つは相互排他的ではありません。選択画面を提示し、選択せずに続行することも可能にします。
ステージ 2 — 送金を送信する
sumsub-create-transaction を使用して
type: travelRule のトランザクションを作成し、送信します。フローを分けるのは次の 2 つのフィールドだけです。
| 決済前 | 決済後 | |
|---|---|---|
info.paymentTxnId | 省略 | オンチェーンハッシュ |
counterparty.institutionInfo.internalId | ステージ 1 の VASP ID(判明している場合) | 同じ |
📘 フロー ② では、この送金に対する exchange がすでに存在しないことを最初に確認してください。重複すると、相手先のキューにリクエストが 2 件入ります。重複排除の段階的な照合(ハッシュ、次にアドレス + アセット、次に金額 + 時間枠)に従って GET /resources/kyt/txns/query/- をクエリし、該当がない場合にのみ exchange を作成します。エンドポイントの構文、照合手順、ヘルパースクリプトについては、
references/find-transactions.md を参照してください。
🚧 フロー ① でハッシュを送信すると、決済後の exchange になります。相手先はすでに実行された送金の承認を求められます。フロー ② でハッシュを省略すると、その逆になります。この 1 つのフィールドで意味が決まります。
通貨とチェーン
通貨に関する 2 つの事実は、精度だけでなく Travel Rule の動作にも影響します。
シンボルはアセットではありません。 USDT と USDC は、それぞれ Sumsub のカタログにある十数種類を超える異なるアセットを指し、1000 を超えるシンボルが複数のチェーン上に存在します。そのような通貨について
cryptoParams.cryptoChain を指定せずに currencyCode を送信すると、何も照合されず、アセットが解決されないため、変換は行われません。
しきい値は amountInDefaultCurrency と比較されます。 このフィールドが
ない場合、Travel Rule のしきい値チェックは完全にスキップされ、金額にかかわらずフローが実行されます。ホストされていないウォレットのしきい値も同様です。この値はトランザクション作成時に確定するため、変換に誤りがある場合は再読み込みではなくバックフィルが必要です。値がわかっている場合は、amountInDefaultCurrency と
defaultCurrencyCode を明示的に送信してください。
📘 相手先のプロトコルには独自のアセット語彙があり、特定のプロトコルに対応するマッピングがあるカタログ項目は少数です。マッピングのない特殊なトークンは送信メッセージに含められず、配信前に exchange が notEnoughCounterpartyData で終了します。ペイロードのデバッグを始める前に、語彙の対応状況を確認してください。
シンボル、チェーン、エイリアスの解決:
sumsub-resolve-currency。
ステージ 3 — 結果を確認する
Sumsub が Travel Rule のステータスを割り当て、その後、ルールによって審査結果が割り当てられます。
この2つは異なるものですが、読者は頻繁に混同しています。ルールでトランザクションが承認されても、相手方との交換が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>で取得してください。
回答は**順番に2つのレスポンスを送信します。**トランザクションから、まだ必要なレスポンスを確認できます。
| フラグ | true | false |
|---|---|---|
needMasking | 1つ目のレスポンスを送信 | 回答済みです。スキップしてください |
travelRuleInfo.needApplicantOwnershipConfirmation | 2つ目のレスポンスを送信 | 回答済みです。スキップしてください |
# 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
🚧 **順序は必須で、間違えてもエラーは表示されません。**2つ目のレスポンスだけを送っても200が返ります。しかし、needMaskingがtrueの間は、どのようなデータを送っても交換はonHoldのままになり、相手方には回答が届かず、リクエストは期限切れになります。アドレスを確認すると相手方のデータのマスキングも解除されるため、1つ目のレスポンスが完了するまでは照合する情報がありません。
📘 2つの呼び出しの間に待機する必要はありません。どちらの呼び出しも更新されたトランザクションを返すため、再読み込みもWebhookの待機も不要です。
トランザクションがonHoldのままなら、1つ目のレスポンスは送信していても、2つ目を送信していない状態です。
回答できる時間
分ではなく、秒単位です。確認タイムアウトは相手方が独自の設定で決めます。現在設定できる最短時間は10秒で、考える時間ではなく、Webhookの配信と2つの呼び出しを含むラウンドトリップ全体に対する時間です。タイムアウトはトランザクションに表示されず、相手方によって異なるため、通常の値ではなく最短時間を想定して設計してください。
プロトコルによっては、まったく待機しないものもあります。相手方のシステムは、同じリクエスト内で問い合わせと回答を求めるため、その時点でSumsubがお客様の保存済み設定から判断できる内容が、そのまま回答になります。回答できる猶予はなく、手動で回答する機会もありません。
🚧 そのため、ここでは自動化は単なる効率化ではありません。手動のプロセスではトラフィックに対応できず、どの種類のリクエストが届くかを事前に判断することもできません。
両方の回答を自動化する
自動化は2つの回答に取って代わるのではなく、事前に回答することで、フラグがすでに false の状態でリクエストが届くようにします。
| これを登録する | 事前に回答されるもの |
|---|---|
| Wallet Address Book にアドレスを登録する | 1つ目の回答 |
| 申請者の支払い方法としてアドレスを登録する | 両方 |
所有権検証モードで再利用が許可されていない限り、どちらも機能しません。この設定はダッシュボードで選択します。所有権確認モードとsumsub-manage-wallet-address-bookを参照してください。
ステージ 6 — Sandbox でテストする
Sandbox には、実際の取引相手のように応答するテスト用 VASP が用意されています。成功するケースから順に検証して、問題が起きたときにどの変更が原因か分かるようにしてください。また、本番環境で連携時に見落とされがちな確定処理を、特に念入りにリハーサルしてください。
手順、トリガー値、Sandbox で再現できないものについては、references/sandbox-testing.mdを参照してください。
本番稼働チェックリスト
- 組織が VASP にリンクされ、Travel Rule の利用権限が有効になっている
- ルールバンドルがインストールされ、有効化されている。各交換結果に対する対応方針も決定済みである
- 確認タイムアウトが実際に希望する値に設定されている
- Webhook エンドポイントがリクエストを受信し、冪等性が確保されている — 1回の交換で複数のイベントが発生する
- 出金:VASP の選択機能が組み込まれている、またはアトリビューションに依存することを意識的に決定している
- 出金:すべてのブロードキャスト後にハッシュが送信され、中止時にはキャンセルが送信される
- 入金:両方の回答が順番どおりに実装され、スキップフラグが遵守されている
- 入金:可能な限り自動で回答される。手動対応は例外である
- Wallet Address Book に入金先と出金先のアドレスが登録されている
- システムで未完了ステータスと最終ステータスが区別され、最終ステータスになった記録がクローズされる
- 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— 取引の検索と重複排除の手順。演算子とエンコードの詳細はreferences/query-syntax.mdを参照してください。sumsub-resolve-currency— 送信するcurrencyCode/cryptoChainと、ここでamountInDefaultCurrencyが重要な理由。- Sumsub のドキュメント:Travel Rule のデータ交換フロー、Travel Rule の設定、Wallet Address Book、ホストされていないウォレットの検証。