> ## 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.

# KYC API連携ガイド：APIの全フローをエンドツーエンドで解説

> DiditのREST APIでKYCをエンドツーエンドで連携する手順です。認証情報の取得、セッション作成、リンク共有、Webhook処理、KYC判定の取得までを数分で完了できます。

このガイドでは、REST API経由でDiditの本人確認を組み込むための一連の流れを説明します。認証情報の取得、セッションの作成、ユーザーへの提示、結果の受信までを扱います。

***

## ステップ1：認証情報を取得する

`api_key`を取得する方法は2つあります。

### オプションA：プログラムによる登録(ブラウザ不要)

CI/CDやAIエージェントに最適です。`POST /programmatic/register/`、続いて`POST /programmatic/verify-email/`の2回のAPI呼び出しで、`api_key`がレスポンス内で直接返されます。

→ 全体の流れ、パスワードのルール、JWTの有効期間、ロックアウトポリシーについては[プログラムによる登録](https://docs.didit.me/ja/integration/programmatic-registration.md)を参照してください。

### オプションB：Didit Consoleから取得する

1. [Didit Console](https://business.didit.me)にアクセスします。
2. 組織を作成または選択します。
3. **Settings** → **API & Webhooks**に移動します。
4. 次の値をコピーします。
   * **API Key**：すべての認証呼び出しで`x-api-key`ヘッダーとして使用します。
   * **Webhook Secret Key**：WebhookのHMAC署名の検証に使用します。

### 環境変数

```bash theme={null}
# Required
DIDIT_API_KEY=your_api_key_here
DIDIT_WEBHOOK_SECRET=your_webhook_secret_here

# Your workflow ID (from the Console)
DIDIT_WORKFLOW_ID=your_workflow_id_here
```

***

## ステップ2：ワークフローを作成する

セッションを作成する前に、認証用のワークフローが必要です。ワークフローは、ユーザーが進む認証ステップを定義します。

### Consoleで作成する

1. [Didit Console](https://business.didit.me) → **Workflows**に移動します
2. **Create Workflow**をクリックします
3. ベースとなるテンプレートを選択します。

| テンプレート | 最適な用途 |
| - | - |
| **KYC** | 本人確認書類による完全な本人確認 |
| **Adaptive Age Verification** | 任意で本人確認書類による補完が可能な年齢確認 |
| **Biometric Authentication** | 再訪ユーザーの再検証 |
| **Address Verification** | 住所証明の確認 |

4. 任意の機能を追加します。

| 機能 | 説明 |
| - | - |
| NFC認証 | パスポートや身分証明書のチップデータを読み取る |
| ライブネス検知 | セルフィー動画でなりすましを防止する |
| 顔照合 | セルフィーと書類の顔写真を照合する |
| 電話番号検証 | SMSで電話番号を確認する |
| メールアドレス認証 | メールアドレスを確認する |
| AMLスクリーニング | 制裁リストやPEPリストと照合する |
| データベース検証 | 政府のデータベースと照合して検証する |
| デバイス・IP分析 | VPN、プロキシ、位置情報を検出する |

5. **Workflow ID**をコピーします

> [!NOTE]
>
> 詳細な設定オプションについては、[ワークフローのドキュメント](https://docs.didit.me/ja/console/workflows.md)を
> 参照してください。

***

## ステップ3: 検証セッションを作成する

APIを呼び出して、ユーザーのセッションを作成します。

### リクエスト

```bash theme={null}
curl -X POST https://verification.didit.me/v3/session/ \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "workflow_id": "your-workflow-id",
    "callback": "https://yourapp.com/verification-complete",
    "vendor_data": "user-123"
  }'
```

### リクエストパラメーター

| パラメーター | 必須 | 説明 |
| - | - | - |
| `workflow_id` | はい | コンソールで取得したワークフローのUUID。 |
| `callback` | いいえ | 検証後にユーザーが遷移するURL。Diditはクエリパラメーターとして`verificationSessionId`と`status`を付加します。カスタムスキーム（例: `myapp://`）に対応しています。省略した場合は、ワークフローに設定されたコールバックURLが使用されます。 |
| `callback_method` | いいえ | リダイレクトを処理するデバイス。`initiator`、`completer`、`both`のいずれかです。デフォルト: `initiator`。 |
| `vendor_data` | いいえ | 社内のユーザー識別子。照合のためにWebhookでそのまま返されます。 |
| `metadata` | いいえ | セッションとともに保存され、Webhookでそのまま返される任意のJSONオブジェクト。 |
| `language` | いいえ | UI言語の指定（ISO 639-1、例: `en`、`es`）。省略した場合はブラウザの言語が自動検出されます。 |
| `contact_details` | いいえ | 事前入力とメール通知に使用する、ユーザーのメールアドレスや電話番号。 |
| `expected_details` | いいえ | 検証済みデータとの照合に使用する、想定されるユーザー情報。 |
| `portrait_image` | 条件付き | Base64エンコードされた顔画像（最大2MB、JPEG、PNG、WebP、TIFFのいずれか）。生体認証や顔照合を先に行うワークフローで基準となる顔です。`vendor_data`のユーザーに保存済みの顔（承認済みのライブネス顔、ePassportの写真、身分証明書の顔写真、または登録済みのプロフィール顔）がある場合は省略でき、Diditが自動的に再利用します。どちらもない場合は`400`が返されます。 |
| `sandbox_scenario` | いいえ | サンドボックス専用のシナリオスラッグ（例: `approve`、`decline_aml_hit`）。セッションにマジック入力値を自動で設定します。 |

### すべてのオプションを含む完全な例

```bash theme={null}
curl -X POST https://verification.didit.me/v3/session/ \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "workflow_id": "11111111-2222-3333-4444-555555555555",
    "callback": "https://yourapp.com/verification-complete",
    "vendor_data": "user-123",
    "metadata": {
      "plan": "premium",
      "signup_source": "mobile-app"
    },
    "contact_details": {
      "email": "alex.sample@example.com",
      "email_lang": "en",
      "send_notification_emails": true,
      "phone": "+15550101000"
    },
    "expected_details": {
      "first_name": "John",
      "last_name": "Doe",
      "date_of_birth": "1990-01-01",
      "expected_document_types": ["P", "ID"]
    }
  }'
```

> [!NOTE]
>
> `expected_details.expected_document_types`は、本人確認ステップを
> 特定の書類タイプに制限します。書類選択画面には指定したタイプのみが
> 表示されます。指定可能な値: `P`（パスポート）、`ID`（国民IDカード）、`DL`
> （運転免許証）、`RP`（在留許可証）、`HIC`（健康保険証）、
> `TC`（税務カード）、`SSC`（社会保障カード）。値は大文字と小文字を区別せず、
> 重複は除去されます。不明な値を指定すると`400`が返されます。完全なスキーマは
> [Create Sessionリファレンス](https://docs.didit.me/ja/sessions-api/create-session.md)を参照してください。

### レスポンス

`201 Created`:

```json theme={null}
{
  "session_id": "11111111-2222-3333-4444-555555555555",
  "session_number": 43762,
  "session_token": "3FaJ9wLqX2Mz",
  "url": "https://verify.didit.me/session/3FaJ9wLqX2Mz",
  "vendor_data": "user-123",
  "metadata": {
    "plan": "premium",
    "signup_source": "mobile-app"
  },
  "status": "Not Started",
  "workflow_id": "11111111-2222-3333-4444-555555555555",
  "workflow_version": 3,
  "callback": "https://yourapp.com/verification-complete"
}
```

### 主なレスポンスフィールド

| フィールド | 説明 |
| - | - |
| `session_id` | このセッションの一意のUUID。`GET /v3/session/{sessionId}/decision/`やWebhookで使用します。 |
| `session_number` | Diditコンソールに表示される連番。 |
| `session_token` | エンドユーザーがホスト型フローを開くことを許可し、ネイティブSDKを初期化するための12文字のURLセーフなトークン（Web SDKは代わりに`url`を受け取ります）。シークレットとして扱ってください。 |
| `url` | ユーザーのリダイレクト先、またはiframeに埋め込むためのホスト型検証URL。（フィールド名は`verification_url`ではなく`url`です。）リクエストに`language`を含めると、URLに言語のパスセグメントが含まれます。例: `https://verify.didit.me/en/session/3FaJ9wLqX2Mz`。 |
| `status` | 現在のステータス。新規作成されたセッションでは`"Not Started"`です。 |
| `workflow_id` | セッションが実行される固定のワークフロー識別子。 |
| `workflow_version` | 作成時にセッションが固定された、公開済みのワークフローバージョン。 |

> [!NOTE]
>
> **冪等性。** `vendor_data`が指定され、同じ`vendor_data`を持つ
> 未完了のセッション（`Not Started`、`In Progress`、`Resubmitted`、`Awaiting User`）が
> ワークフローの最新の公開バージョンにすでに存在する場合、
> 重複を作成せずにその既存セッションが返されます（この場合も`201`）。
> その際、`callback`と`metadata`は新しい値に更新され、
> `status`は既存セッションの状態を反映します。

> [!NOTE]
>
> **`x-api-key`がない場合は`403`が返されます。** 本文は
> `{"detail": "You do not have permission to perform this action."}`で、
> 権限のない有効なキーの場合と同じです。誤った、失効した、または期限切れの
> キーでは`401`が返されます。クレジットが不足している場合は
> `400`が返されます。詳細なドキュメントは[Create Session APIリファレンス](https://docs.didit.me/ja/sessions-api/create-session.md)を
> 参照してください。

***

## ステップ4: ユーザーに検証を表示する

ユーザーに検証フローを表示する方法を選択してください。

### オプションA: ネイティブSDK (モバイルに推奨)

カメラ、NFC、生体認証との連携には、`session_token`をネイティブSDKで使用します。

→ [iOS SDK](https://docs.didit.me/ja/integration/native-sdks/ios-sdk.md) · [Android SDK](https://docs.didit.me/ja/integration/native-sdks/android-sdk.md) · [React Native](https://docs.didit.me/ja/integration/native-sdks/react-native-sdk.md) · [Flutter](https://docs.didit.me/ja/integration/native-sdks/flutter-sdk.md)

### オプションB: InContext iframe (Webに推奨)

ステップ3で返された`url`を使用して、ページ内に検証を埋め込みます。

```html theme={null}
<iframe
  src="{url}"
  style="width: 100%; height: 700px; border: none;"
  allow="camera; microphone; geolocation; clipboard-write; fullscreen; autoplay; encrypted-media; digital-credentials-get"
></iframe>
```

→ [InContext iframe](https://docs.didit.me/ja/integration/web-sdks/incontext-iframe.md)

### オプションC: リダイレクト

ユーザーを検証URLにリダイレクトします。

```javascript theme={null}
window.location.href = session.url;
```

→ [Webリダイレクト](https://docs.didit.me/ja/integration/web-sdks/web-redirect.md)

### オプションD: モバイルWebView

ネイティブSDKがないモバイルアプリ向けです。

```jsx theme={null}
// React Native
<WebView source={{ uri: session.url }} />
```

→ [iOS/AndroidのWebView](https://docs.didit.me/ja/integration/web-sdks/webview-in-ios-android.md)

***

## ステップ5: 結果を受け取る

### Webhookで受け取る (推奨)

Webhookは、認証ステータスが変わるとリアルタイムでサーバーに通知します。

**設定:**

1. [Console](https://business.didit.me) (**API & Webhooks**) で、または[`POST /v3/webhook/destinations/`](https://docs.didit.me/ja/management-api/webhook/create-destination)を使用して、`webhook_version: "v3"`と`subscribed_events`配列 (例: `["status.updated"]`) を指定してWebhookの送信先を作成します
2. 作成時に返される`secret_shared_key`を保存します。これはすべての署名方式で使用するHMACシークレットです
3. サーバーにWebhookエンドポイントを実装します

**Webhookペイロードの例:**

`decision`の中では、機能ごとのすべての結果が**複数形の配列** (`id_verifications[]`、`nfc_verifications[]`、`liveness_checks[]`、`face_matches[]`、…) として配信され、ワークフロー内のノード1つにつき1エントリが含まれます。これはV3の契約であり、絶対に変わりません。単数形の`nfc` / `id_verification` / `liveness`キーを前提にコードを書かないでください。

```json theme={null}
{
  "event_id": "9c0c8b8a-1111-4222-9333-444444444444",
  "webhook_type": "status.updated",
  "timestamp": 1774970000,
  "created_at": 1774969994,
  "application_id": "11111111-2222-3333-4444-555555555555",
  "environment": "live",
  "session_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "status": "Approved",
  "workflow_id": "22222222-3333-4444-5555-666666666666",
  "workflow_version": 4,
  "vendor_data": "user-123",
  "metadata": { "plan": "premium" },
  "decision": {
    "session_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
    "status": "Approved",
    "id_verifications": [
      { "node_id": "id_verification_1", "status": "Approved", "document_type": "Passport", "warnings": [] }
    ],
    "liveness_checks": [
      { "node_id": "liveness_1", "status": "Approved", "method": "PASSIVE", "score": 95.4, "warnings": [] }
    ],
    "face_matches": [
      { "node_id": "face_match_1", "status": "Approved", "score": 96.1, "warnings": [] }
    ],
    "reviews": []
  }
}
```

`decision`オブジェクトは、`status`が`Approved`、`Declined`、`In Review`、`Abandoned`のいずれかの場合にのみ含まれます (`Resubmitted`イベントには代わりに`resubmit_info`が含まれます)。Diditは再試行時や複数の送信先へのファンアウト時にも同じ`event_id`を使用するため、冪等性のキーにはこの値を使用してください。すべての複数形の配列とフィールドについては[データモデル](https://docs.didit.me/ja/reference/data-models.md)を参照してください。

**署名を検証する (X-Signature-V2、推奨):**

`X-Signature-V2`は、JSONボディを正規化して再エンコードしたものに対するHMAC-SHA256です。正規化では、整数値の浮動小数点数を整数に短縮し、キーを再帰的にソートし、区切り文字を詰め、Unicodeをそのまま保持します。

```javascript theme={null}
const crypto = require("crypto");

// Match Didit's float normalisation: whole-valued floats serialise as ints.
function shortenFloats(data) {
  if (Array.isArray(data)) return data.map(shortenFloats);
  if (data !== null && typeof data === "object") {
    return Object.fromEntries(
      Object.entries(data).map(([key, value]) => [key, shortenFloats(value)])
    );
  }
  return data;
}

// Sort object keys recursively before re-stringifying.
function sortKeys(obj) {
  if (Array.isArray(obj)) return obj.map(sortKeys);
  if (obj !== null && typeof obj === "object") {
    return Object.keys(obj).sort().reduce((acc, key) => {
      acc[key] = sortKeys(obj[key]);
      return acc;
    }, {});
  }
  return obj;
}

function verifyWebhook(rawBody, signatureV2, timestamp, secret) {
  // 1. Reject anything older than 5 minutes
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp, 10)) > 300) return false;

  // 2. Recompute HMAC-SHA256 against the canonical JSON Didit signs
  const canonical = JSON.stringify(sortKeys(shortenFloats(JSON.parse(rawBody))));
  const expected = crypto.createHmac("sha256", secret).update(canonical, "utf8").digest("hex");

  // 3. Constant-time compare
  const a = Buffer.from(expected, "utf8");
  const b = Buffer.from(signatureV2, "utf8");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

→ [Webhookの完全なドキュメント](https://docs.didit.me/ja/integration/webhooks.md)では、生のバイト列に対する`X-Signature`、非推奨のエンベロープのみを対象とする`X-Signature-Simple`、およびその選び方を説明しています。Diditは失敗した配信 (`5xx`、`404`、タイムアウト) を最大2回 (約1分後、次に約4分後) 再試行し、**5 秒**でタイムアウトします。すばやく`2xx`を返してください。

### APIで取得する (任意)

同じ判定ペイロードはいつでも取得できます:

```bash theme={null}
curl https://verification.didit.me/v3/session/{session_id}/decision/ \
  -H "x-api-key: YOUR_API_KEY"
```

レスポンスの構造 (複数形の配列) はWebhookペイロード内の`decision`オブジェクトと同一で、このエンドポイントはセッションのライフサイクルのどの時点でも呼び出せます。リアルタイムの更新にはWebhookを使用し、APIの呼び出しはバックフィル、監査、再構築用にとどめてください。`/decision/`エンドポイントには、APIキーごとに毎分600件のGETリクエストというレート制限があります ([レート制限](https://docs.didit.me/ja/integration/rate-limiting.md)を参照)。エラーの扱い: 不明な`session_id`の場合は`404`と`{"detail": "Not found."}`が返り、認証の失敗は`403`として返ります。このエンドポイントが`401`を返すことはありません。レスポンス内のメディアURLは有効期間の短い署名付きリンクです。保存せずに、すみやかに取得してください。

→ [セッション取得APIリファレンス](https://docs.didit.me/ja/sessions-api/retrieve-session.md)

***

## 完全なコード例

### Node.js / Express

```javascript theme={null}
const express = require("express");
const crypto = require("crypto");
const app = express();

// Create verification session
app.post("/api/verify", async (req, res) => {
  const { userId } = req.body;

  const response = await fetch("https://verification.didit.me/v3/session/", {
    method: "POST",
    headers: {
      "x-api-key": process.env.DIDIT_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      workflow_id: process.env.DIDIT_WORKFLOW_ID,
      callback: `${process.env.APP_URL}/verification-complete`,
      vendor_data: userId,
    }),
  });

  const session = await response.json();

  // Store session_id associated with user
  await db.users.update({
    where: { id: userId },
    data: { verificationSessionId: session.session_id },
  });

  res.json({
    verificationUrl: session.url,             // hosted verification URL
    sessionToken: session.session_token,      // for native/web SDKs
  });
});

// Handle webhook — uses verifyWebhook / shortenFloats / sortKeys from Step 5
app.post(
  "/api/webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.get("X-Signature-V2");
    const timestamp = req.get("X-Timestamp");

    if (
      !verifyWebhook(
        req.body.toString("utf8"),
        signature,
        timestamp,
        process.env.DIDIT_WEBHOOK_SECRET,
      )
    ) {
      return res.status(401).json({ error: "Invalid signature" });
    }

    const payload = JSON.parse(req.body);
    const { event_id, session_id, status, vendor_data } = payload;

    // Idempotency: the same event_id is reused on retries and fan-out.
    // Skip events you have already processed (e.g. a processed_events table).

    // Update user's verification status (status values are case-sensitive strings)
    if (status === "Approved") {
      db.users.update({
        where: { id: vendor_data },
        data: { isVerified: true, verifiedAt: new Date() },
      });
    }

    // Return 2xx quickly (Didit's delivery timeout is 5s); do heavy work asynchronously
    res.json({ received: true });
  },
);

app.listen(3000);
```

### Python / FastAPI

```python theme={null}
from fastapi import FastAPI, Request, HTTPException
import httpx
import hashlib
import hmac
import json
import os
import time

app = FastAPI()

@app.post("/api/verify")
async def create_verification(user_id: str):
    async with httpx.AsyncClient() as client:
        response = await client.post(
            "https://verification.didit.me/v3/session/",
            headers={
                "x-api-key": os.environ["DIDIT_API_KEY"],
                "Content-Type": "application/json"
            },
            json={
                "workflow_id": os.environ["DIDIT_WORKFLOW_ID"],
                "callback": f"{os.environ['APP_URL']}/verification-complete",
                "vendor_data": user_id
            }
        )

    session = response.json()
    return {
        "verification_url": session["url"],            # hosted verification URL
        "session_token": session["session_token"],     # for native/web SDKs
    }

def shorten_floats(data):
    """Match Didit: whole-valued floats serialise as ints."""
    if isinstance(data, dict):
        return {k: shorten_floats(v) for k, v in data.items()}
    if isinstance(data, list):
        return [shorten_floats(x) for x in data]
    if isinstance(data, float) and data.is_integer():
        return int(data)
    return data

@app.post("/api/webhook")
async def handle_webhook(request: Request):
    payload = await request.json()
    signature = request.headers.get("X-Signature-V2")
    timestamp = request.headers.get("X-Timestamp")

    # Verify timestamp freshness
    if not signature or not timestamp or abs(int(time.time()) - int(timestamp)) > 300:
        raise HTTPException(status_code=401, detail="Request too old")

    # Recompute Didit's V2 canonical JSON: shortened floats, sorted keys,
    # compact separators, Unicode preserved (ensure_ascii=False)
    canonical = json.dumps(
        shorten_floats(payload),
        sort_keys=True,
        separators=(",", ":"),
        ensure_ascii=False,
    )
    expected = hmac.new(
        os.environ["DIDIT_WEBHOOK_SECRET"].encode("utf-8"),
        canonical.encode("utf-8"),
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        raise HTTPException(status_code=401, detail="Invalid signature")

    # Idempotency: dedupe on payload["event_id"] (reused on retries)

    if payload["status"] == "Approved":
        # Update user in database
        pass

    return {"received": True}
```

***

## セッションのステータス

`status`が取りうる値は次の10種類で、大文字と小文字を区別する厳密な文字列です (`Kyc Expired`は先頭の`K`のみが大文字である点に注意してください):

| ステータス | 説明 |
| - | - |
| `Not Started` | セッションは作成済みですが、ユーザーがまだリンクを開いていません。 |
| `In Progress` | ユーザーが現在認証を進めています。 |
| `In Review` | 認証が手動レビューの対象としてフラグ付けされています。 |
| `Approved` | すべてのチェックに合格しました。 |
| `Declined` | 1つ以上のチェックに不合格でした。 |
| `Resubmitted` | レビュアーが特定のステップのやり直しを依頼しました。 |
| `Awaiting User` | KYBの親セッションが、子のKYC当事者の完了を待っています。 |
| `Abandoned` | ユーザーが開始しましたが、所定の時間内に完了しませんでした。 |
| `Expired` | ユーザーがリンクを開く前にセッションがタイムアウトしました。 |
| `Kyc Expired` | 以前に承認されたセッションで、書類の有効期限が過ぎています。 |

ライフサイクル全体、状態遷移、Mermaid図については[認証ステータス](https://docs.didit.me/ja/integration/verification-statuses.md)を参照してください。

***

## 次のステップ

1. **連携方法を選ぶ**:
   * モバイルアプリ → [ネイティブSDK](https://docs.didit.me/ja/integration/native-sdks/overview.md)
   * Webアプリ → [Web SDK](https://docs.didit.me/ja/integration/web-sdks/overview.md)
2. **Webhookを設定**: [Webhook](https://docs.didit.me/ja/integration/webhooks.md)
3. **必要な処理能力を見積もる**: [レート制限](https://docs.didit.me/ja/integration/rate-limiting.md)
4. **すべてのステータスに対応する**: [認証ステータス](https://docs.didit.me/ja/integration/verification-statuses.md)
5. **ワークフローをカスタマイズする**: [ワークフロー](https://docs.didit.me/ja/console/workflows.md)
6. **APIに接続できることを確認する**: [ヘルスチェック](https://docs.didit.me/ja/api-reference/healthcheck.md)

## お困りの場合

* **サポート**: [support@didit.me](mailto:support@didit.me)
* **プログラムによるオンボーディング**: [プログラムによる登録](https://docs.didit.me/ja/integration/programmatic-registration.md)


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