# 機能設計書 47-Webhook

## 概要

本ドキュメントは、Ghost CMSにおけるWebhook機能の設計仕様を記載したものである。

### 本機能の処理概要

Webhook機能は、Ghost CMS内で発生したイベントを外部URLにHTTPリクエストとして通知する機能である。記事の公開、メンバーの追加、タグの変更など、様々なイベントをリアルタイムで外部システムに連携できる。各WebhookはインテグレーションまたはAPIキーに紐づき、セキュアな署名付きリクエストを送信する。

**業務上の目的・背景**：現代のWebアプリケーションでは、システム間連携の自動化が重要である。Webhook機能により、Ghostで発生したイベントを外部システム（Slack、Zapier、自社システム等）に即座に通知し、ワークフローの自動化やリアルタイム同期を実現する。

**機能の利用シーン**：
- 記事公開時にSlackチャンネルへ通知
- メンバー登録時にCRMシステムへデータ同期
- コンテンツ変更時に静的サイトの再ビルドをトリガー
- 外部分析システムへのイベントデータ送信
- カスタムワークフローの自動実行

**主要な処理内容**：
1. Webhookの作成（Add）：イベント、ターゲットURL、シークレットの設定
2. Webhookの編集（Edit）：設定の更新
3. Webhookの削除（Destroy）：不要なWebhookの削除
4. イベントリスニング：Ghost内イベントの監視
5. トリガー実行：イベント発生時にHTTPリクエスト送信
6. 結果記録：送信結果（成功/失敗）の記録

**関連システム・外部連携**：
- インテグレーション管理との連携
- Ghostイベントシステム（common/events）との連携
- 外部Webhookエンドポイント

**権限による制御**：
- インテグレーションに紐づくWebhookはそのインテグレーションの所有者のみ編集可能
- APIキー経由の場合、同一インテグレーションのWebhookのみ操作可能
- カスタムインテグレーション制限がある場合、Webhookも制限対象

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 58 | インテグレーション設定 | 補助機能 | Webhookの設定・管理 |

## 機能種別

CRUD操作 / イベント駆動 / HTTP通信

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| event | string | Yes | 監視するイベント名 | 小文字、定義済みイベント |
| target_url | string | Yes | 通知先URL | 最大2000文字、有効なURL |
| name | string | No | Webhook名 | 最大191文字 |
| secret | string | No | 署名用シークレット | 最大191文字 |
| api_version | string | No | APIバージョン | デフォルト: 現在のバージョン |
| integration_id | string | Yes | 紐づくインテグレーションID | 24文字、有効なID |
| id | string | Yes（Edit/Destroy時） | WebhookのID | 24文字 |

### 入力データソース

- 管理画面のインテグレーション設定
- Admin API

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | Webhookの一意識別子 |
| event | string | 監視するイベント名 |
| target_url | string | 通知先URL |
| name | string | Webhook名 |
| api_version | string | APIバージョン |
| integration_id | string | 紐づくインテグレーションID |
| last_triggered_at | dateTime | 最終トリガー日時 |
| last_triggered_status | string | 最終トリガーのHTTPステータス |
| last_triggered_error | string | 最終トリガーのエラー情報 |
| created_at | dateTime | 作成日時 |
| updated_at | dateTime | 更新日時 |

### 出力先

- APIレスポンス（JSON形式）
- 外部Webhookエンドポイント（HTTPリクエスト）

## 処理フロー

### 処理シーケンス

```
[Webhook登録フロー]
1. Webhook作成リクエスト受信
2. 重複チェック（同一event+target_url）
3. データベース保存
4. レスポンス返却

[Webhookトリガーフロー]
1. Ghostイベント発生
2. イベントリスナーが検知
3. 該当Webhookを検索
4. ペイロード生成
5. 署名生成（secretがある場合）
6. HTTPリクエスト送信
7. 結果記録（last_triggered_*を更新）
```

### フローチャート

```mermaid
flowchart TD
    subgraph Registration
        A[Webhook作成リクエスト] --> B{重複チェック}
        B -->|Exists| C[ValidationError]
        B -->|OK| D[DB保存]
        D --> E[レスポンス返却]
    end

    subgraph Trigger
        F[Ghostイベント発生] --> G[イベントリスナー検知]
        G --> H[該当Webhook検索]
        H --> I{Webhookあり?}
        I -->|No| J[終了]
        I -->|Yes| K[ペイロード生成]
        K --> L[署名生成]
        L --> M[HTTPリクエスト送信]
        M --> N{成功?}
        N -->|Yes| O[成功記録]
        N -->|410| P[Webhook削除]
        N -->|Other| Q[エラー記録]
    end
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-47-001 | 重複禁止 | 同一event+target_urlのWebhookは作成不可 | 作成時 |
| BR-47-002 | 所有者限定 | Webhookは所有インテグレーションからのみ操作可能 | 編集・削除時 |
| BR-47-003 | 410自動削除 | 410レスポンスを受信したWebhookは自動削除 | トリガー時 |
| BR-47-004 | リトライ | 失敗時は最大5回リトライ（テスト環境除く） | トリガー時 |
| BR-47-005 | タイムアウト | リクエストタイムアウトは2秒 | トリガー時 |
| BR-47-006 | 署名付与 | secretがある場合はX-Ghost-Signatureヘッダーを付与 | トリガー時 |

### 計算ロジック

署名生成:
```javascript
// secretがある場合の署名生成
const ts = Date.now();
const signature = crypto
    .createHmac('sha256', secret)
    .update(`${reqPayload}${ts}`)
    .digest('hex');

headers['X-Ghost-Signature'] = `sha256=${signature}, t=${ts}`;
```

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 作成 | webhooks | INSERT | Webhookレコード作成 |
| 編集 | webhooks | UPDATE | Webhook情報更新 |
| 削除 | webhooks | DELETE | Webhookレコード削除 |
| トリガー結果更新 | webhooks | UPDATE | last_triggered_*の更新 |
| イベント検索 | webhooks | SELECT | イベントに該当するWebhook検索 |

### テーブル別操作詳細

#### webhooks

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id, event, target_url, name, secret, api_version, integration_id, status, created_at, updated_at | 新規データ | status='available' |
| UPDATE | last_triggered_at, last_triggered_status, last_triggered_error, updated_at | トリガー結果 | autoRefresh: false |
| SELECT | * | event=指定イベント | findAllByEvent |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | 認証なし | 認証情報を付与 |
| 403 | NoPermissionError | 他インテグレーションのWebhookを操作 | 所有インテグレーションのAPIキーを使用 |
| 404 | NotFoundError | Webhookが存在しない | 有効なIDを指定 |
| 422 | ValidationError | 重複Webhook、無効なintegration_id | パラメータを確認 |

### リトライ仕様

- Webhook送信失敗時は最大5回リトライ（エクスポネンシャルバックオフ）
- テスト環境（NODE_ENV=test*）ではリトライなし
- タイムアウト: 2秒

## トランザクション仕様

- Webhook作成・編集・削除は単一トランザクション
- トリガー結果更新はautoRefresh: falseで非同期更新

## パフォーマンス要件

- Webhook作成/編集: 200ms以内
- イベント検索: 50ms以内
- HTTPリクエスト送信: タイムアウト2秒

## セキュリティ考慮事項

- secretを設定して署名検証を推奨
- 署名にはタイムスタンプを含めリプレイ攻撃を防止
- target_urlは信頼できるエンドポイントを指定
- actionsCollectCRUDで操作履歴を記録

## 備考

- 対応イベント一覧: site.changed, post.added/deleted/edited/published等、page.*, tag.*, member.*
- インポート時はWebhookトリガーをスキップ
- Webhookは非同期で処理され、結果は後から確認可能

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、Webhookのデータモデルとスキーマを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | webhook.js | `ghost/core/core/server/models/webhook.js` | Webhookモデル定義 |
| 1-2 | schema.js | `ghost/core/core/server/data/schema/schema.js` | webhooksテーブル定義（353-370行目） |

**読解のコツ**:
- tableName: 'webhooks'でテーブル名を確認
- defaults()でapi_versionとstatusのデフォルト値を確認
- findAllByEventとgetByEventAndTargetの静的メソッドに注目

**主要処理フロー**:
- **6行目**: tableName: 'webhooks'
- **12-17行目**: defaults関数でapi_version, statusを設定
- **46-52行目**: findAllByEventでイベントに該当するWebhookを検索
- **55-75行目**: getByEventAndTargetで重複チェック

#### Step 2: APIエンドポイントを理解する

WebhookのAPIコントローラーを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | webhooks.js | `ghost/core/core/server/api/endpoints/webhooks.js` | APIエンドポイント |

**主要処理フロー**:
1. **22-35行目**: addアクションでwebhooksService.addを呼び出し
2. **37-86行目**: editアクションで権限チェックとWebhook更新
3. **88-131行目**: destroyアクションで権限チェックとWebhook削除
4. **42-64行目**: permissions.beforeで事前権限チェック（integration_id確認）

#### Step 3: Webhookサービスを理解する

Webhook作成時のビジネスロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | webhooks-service.js | `ghost/core/core/server/services/webhooks/webhooks-service.js` | Webhookサービス |

**主要処理フロー**:
- **18-29行目**: add関数で重複チェック
- **25-28行目**: 重複がある場合はValidationError
- **31-48行目**: WebhookModel.addでDB保存、外部キー制約エラーハンドリング

#### Step 4: イベントリスニングを理解する

Ghostイベントとの連携を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | listen.js | `ghost/core/core/server/services/webhooks/listen.js` | イベントリスナー登録 |

**主要処理フロー**:
- **10-45行目**: WEBHOOKS配列で対応イベントを定義
- **47-68行目**: listen関数で各イベントにリスナーを登録
- **59-66行目**: イベント発生時にwebhookTrigger.triggerを呼び出し
- **61-63行目**: インポート時はトリガーをスキップ

#### Step 5: Webhookトリガーを理解する

HTTPリクエスト送信処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | webhook-trigger.js | `ghost/core/core/server/services/webhooks/webhook-trigger.js` | Webhookトリガー実装 |

**主要処理フロー**:
- **23-49行目**: getAll関数でイベントに該当するWebhookを取得
- **51-61行目**: update関数でトリガー結果を記録
- **64-70行目**: destroy関数で410レスポンス時にWebhook削除
- **98-143行目**: trigger関数でHTTPリクエスト送信
- **116-124行目**: ヘッダー設定（Content-Type, Content-Version）
- **122-124行目**: 署名生成（secretがある場合）

### プログラム呼び出し階層図

```
[Webhook登録]
API Request (POST /ghost/api/admin/webhooks/)
    │
    ├─ webhooks.js (controller)
    │      └─ webhooksService.add()
    │             ├─ Webhook.getByEventAndTarget() - 重複チェック
    │             └─ Webhook.add() - DB保存
    │
    └─ Response (JSON)

[Webhookトリガー]
Ghost Event (e.g., post.published)
    │
    ├─ listen.js (イベントリスナー)
    │      └─ webhookTrigger.trigger(event, model)
    │             │
    │             ├─ getAll(event) - 該当Webhook取得
    │             │
    │             ├─ payload(event, model) - ペイロード生成
    │             │
    │             └─ request(url, opts) - HTTPリクエスト送信
    │                    │
    │                    ├─ onSuccess() - 成功記録
    │                    │
    │                    └─ onError() - エラー記録/410時削除
    │
    └─ 外部Webhookエンドポイント
```

### データフロー図

```
[Webhookトリガーのデータフロー]

[入力]                      [処理]                           [出力]

Ghostイベント ─────────────▶ ┌──────────────────────────┐
(post.published等)          │   listen.js              │
                           │          │               │
モデルデータ ───────────────▶ │          ▼               │
                           │   webhook-trigger.js     │
                           │          │               │
                           │          ├─ Webhook検索   │
                           │          │               │
                           │          ├─ ペイロード生成  │
                           │          │               │
                           │          └─ 署名生成      │
                           └──────────────────────────┘
                                       │
                                       ▼
                           ┌──────────────────────────┐
                           │  HTTP POST Request       │
                           │  {                       │ ───▶ 外部URL
                           │    body: JSON payload,   │
                           │    headers: {            │
                           │      Content-Type,       │
                           │      Content-Version,    │
                           │      X-Ghost-Signature   │
                           │    }                     │
                           │  }                       │
                           └──────────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| webhook.js | `ghost/core/core/server/models/webhook.js` | ソース | Webhookモデル定義 |
| webhooks.js | `ghost/core/core/server/api/endpoints/webhooks.js` | ソース | APIエンドポイント |
| webhooks-service.js | `ghost/core/core/server/services/webhooks/webhooks-service.js` | ソース | Webhookサービス |
| listen.js | `ghost/core/core/server/services/webhooks/listen.js` | ソース | イベントリスナー |
| webhook-trigger.js | `ghost/core/core/server/services/webhooks/webhook-trigger.js` | ソース | Webhookトリガー |
| payload.js | `ghost/core/core/server/services/webhooks/payload.js` | ソース | ペイロード生成 |
| serialize.js | `ghost/core/core/server/services/webhooks/serialize.js` | ソース | シリアライズ |
| index.js | `ghost/core/core/server/services/webhooks/index.js` | ソース | サービスエクスポート |
| schema.js | `ghost/core/core/server/data/schema/schema.js` | 設定 | DBスキーマ定義 |
