概要
クライアントサイドからの送信は、次の3つの保証に基づいています。- なりすましを防ぐ対象者の紐付け。 トークンは発行時に1つの
vendor_data(ユーザーID)にスコープされます。このトークンで送信されるすべての取引の対象者は、この紐付けに基づいてサーバーサイドで識別されます。そのため、改ざんされたクライアントが別のユーザーに代わって送信することはできず、ペイロードに認証情報を含める必要もありません。 - 自動的なデバイスインテリジェンス。 SDKがデバイスフィンガープリントを収集し、送信とともに送ります。DiditがIPアドレスとユーザーエージェントをサーバーサイドで取得します。統合されたデバイスコンテキストは、追加のライブラリ、個別のデバイストークン、お客様側での実装なしに、取引の対象者に紐付けられます。これにより、デバイスベースのルールや、セッションと取引をまたいだデバイスによる関連付けが可能になります。
- 自動起動されるウォレット所有権の証明。 作成された取引でエンドユーザーにウォレットを管理していることを証明してもらう必要がある場合、レスポンスには種類が
wallet_ownershipのaction_requiredブロックが含まれ、SDKはそのフローを即座に開き、完了後に取引情報を再取得します。ポーリングループやリンク配信を自前で構築する必要はありません。必須のverification_sessionアクションは扱いが異なります。これは自動的に起動されることはなく、同じブロックで返されるため、アプリ側で独自のDidit認証連携を使って起動します。
エンドユーザーがその場にいない場合、つまりバックオフィスでの送金、一括インポート、自社システムから報告されるイベントなどでは、引き続きサーバーサイドの
POST /v3/transactions/が適しています。
どちらの経路でも同じ取引レコードが作成され、同じルールが実行され、同じWebhookが送信されます。フロー
1
取引トークンを発行する
バックエンドがAPIキーとユーザーの
vendor_dataを使ってPOST /v3/transactions/sdk-token/を呼び出し、返されたsdk_tokenをアプリに渡します。2
デバイスから送信する
アプリがSDKの
submitTransactionを呼び出すと、SDKはX-Transaction-Tokenヘッダーを付け、デバイス情報を自動的に付加したうえで、ペイロードを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キー認証を使用します。
他の
/v3/ APIと同様に、camelCaseのエイリアス(vendorData、ttlSeconds、maxUses)も使用できます。
デバイスからの送信
SDKはX-Transaction-Tokenヘッダーにトークンを付けてPOST /v1/transactions/へ送信します。
送信されるペイロードはcamelCaseです。submitTransactionメソッドがペイロードを自動で構築するため、ペイロードを直接扱うのは独自のHTTPクライアントからエンドポイントを呼び出す場合だけです。
POST /v3/transactions/の対応するフィールドにマッピングされます。
レスポンスは、
/v3/エンドポイントと同じsnake_caseの取引詳細に、action_requiredブロックを加えたものです。
セルフホスト型ウォレットの宣言
上記の例では、送金先をユーザー自身のセルフホスト型ウォレットとして宣言しています。取引相手のpaymentMethod.typeがunhosted_walletになっています。
この宣言により、トラベルルールのやり取りの進め方が変わります。
- Diditは取引相手VASPへのルーティングを完全にスキップし(セルフホスト型ウォレットの背後にVASPは存在しないため)、
UNCONFIRMED_OWNERSHIPの状態で送金を直接作成します。 - トラベルルールの設定で
auto_wallet_verificationがオン(デフォルト)の場合、ウォレット所有権ウィジェットのセッションが自動的に発行され、上記のレスポンスと同様にaction_requiredで返されます。 - SDKはウィジェットを自動的に起動します。ユーザーがウォレットを管理していることを証明すると、送金は名前の照合を実行して
COMPLETEDに進みます。名前がウォレットのアドレス帳に登録されている保有者と一致しない場合はCOUNTERPARTY_MISMATCHED_DATAに進みます。
サブジェクトの強制適用
次の3点は常にトークンによって決まり、ペイロードによって決まることはありません。- 取引が作成されるアプリケーション。
- サブジェクトの識別情報。ペイロード内の
subject.externalUserIdは無視され、トークンのvendor_dataに置き換えられます。 - サブジェクトのデバイスコンテキスト(後述)。
subject.deviceで指定された内容と競合する場合は、サーバー側で導出されたフィールドが優先されます。
401が返された場合は、トークンが拒否されたことを意味します。原因は、トークンの欠落、不明なトークン、失効、max_usesの超過、または期限切れのいずれかです。
期限切れのトークンに対するレスポンスでは、エラー詳細に必ずexpiredという単語が含まれます。SDKはこれによってexpired_tokenエラーとinvalid_tokenエラーを区別しています。
取引の再取得
GET /v1/transactions/{transaction_id}/は、同じX-Transaction-Token認証で同じ詳細を返します。
SDKは、起動したアクションフローが完了した後、またはそのウィンドウが閉じられた後にこれを自動的に呼び出すため、最終状態が単一のイベントや通知に依存することはありません。
トークンが有効な間はいつでも、このエンドポイントを自分で呼び出してstatusとaction_requiredを再確認できます。バックエンドからは、GET /v3/transactions/{transaction_id}/で同じレコードを取得できます。
自動デバイスインテリジェンス
すべての送信には、お客様側での連携作業なしにデバイスインテリジェンスが付与されます。- SDKはプライバシーに配慮したデバイスフィンガープリント(
didit-fp-v2スキーマ:プラットフォームとナビゲーターのシグナル、永続的なデバイスID、ボットスコア、複合ハッシュ)を収集し、リクエストとともに送信します。 - DiditはIPアドレス、ユーザーエージェント、accept-languageを接続そのものからサーバー側で導出するため、ペイロードでこれらを偽装することはできません。
/v3/の取引では、引き続き参加者ごとに明示的なdevice_contextオブジェクトを渡すことができます。SDK経由であれば、同じデータのより強力なバージョンを追加の作業なしで得られます。
ユーザーに必要なアクション
一部の取引では、決済が完了する前にエンドユーザーが何らかの操作を行う必要があります。ルールによって認証セッションが求められる場合や、トラベルルールの送金で送金先ウォレットが本当にユーザーのものであることの証明が求められる場合です。 どちらのケースも同じ場所、つまり送信レスポンス(およびその後に取引を取得するたびのレスポンス)のaction_requiredブロックに表示されます。
同じ取引に両方が該当しうる場合は、
wallet_ownershipが優先されます。所有権の証明がないと、トラベルルールの情報交換そのものが進まないためです。
保留中のアクションが解決されると、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認証連携を使って起動します。
SDKが自動起動したウォレット所有権フローが完了するか閉じられると、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はオンボーディング時に取得したユーザーの検証済みの基準となる顔(承認済みのライブネスセルフィー、パスポートのICチップの写真、または書類の顔写真)をすでに保持しています。
ユーザーはライブのセルフィーを1枚撮影するだけです。Diditはライブネス検知と、保存済みの基準との1:1の顔照合を実行します。書類の再スキャンも再オンボーディングも不要で、必要な取引に限って数秒の手間がかかるだけです。4
結果に応じて取引を確定する
セッションが完了すると、取引は自動的に再評価されます。再確認に合格した場合は
APPROVED、不合格の場合はDECLINEDとなり、いずれの場合もtransaction.status.updatedのWebhookが送信されます(取引のタイムラインにはreason: remediation_completedが記録されます)。
アプリは、onActionCompleted形式のコールバック、GET /v1/transactions/{transaction_id}/、またはWebhookで結果を取得します。Web SDK
submitTransactionは現在、Web SDKのシングルトンで利用できます。
{ transactionId, status, travelRuleStatus?, actionRequired? }です。
SDKレベルのペイロードではcategory、details、customPropertiesを使用します。SDKがそれらをワイヤー上のエイリアス(type、info、props)に自動でマッピングします。
エラーはDiditTransactionErrorで型付けされます。
ネイティブSDK
ネイティブの
submitTransaction APIは、iOSおよびAndroid SDK 4.1.0(およびこのバージョンを固定しているReact NativeとFlutterのリリース)で提供されます。
Web SDKとは異なり、ネイティブのペイロードではワイヤー上のフィールド名であるtype、info、propsを直接使用します。getTransaction(transactionToken, transactionId)も公開しています。SDKはアクションフローの起動後にこれを自動で呼び出しますが、任意のタイミングでご自身で呼び出すこともできます。
モバイルでは、デバイスフィンガープリントのトランスポートがすべてのSDKのネットワーク呼び出しにすでに適用されているため、取引の送信にも同じデバイスインテリジェンスが自動的に付与されます。
SDKを使わない場合
SDKが行う処理はすべて通常のHTTPSであり、ここで説明する内容にSDKは必須ではありません。 同等の連携方法が2つあります。 サーバーサイドのみ(トークンは一切不要)。 バックエンドからAPIキーを使用してPOST /v3/transactions/で送信します。レスポンス(およびその後のすべてのGET /v3/transactions/{transaction_id}/)には同じaction_requiredブロックが含まれます。代わりに収集してくれるSDKがないため、参加者ごとに明示的なdeviceコンテキストを渡すこともできます。
独自のHTTPスタックを使ったクライアントサイド。 POST /v3/transactions/sdk-token/でトークンを発行し、デバイスからX-Transaction-Tokenヘッダーを付けてPOST /v1/transactions/とGET /v1/transactions/{transaction_id}/を呼び出します。同じサブジェクトの紐付けと、サーバーサイドでのIP・ユーザーエージェントの導出が適用されます。
そのうえで、action_requiredをご自身で処理します。
また、クライアントのコールバックなしで完了を検知します。
- Webhook(推奨)。 保留中のアクションが完了すると取引の処理が完了し
transaction.status.updatedが発火し、travel_rule.status.updatedは情報交換の状態が遷移するたびに発火します。どちらにも、ポーリングなしで記録を更新するのに十分な情報が含まれます。 - ポーリング。 取引を再取得します(バックエンドからは
/v3/、トークンが有効な間はトークンを使って/v1/)。解決されるとaction_requiredはnullに戻り、status/travel_rule.statusに結果が格納されます。
Webhook
バックエンドは、アプリからの報告を必要としません。 送信した取引にユーザーのアクションが必要な場合、取引Webhook(transaction.createdまたはtransaction.status.updated)に同じaction_requiredブロックが含まれるため、フローの完了前にアプリが閉じられても、システムは保留中のアクションを把握できます。
verification_sessionアクションにはAWAITING_USERステータスが伴いますが、wallet_ownershipアクションはそれ自体では取引のステータスを変更しないため、ステータスではなくaction_requiredを参照してください。
トラベルルールの状態が遷移すると、さらにtravel_rule.status.updatedも送信されます。
次のステップ
取引の送信
サーバーサイドの取引作成APIと、ペイロードの完全なリファレンス。
トラベルルール
マネージド型の送金人・受取人間の情報交換、設定、ライフサイクル。
ウォレット所有権ウィジェット
wallet_ownershipアクションの背後にある、ホスト型の管理権証明フロー。
Webhook
transaction.created、transaction.status.updated、署名検証。
ルール
認証セッションを作成するものを含む、ルールのアクション。
ステータス
取引のライフサイクルにおけるAWAITING_USERの意味。