> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# SDKによる取引送信 - クライアントサイドKYT

> Diditのスコープ付きトークンを使い、アプリから監視対象の取引を送信できます。デバイスインテリジェンスの自動取得、なりすましできない対象者の紐付け、ウォレット所有確認フローに対応しています。

Didit SDKは、[トラベルルール](https://docs.didit.me/ja/transaction-monitoring/travel-rule.md)の送金や暗号資産のスクリーニングを含む[モニタリング対象の取引](https://docs.didit.me/ja/transaction-monitoring/transactions.md)を、エンドユーザーのデバイスから直接送信できます。
バックエンドがスコープ付きの短期間有効なトークンを発行し、それ以外はSDKが処理します。
SDKはデバイスインテリジェンスを自動的に付加し、トラベルルールの送金でウォレットの管理権限を有することの証明が必要な場合は、そのフローをその場で自動的に起動します。
必須の認証セッションが自動的に起動されることはありません。認証セッションはレスポンスで返されるため、アプリ側で独自のDidit認証連携を使って起動してください。詳しくは後述の[必要なユーザーアクション](#ユーザーに必要なアクション)を参照してください。

## 概要

クライアントサイドからの送信は、次の3つの保証に基づいています。

* **なりすましを防ぐ対象者の紐付け。** トークンは発行時に1つの`vendor_data`（ユーザーID）にスコープされます。このトークンで送信されるすべての取引の対象者は、この紐付けに基づいてサーバーサイドで識別されます。そのため、改ざんされたクライアントが別のユーザーに代わって送信することはできず、ペイロードに認証情報を含める必要もありません。
* **自動的なデバイスインテリジェンス。** SDKがデバイスフィンガープリントを収集し、送信とともに送ります。DiditがIPアドレスとユーザーエージェントをサーバーサイドで取得します。統合されたデバイスコンテキストは、追加のライブラリ、個別のデバイストークン、お客様側での実装なしに、取引の対象者に紐付けられます。これにより、デバイスベースの[ルール](https://docs.didit.me/ja/transaction-monitoring/rules.md)や、セッションと取引をまたいだデバイスによる関連付けが可能になります。
* **自動起動されるウォレット所有権の証明。** 作成された取引でエンドユーザーにウォレットを管理していることを証明してもらう必要がある場合、レスポンスには種類が`wallet_ownership`の`action_required`ブロックが含まれ、SDKはそのフローを即座に開き、完了後に取引情報を再取得します。ポーリングループやリンク配信を自前で構築する必要はありません。必須の`verification_session`アクションは扱いが異なります。これは自動的に起動されることはなく、同じブロックで返されるため、アプリ側で独自のDidit認証連携を使って起動します。

> [!NOTE]
>
> エンドユーザーがその場にいない場合、つまりバックオフィスでの送金、一括インポート、自社システムから報告されるイベントなどでは、引き続きサーバーサイドの[`POST /v3/transactions/`](https://docs.didit.me/ja/transaction-monitoring/transactions)が適しています。
> どちらの経路でも同じ取引レコードが作成され、同じルールが実行され、同じ[Webhook](https://docs.didit.me/ja/transaction-monitoring/webhooks.md)が送信されます。

## フロー

**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キー認証を使用します。

```bash theme={null}
curl -X POST https://verification.didit.me/v3/transactions/sdk-token/ \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "vendor_data": "user-042",
    "ttl_seconds": 900,
    "max_uses": 3
  }'
```

```json theme={null}
{
  "sdk_token": "q7Zt...urlsafe",
  "expires_at": "2026-07-07T12:15:00Z"
}
```

| フィールド | 型 | 説明 |
| - | - | - |
| `vendor_data` | string | **必須です。** このトークンの対象となるエンドユーザーの社内識別子です。このトークンで送信されるすべての取引では、対象者の身元がこの値に基づいて固定されます。 |
| `ttl_seconds` | integer | トークンの有効期間（秒）です。デフォルトは`900`（15分）、最大は`86400`（24時間）です。 |
| `max_uses` | integer | 任意です。このトークンで許可される成功した送信の最大回数です。省略すると、TTL内で無制限に使用できます。 |

他の`/v3/` APIと同様に、camelCaseのエイリアス（`vendorData`、`ttlSeconds`、`maxUses`）も使用できます。

> [!WARNING]
>
> **APIキーは絶対にアプリに含めないでください。**
> APIキーはバックエンドに保持してトークンの発行に使用し、デバイスに渡すのは短期間のみ有効な`sdk_token`のみです。
> 有効期間の長いトークンを使い回すのではなく、ユーザーセッションごとに新しいトークンを発行し、想定される送信回数がわかっている場合は`max_uses`を必要最小限に設定してください。

## デバイスからの送信

SDKは`X-Transaction-Token`ヘッダーにトークンを付けて`POST /v1/transactions/`へ送信します。
送信されるペイロードはcamelCaseです。`submitTransaction`メソッドがペイロードを自動で構築するため、ペイロードを直接扱うのは独自のHTTPクライアントからエンドポイントを呼び出す場合だけです。

```bash theme={null}
curl -X POST https://verification.didit.me/v1/transactions/ \
  -H "X-Transaction-Token: SDK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "txnId": "wd-2026-07-07-0042",
    "type": "travelRule",
    "info": {
      "direction": "out",
      "amount": "0.25",
      "currency": "ETH",
      "currencyType": "crypto",
      "cryptoParams": { "crypto_chain": "ETH" }
    },
    "subject": { "fullName": "Jane Doe" },
    "counterparty": {
      "fullName": "Ana Diaz",
      "paymentMethod": { "type": "unhosted_wallet", "accountId": "0xBeneficiaryWallet01" }
    },
    "travelRule": {
      "status": "AWAITING_COUNTERPARTY",
      "beneficiaryData": { "wallet_address": "0xBeneficiaryWallet01", "name": "Ana Diaz" }
    },
    "includeCryptoScreening": true
  }'
```

各トップレベルフィールドは、[`POST /v3/transactions/`](https://docs.didit.me/ja/transaction-monitoring/transactions)の対応するフィールドにマッピングされます。

| 送信フィールド | `/v3/`での対応フィールド | 備考 |
| - | - | - |
| `txnId` | `transaction_id` | **必須です。** 取引に対してお客様が付与する一意の識別子です。 |
| `txnDate` | `transaction_at` | ISO-8601形式のタイムスタンプです。デフォルトは現在時刻です。 |
| `zoneId` | `time_zone` | IANAタイムゾーンIDです(例：`Europe/Madrid`)。 |
| `type` | `transaction_category` | **必須です。** `finance`、`kyc`、`travelRule`、`userPlatformEvent`、`gamblingBet`、およびその他のカテゴリーです。 |
| `info` | `transaction_details` | **必須です。** `direction`、`amount`、`currency`(必須)に加え、`currencyType`、`amountInDefaultCurrency`、`defaultCurrencyCode`、`paymentDetails`、`paymentTxnId`、`type`(アクションの種類)、`cryptoParams`です。 |
| `subject`, `counterparty` | `subject`, `counterparty` | 参加者の情報です。`type`(エンティティの種類)、`externalUserId`、`fullName`、`firstName`、`lastName`、`dob`、`address`、`institutionInfo`、`device`、`paymentMethod`(`type`、`accountId`、`issuingCountry`)を指定します。取引相手の`paymentMethod.type`を`unhosted_wallet`にすると、送金先をセルフホスト型ウォレットとして宣言できます。詳しくは[後述](#セルフホスト型ウォレットの宣言)を参照してください。 |
| `props` | `custom_properties` | カスタムのキーと値のペアです。ルールでは`custom_values.<key>`として使用できます。 |
| `travelRule` | `travel_rule_details` | `status`、`protocol`、`required`、`obligationsCount`、`originatorData`、`beneficiaryData`、`metadata`です。このブロックを送信する場合は常に`status`が必須で、指定しないとリクエストは`400`で失敗します。トラベルルールが有効な場合、Diditは送信された`status`をすべて無視し、Didit自身がやり取りを進めます。 |
| `includeCryptoScreening` | `include_crypto_screening` | 取引ごとに暗号資産スクリーニングの設定を上書きします。 |

レスポンスは、`/v3/`エンドポイントと同じsnake\_caseの取引詳細に、`action_required`ブロックを加えたものです。

```json theme={null}
{
  "uuid": "22222222-3333-4444-5555-666666666666",
  "txn_id": "wd-2026-07-07-0042",
  "transaction_type": "travelRule",
  "direction": "OUTBOUND",
  "status": "APPROVED",
  "amount": "0.25",
  "currency": "ETH",
  "travel_rule": {
    "uuid": "33333333-4444-5555-6666-777777777777",
    "status": "UNCONFIRMED_OWNERSHIP",
    "required": true
  },
  "action_required": {
    "type": "wallet_ownership",
    "url": "https://verify.didit.me/wallet-ownership/3s9x...urlsafe",
    "widget_session_id": "77777777-8888-9999-aaaa-bbbbbbbbbbbb",
    "expires_at": "2026-07-07T13:00:00Z"
  }
}
```

### セルフホスト型ウォレットの宣言

上記の例では、送金先をユーザー自身のセルフホスト型ウォレットとして宣言しています。取引相手の`paymentMethod.type`が`unhosted_wallet`になっています。
この宣言により、[トラベルルールのやり取り](https://docs.didit.me/ja/transaction-monitoring/travel-rule.md#申告されたセルフホスト型ウォレット)の進め方が変わります。

* Diditは取引相手VASPへのルーティングを完全にスキップし(セルフホスト型ウォレットの背後にVASPは存在しないため)、`UNCONFIRMED_OWNERSHIP`の状態で送金を直接作成します。
* [トラベルルールの設定](https://docs.didit.me/ja/transaction-monitoring/travel-rule.md#セットアップ)で`auto_wallet_verification`がオン(デフォルト)の場合、[ウォレット所有権ウィジェット](https://docs.didit.me/ja/transaction-monitoring/wallet-ownership-widget.md)のセッションが自動的に発行され、上記のレスポンスと同様に`action_required`で返されます。
* SDKはウィジェットを自動的に起動します。ユーザーがウォレットを管理していることを証明すると、送金は名前の照合を実行して`COMPLETED`に進みます。名前がウォレットのアドレス帳に登録されている保有者と一致しない場合は`COUNTERPARTY_MISMATCHED_DATA`に進みます。

宣言がない場合、Diditは代わりに[経路](https://docs.didit.me/ja/transaction-monitoring/travel-rule.md#概要)を順に試し、取引相手VASPが見つからない場合にのみ所有権確認フローにフォールバックします。
出金先がユーザー自身のウォレットであることをプロダクト側ですでに把握している場合は、必ずウォレットを宣言してください。失敗に終わるルーティングを省略でき、ユーザーをすぐに所有権確認フローへ進めることができます。

### サブジェクトの強制適用

次の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}/`](https://docs.didit.me/ja/transaction-monitoring/transactions)で同じレコードを取得できます。

## 自動デバイスインテリジェンス

すべての送信には、お客様側での連携作業なしにデバイスインテリジェンスが付与されます。

* SDKはプライバシーに配慮したデバイスフィンガープリント(`didit-fp-v2`スキーマ：プラットフォームとナビゲーターのシグナル、永続的なデバイスID、ボットスコア、複合ハッシュ)を収集し、リクエストとともに送信します。
* DiditはIPアドレス、ユーザーエージェント、accept-languageを接続そのものからサーバー側で導出するため、ペイロードでこれらを偽装することはできません。

統合された結果はサブジェクト参加者のデバイスコンテキストに正規化され、KYCセッションからもデータが追加されるものと同じデバイスグラフにアップサートされます。
これにより、ユーザーベース全体で取引をデバイスによって関連付けられるようになり(「デバイスによる関連」)、デバイスとネットワークのシグナルが[ルール](https://docs.didit.me/ja/transaction-monitoring/rules.md)と[IPエンリッチメント](https://docs.didit.me/ja/transaction-monitoring/ip-enrichment.md)に反映され、[コンソール](https://docs.didit.me/ja/transaction-monitoring/console.md)の取引詳細にも表示されます。

サーバーから送信する`/v3/`の取引では、引き続き参加者ごとに明示的な`device_context`オブジェクトを渡すことができます。SDK経由であれば、同じデータのより強力なバージョンを追加の作業なしで得られます。

## ユーザーに必要なアクション

一部の取引では、決済が完了する前にエンドユーザーが何らかの操作を行う必要があります。ルールによって認証セッションが求められる場合や、トラベルルールの送金で送金先ウォレットが本当にユーザーのものであることの証明が求められる場合です。
どちらのケースも同じ場所、つまり送信レスポンス（およびその後に取引を取得するたびのレスポンス）の`action_required`ブロックに表示されます。

```json theme={null}
"action_required": {
  "type": "verification_session",
  "url": "https://verify.didit.me/session/...",
  "session_id": "11111111-2222-3333-4444-555555555555",
  "session_token": "SESSION_TOKEN",
  "status": "Not Started"
}
```

```json theme={null}
"action_required": {
  "type": "wallet_ownership",
  "url": "https://verify.didit.me/wallet-ownership/3s9x...urlsafe",
  "widget_session_id": "77777777-8888-9999-aaaa-bbbbbbbbbbbb",
  "expires_at": "2026-07-07T13:00:00Z"
}
```

| タイプ | 表示されるタイミング | ユーザーが行うこと |
| - | - | - |
| `verification_session` | [ルールアクション](https://docs.didit.me/ja/transaction-monitoring/rules.md)で追加のユーザー認証が求められ、取引が`AWAITING_USER`に移行した場合。 | アプリから起動されるホスト型の認証セッション（例：生体認証による再確認）を完了します。下記の[認証セッションの起動](#認証セッションの起動)を参照してください。 |
| `wallet_ownership` | トラベルルールの送金でエンドユーザーによるウォレット管理の証明が必要であり、かつ[トラベルルール設定](https://docs.didit.me/ja/transaction-monitoring/travel-rule.md#セットアップ)で`auto_wallet_verification`が有効な場合。証明が必要になるのは、送金先が[セルフホスト型ウォレットとして申告された](#セルフホスト型ウォレットの宣言)場合（`UNCONFIRMED_OWNERSHIP`）、または送信側の送金で取引相手のVASPが見つからなかった場合です。Diditは取引に紐づいた[ウォレット所有権ウィジェット](https://docs.didit.me/ja/transaction-monitoring/wallet-ownership-widget.md)のセッションを自動発行します。 | トラベルルール設定で許可されている方法に応じて、メッセージ署名（対応チェーンの場合）、スクリーンショット、または自己申告によってウォレットの管理を証明します。ただし、自動発行されたセッションでSatoshiテストが使われることは決してありません。このアクションはSDKが自動的に起動します。 |

同じ取引に両方が該当しうる場合は、`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認証連携を使って起動します。

> [!WARNING]
>
> ウォレット所有権ウィジェットを、モバイルアプリに埋め込まれたWebView内で表示してはいけません。ウォレットのディープリンクがWebViewの外に出られず、証明フローが行き詰まってしまいます。
> SDKは自動起動時にすでに適切なブラウザ画面を使用しています。`url`を自分で扱う場合も、このルールを守ってください。
> 詳しくは[ウォレット所有権ウィジェットのガイド](https://docs.didit.me/ja/transaction-monitoring/wallet-ownership-widget.md#返されたurlを開く)を参照してください。

SDKが自動起動したウォレット所有権フローが完了するか閉じられると、SDKは取引を再取得し、更新された結果を`onActionCompleted`コールバックに渡します。
`verification_session`アクションはアプリ側で起動するため、このサイクルには含まれません。次のセクションを参照してください。

### 認証セッションの起動

`action_required.type`が`verification_session`の場合は、`result.actionRequired`からブロックを読み取り、既存のDidit認証の連携に渡します。

```typescript Web theme={null}
if (result.actionRequired?.type === "verification_session") {
  DiditSdk.shared.startVerification({ url: result.actionRequired.url });
}
```

```swift iOS theme={null}
if actionRequired.type == "verification_session" {
    try await DiditSdk.shared.startVerification(token: actionRequired.sessionToken)
}
```

```kotlin Android theme={null}
if (actionRequired.type == "verification_session") {
    DiditSdk.startVerification(token = actionRequired.sessionToken) { result ->
        // handle the verification result
    }
}
```

```typescript React Native theme={null}
if (actionRequired.type === "verification_session") {
  await startVerification(actionRequired.sessionToken);
}
```

```dart Flutter theme={null}
if (actionRequired.type == 'verification_session') {
  await DiditSdk.startVerification(actionRequired.sessionToken);
}
```

認証フローが完了したら、`GET /v1/transactions/{transaction_id}/`を呼び出して（または[取引のWebhook](#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`ブロックを含んで返されます。

```json theme={null}
{
  "status": "AWAITING_USER",
  "action_required": {
    "type": "verification_session",
    "url": "https://verify.didit.me/session/...",
    "session_id": "11111111-2222-3333-4444-555555555555",
    "session_token": "SESSION_TOKEN",
    "status": "Not Started"
  }
}
```

**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で結果を取得します。

これが機能するのは、KYCオンボーディング、生体認証による再確認、取引の判定が1つのプラットフォーム上で実行されるためです。オンボーディング時に撮影した顔がステップアップの基準となり、デバイスグラフがセッションと取引を結び付け、ルールエンジンがそれらを統合します。ベンダー間をつなぐ処理も、顔画像のエクスポートも、別途の生体認証契約も不要です。
再確認は実際に実行された場合にのみ、標準の生体認証機能([\$0.10](https://docs.didit.me/ja/getting-started/pricing.md))として課金されます。

## Web SDK

`submitTransaction`は現在、Web SDKのシングルトンで利用できます。

```typescript theme={null}
import { DiditSdk } from "@didit-protocol/sdk-web";

const result = await DiditSdk.shared.submitTransaction({
  transactionToken: sdkToken, // minted by your backend via POST /v3/transactions/sdk-token/
  transaction: {
    txnId: "wd-2026-07-07-0042",
    txnDate: new Date().toISOString(),
    zoneId: "Europe/Madrid",
    category: "travelRule",
    details: {
      direction: "out",
      amount: "0.25",
      currency: "ETH",
      currencyType: "crypto",
      paymentDetails: "ETH withdrawal to self-hosted wallet",
      cryptoParams: { crypto_chain: "ETH" }
    },
    subject: {
      type: "individual",
      fullName: "Jane Doe"
      // no externalUserId needed: the subject identity comes from the token
    },
    counterparty: {
      type: "individual",
      fullName: "Ana Diaz",
      paymentMethod: {
        type: "unhosted_wallet",
        accountId: "0xBeneficiaryWallet01"
      }
    },
    travelRule: {
      status: "AWAITING_COUNTERPARTY", // ignored when Travel Rule is enabled: Didit drives the exchange
      beneficiaryData: {
        wallet_address: "0xBeneficiaryWallet01",
        name: "Ana Diaz"
      }
    },
    includeCryptoScreening: true
  },
  autoLaunchAction: true, // default: auto-launches wallet_ownership only; verification_session comes back in result.actionRequired for you to launch
  onActionCompleted: (refreshed) => {
    console.log(refreshed.status, refreshed.travelRuleStatus);
  }
});

console.log(result.transactionId, result.status, result.actionRequired?.type);
```

結果は`{ transactionId, status, travelRuleStatus?, actionRequired? }`です。
SDKレベルのペイロードでは`category`、`details`、`customProperties`を使用します。SDKがそれらをワイヤー上のエイリアス(`type`、`info`、`props`)に自動でマッピングします。

エラーは`DiditTransactionError`で型付けされます。

```typescript theme={null}
import { DiditSdk, DiditTransactionError } from "@didit-protocol/sdk-web";

try {
  await DiditSdk.shared.submitTransaction({ transactionToken, transaction });
} catch (error) {
  if (error instanceof DiditTransactionError) {
    switch (error.type) {
      case "expired_token": // mint a fresh token and retry
        break;
      case "invalid_token": // rejected: unknown, revoked, or over max_uses
        break;
      case "validation": // inspect error.fieldErrors for per-field details
        break;
      case "network": // retry with backoff
        break;
    }
  }
}
```

## ネイティブSDK

> [!NOTE]
>
> ネイティブの`submitTransaction` APIは、iOSおよびAndroid SDK 4.1.0(およびこのバージョンを固定しているReact NativeとFlutterのリリース)で提供されます。
> Web SDKとは異なり、ネイティブのペイロードではワイヤー上のフィールド名である`type`、`info`、`props`を直接使用します。

```swift Swift theme={null}
import DiditSDK

let payload = DiditTransactionPayload(
    txnId: "wd-2026-07-07-0042",
    type: "travelRule",
    info: DiditTransactionInfo(
        direction: "out",
        amount: 0.25,
        currency: "ETH",
        currencyType: "crypto",
        cryptoParams: ["crypto_chain": "ETH"]
    ),
    subject: DiditTransactionParticipant(type: "individual", fullName: "Jane Doe"),
    counterparty: DiditTransactionParticipant(
        type: "individual",
        fullName: "Ana Diaz",
        paymentMethod: DiditTransactionPaymentMethod(type: "unhosted_wallet", accountId: "0xBeneficiaryWallet01")
    ),
    travelRule: DiditTravelRuleInfo(
        status: "AWAITING_COUNTERPARTY", // required by the API; ignored when Travel Rule is enabled
        beneficiaryData: ["wallet_address": "0xBeneficiaryWallet01", "name": "Ana Diaz"]
    ),
    includeCryptoScreening: true
)

let result = try await DiditSdk.shared.submitTransaction(
    transactionToken: sdkToken,
    transaction: payload,
    options: DiditTransactionOptions(
        autoLaunchAction: true,           // default
        useNativeWalletOwnership: true,   // default: native flow, browser fallback
        onTransactionUpdated: { refreshed in
            print(refreshed.status ?? "", refreshed.travelRuleStatus ?? "")
        }
    )
)

print(result.transactionId, result.status ?? "", result.actionRequired?.type ?? "none")
// Errors: DiditTransactionError .invalidToken / .expiredToken / .validation(fieldErrors) / .network
```

```kotlin Kotlin theme={null}
import me.didit.sdk.DiditSdk
import me.didit.sdk.transactions.*

val payload = DiditTransactionPayload(
    txnId = "wd-2026-07-07-0042",
    type = "travelRule",
    info = DiditTransactionInfo(
        direction = "out",
        amount = 0.25,
        currency = "ETH",
        currencyType = "crypto",
        cryptoParams = mapOf("crypto_chain" to "ETH")
    ),
    subject = DiditTransactionParticipant(type = "individual", fullName = "Jane Doe"),
    counterparty = DiditTransactionParticipant(
        type = "individual",
        fullName = "Ana Diaz",
        paymentMethod = DiditTransactionPaymentMethod(
            type = "unhosted_wallet",
            accountId = "0xBeneficiaryWallet01"
        )
    ),
    travelRule = DiditTravelRule(
        status = "AWAITING_COUNTERPARTY", // required by the API; ignored when Travel Rule is enabled
        beneficiaryData = mapOf("wallet_address" to "0xBeneficiaryWallet01", "name" to "Ana Diaz")
    ),
    includeCryptoScreening = true
)

// suspend fun - call from a coroutine; DiditSdk.initialize() must have run first
val result = DiditSdk.submitTransaction(
    transactionToken = sdkToken,
    transaction = payload,
    options = DiditTransactionOptions(
        autoLaunchAction = true,          // default
        useNativeWalletOwnership = true,  // default: native flow, Custom Tab fallback
        onTransactionUpdated = { refreshed ->
            Log.d("Didit", "${refreshed.status} ${refreshed.travelRuleStatus}")
        }
    )
)

Log.d("Didit", "${result.transactionId} ${result.status} ${result.actionRequired?.type}")
// Errors: sealed DiditTransactionException - InvalidToken / ExpiredToken / Validation(fieldErrors) / Network
```

```typescript React Native theme={null}
import {
  submitTransaction,
  getTransaction,
  DiditTransactionError,
} from "@didit-protocol/sdk-react-native";

const result = await submitTransaction(sdkToken, {
  txnId: "wd-2026-07-07-0042",
  type: "travelRule",
  info: {
    direction: "out",
    amount: 0.25,
    currency: "ETH",
    currencyType: "crypto",
    cryptoParams: { crypto_chain: "ETH" },
  },
  subject: { type: "individual", fullName: "Jane Doe" },
  counterparty: {
    type: "individual",
    fullName: "Ana Diaz",
    paymentMethod: { type: "unhosted_wallet", accountId: "0xBeneficiaryWallet01" },
  },
  travelRule: {
    status: "AWAITING_COUNTERPARTY", // required by the API; ignored when Travel Rule is enabled
    beneficiaryData: { wallet_address: "0xBeneficiaryWallet01", name: "Ana Diaz" },
  },
  includeCryptoScreening: true,
}, {
  autoLaunchAction: true, // default
  onTransactionUpdated: (refreshed) => {
    console.log(refreshed.status, refreshed.travelRuleStatus);
  },
});

console.log(result.transactionId, result.status, result.actionRequired?.type);
// Errors: DiditTransactionError with code invalid_token | expired_token | validation | network
```

```dart Flutter theme={null}
import 'package:didit_sdk/sdk_flutter.dart';

final result = await DiditSdk.submitTransaction(
  sdkToken,
  DiditTransactionPayload(
    txnId: 'wd-2026-07-07-0042',
    type: 'travelRule',
    info: DiditTransactionInfo(
      direction: 'out',
      amount: 0.25,
      currency: 'ETH',
      currencyType: 'crypto',
      cryptoParams: {'crypto_chain': 'ETH'},
    ),
    subject: DiditTransactionParticipant(type: 'individual', fullName: 'Jane Doe'),
    counterparty: DiditTransactionParticipant(
      type: 'individual',
      fullName: 'Ana Diaz',
      paymentMethod: DiditTransactionPaymentMethod(
        type: 'unhosted_wallet',
        accountId: '0xBeneficiaryWallet01',
      ),
    ),
    travelRule: DiditTravelRule(
      status: 'AWAITING_COUNTERPARTY', // required by the API; ignored when Travel Rule is enabled
      beneficiaryData: {'wallet_address': '0xBeneficiaryWallet01', 'name': 'Ana Diaz'},
    ),
    includeCryptoScreening: true,
  ),
  options: DiditTransactionOptions(
    autoLaunchAction: true, // default
    onTransactionUpdated: (refreshed) {
      debugPrint('${refreshed.status} ${refreshed.travelRuleStatus}');
    },
  ),
);

debugPrint('${result.transactionId} ${result.status} ${result.actionRequired?.type}');
// Errors: DiditTransactionException with code invalid_token | expired_token | validation | network
```

すべてのSDKは、同じトークンで取引を再取得するための`getTransaction(transactionToken, transactionId)`も公開しています。SDKはアクションフローの起動後にこれを自動で呼び出しますが、任意のタイミングでご自身で呼び出すこともできます。

モバイルでは、デバイスフィンガープリントのトランスポートがすべてのSDKのネットワーク呼び出しにすでに適用されているため、取引の送信にも同じデバイスインテリジェンスが自動的に付与されます。

## SDKを使わない場合

SDKが行う処理はすべて通常のHTTPSであり、ここで説明する内容にSDKは必須ではありません。
同等の連携方法が2つあります。

**サーバーサイドのみ(トークンは一切不要)。** バックエンドからAPIキーを使用して[`POST /v3/transactions/`](https://docs.didit.me/ja/management-api/transactions/create)で送信します。レスポンス(およびその後のすべての`GET /v3/transactions/{transaction_id}/`)には同じ`action_required`ブロックが含まれます。代わりに収集してくれるSDKがないため、参加者ごとに明示的な`device`コンテキストを渡すこともできます。

**独自のHTTPスタックを使ったクライアントサイド。** [`POST /v3/transactions/sdk-token/`](https://docs.didit.me/ja/management-api/transactions/sdk-token)でトークンを発行し、デバイスから`X-Transaction-Token`ヘッダーを付けて[`POST /v1/transactions/`](https://docs.didit.me/ja/management-api/transactions/submit-sdk)と[`GET /v1/transactions/{transaction_id}/`](https://docs.didit.me/ja/management-api/transactions/get-sdk)を呼び出します。同じサブジェクトの紐付けと、サーバーサイドでのIP・ユーザーエージェントの導出が適用されます。

そのうえで、`action_required`をご自身で処理します。

| `action_required.type` | 対応方法 |
| - | - |
| `wallet_ownership` | `url`をユーザーに届け、**実際のブラウザ環境**で開きます。Webでは通常のタブ、モバイルでは`SFSafariViewController` / Chrome Custom Tabsを使用するか、メールリンクやQRコードによる帯域外の手段で届けます。埋め込みWebView内では絶対に表示しないでください。ウォレットのディープリンクは埋め込みWebViewから抜け出せません。 |
| `verification_session` | ユーザーを`url`に誘導します。これはDiditの標準のホスト型検証ページです。または、すでに運用しているDiditの認証連携に`session_token`を渡します。 |

また、クライアントのコールバックなしで完了を検知します。

* **Webhook(推奨)。** 保留中のアクションが完了すると取引の処理が完了し`transaction.status.updated`が発火し、`travel_rule.status.updated`は情報交換の状態が遷移するたびに発火します。どちらにも、ポーリングなしで記録を更新するのに十分な情報が含まれます。
* **ポーリング。** 取引を再取得します(バックエンドからは`/v3/`、トークンが有効な間はトークンを使って`/v1/`)。解決されると`action_required`は`null`に戻り、`status` / `travel_rule.status`に結果が格納されます。

この設計には一度きりの依存関係はありません。ウィジェット、認証セッション、バックエンドはすべて同じ永続的な取引の状態に収束するため、タブが閉じられたりWebhookを受け取り損ねたりしても、取引を再取得することで必ず復旧できます。

## Webhook

バックエンドは、アプリからの報告を必要としません。
送信した取引にユーザーのアクションが必要な場合、[取引Webhook](https://docs.didit.me/ja/transaction-monitoring/webhooks.md)(`transaction.created`または`transaction.status.updated`)に同じ`action_required`ブロックが含まれるため、フローの完了前にアプリが閉じられても、システムは保留中のアクションを把握できます。
`verification_session`アクションには`AWAITING_USER`ステータスが伴いますが、`wallet_ownership`アクションはそれ自体では取引のステータスを変更しないため、ステータスではなく`action_required`を参照してください。
トラベルルールの状態が遷移すると、さらに[`travel_rule.status.updated`](https://docs.didit.me/ja/transaction-monitoring/travel-rule#webhook)も送信されます。

## 次のステップ

> [card] [取引の送信](https://docs.didit.me/ja/transaction-monitoring/transactions.md)
>
> サーバーサイドの取引作成APIと、ペイロードの完全なリファレンス。

> [card] [トラベルルール](https://docs.didit.me/ja/transaction-monitoring/travel-rule.md)
>
> マネージド型の送金人・受取人間の情報交換、設定、ライフサイクル。

> [card] [ウォレット所有権ウィジェット](https://docs.didit.me/ja/transaction-monitoring/wallet-ownership-widget.md)
>
> wallet\_ownershipアクションの背後にある、ホスト型の管理権証明フロー。

> [card] [Webhook](https://docs.didit.me/ja/transaction-monitoring/webhooks.md)
>
> transaction.created、transaction.status.updated、署名検証。

> [card] [ルール](https://docs.didit.me/ja/transaction-monitoring/rules.md)
>
> 認証セッションを作成するものを含む、ルールのアクション。

> [card] [ステータス](https://docs.didit.me/ja/transaction-monitoring/statuses.md)
>
> 取引のライフサイクルにおけるAWAITING\_USERの意味。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.