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

# Webhook 処理ガイド

> Legitmark Webhook イベントを処理するためのコード例、ベストプラクティス、実装ガイダンス。

このガイドでは、複数の言語でのコード例と本番環境での推奨事項を含め、信頼性の高い Webhook ハンドラーの構築方法を説明します。

## TypeScript SDK の使用

[TypeScript SDK](https://github.com/legitmark-eng/legitmark-typescript)（`v0.2.0+`）を使用する場合、SDK は型付きペイロードとヘルパー関数を提供し、イベント解析とステータスチェックを処理します。

```typescript theme={null}
import {
  parseWebhookEvent,
  isAuthentic,
  isCounterfeit,
  needsResubmission,
  isCancelled,
} from 'legitmark';

app.post('/webhooks/legitmark', express.json(), (req, res) => {
  const event = parseWebhookEvent(req.body);

  if (isAuthentic(event)) {
    markItemAsAuthentic(event.reference_id);
  } else if (isCounterfeit(event)) {
    flagItem(event.reference_id);
  } else if (needsResubmission(event)) {
    requestNewPhotos(event.reference_id, event.sides);
  } else if (isCancelled(event)) {
    closeCase(event.sr_uuid);
  }

  res.status(200).send('OK');
});
```

`parseWebhookEvent()` はペイロードの構造を検証し、型付きの `LegitmarkWebhookEvent` を返します。ヘルパー関数は特定のステータスの組み合わせをチェックします：

| ヘルパー                                | `true` を返す条件                      |
| ----------------------------------- | --------------------------------- |
| `isAuthentic(event)`                | `COMPLETE + APPROVED` — 商品は本物     |
| `isCounterfeit(event)`              | `COMPLETE + REJECTED` — 商品は本物ではない |
| `isCancelled(event)`                | `CANCELLED` — リクエストがキャンセルされた      |
| `needsResubmission(event)`          | `media_rejected` — 画像の再アップロードが必要  |
| `isQcApproved(event)`               | `QC + APPROVED` — 写真が品質審査を通過      |
| `isAuthenticationInProgress(event)` | `UNDERWAY + ASSIGNED` — 鑑定士が作業中   |

## 基本ハンドラー

TypeScript SDK を使用しない場合、以下は複数の言語での例です。

<CodeGroup>
  ```javascript Node.js (Express) theme={null}
  app.post('/webhooks/legitmark', express.json(), (req, res) => {
    const event = req.body;

    switch (event.event_type) {
      case 'state_change':
        if (event.state.primary === 'COMPLETE') {
          updateAuthResult(event.reference_id, event.state.supplement);
        }
        break;

      case 'media_rejected':
        event.sides.forEach(side => {
          flagImageForReupload(event.reference_id, side.side, side.reason);
        });
        break;

      case 'invalidate_sr':
        cancelItem(event.reference_id, event.invalidation_reason.message);
        break;
    }

    res.status(200).send('OK');
  });
  ```

  ```python Python (Flask) theme={null}
  @app.route('/webhooks/legitmark', methods=['POST'])
  def handle_webhook():
      event = request.get_json()

      if event['event_type'] == 'state_change':
          if event['state']['primary'] == 'COMPLETE':
              update_auth_result(event['reference_id'], event['state']['supplement'])

      elif event['event_type'] == 'media_rejected':
          for side in event['sides']:
              flag_for_reupload(event['reference_id'], side['side'], side['reason'])

      elif event['event_type'] == 'invalidate_sr':
          cancel_item(event['reference_id'], event['invalidation_reason']['message'])

      return 'OK', 200
  ```
</CodeGroup>

## ベストプラクティス

<AccordionGroup>
  <Accordion title="200 を即座に返す">
    Webhook をできるだけ早く確認応答してください。処理ロジックが複雑な場合や外部サービスを呼び出す場合は、タスクをキューに入れてから `200` を返してください。
  </Accordion>

  <Accordion title="重複配信を処理する">
    同じイベントを複数回受信する可能性があります。Payload フィールドから重複排除キーを構築してください：

    * **`state_change`**：`sr_uuid` + `state.primary` + `state.supplement`（同じ SR が複数のステータス変更を発行します）
    * **`media_rejected`**：`sr_uuid` + `event_type`
    * **`invalidate_sr`**：`sr_uuid` + `event_type`

    本番環境では、永続ストア（データベース、Redis）を使用して重複排除を行ってください。
  </Accordion>

  <Accordion title="生のペイロードを記録する">
    デバッグのために完全な Webhook ペイロードを保存してください。問題が発生した場合、元のデータがあると診断が大幅に早くなります。
  </Accordion>

  <Accordion title="未知のイベントタイプを適切に処理する">
    認識されない `event_type` 値を持つ Webhook を拒否しないでください。将来、新しいイベントタイプが追加される可能性があります。すべてのイベントに対して `200` を返してください。
  </Accordion>
</AccordionGroup>

## まとめ

Webhook ハンドラーは以下を満たす必要があります：

* **HTTPS エンドポイント** を使用し、30 秒以内に **200** を返す
* **冪等性** を保証する — Payload フィールドを使用して重複排除する（`sr_uuid` だけでは不十分、1 つの SR が複数のイベントを発行するため）
* **未知のイベントタイプを受け入れる** — エラーにしない（新しいイベントが追加される可能性あり）
* 複雑なロジックは **非同期で処理** してタイムアウトを回避する
