# 通知設計書 28-post.published

## 概要

本ドキュメントは、Ghost CMSにおける投稿公開時のWebhook通知（`post.published`イベント）の設計仕様を記載する。

### 本通知の処理概要

投稿（Post）が公開された際にトリガーされるWebhook通知である。下書きから公開、または予約投稿の公開時に発火し、外部システムに新規公開コンテンツを通知する。SNS自動投稿、メールマガジン配信、検索インデックス登録などの自動化に活用される。

**業務上の目的・背景**：コンテンツ公開は読者に対する最も重要なアクションである。公開タイミングで外部システムに通知することで、TwitterやFacebookへの自動投稿、RSSフィードの更新通知、Slackへの共有、検索エンジンへのインデックス登録依頼など、コンテンツ配信ワークフローを自動化できる。

**通知の送信タイミング**：投稿の`status`が`published`に変更され、`post.published`イベントが発行された時点で、登録されているすべてのWebhookに対してHTTPリクエストが送信される。

**通知の受信者**：Ghost管理画面のIntegration設定でこのイベントに対して登録されたWebhookエンドポイント。

**通知内容の概要**：公開された投稿の完全なJSONデータ（current）と、変更されたフィールドの公開前データ（previous）が含まれる。タグ、著者情報も関連データとして含まれる。

**期待されるアクション**：Webhook受信側システムは、公開データを受け取り、SNS投稿、メール配信、インデックス登録などを実行する。

## 通知種別

Webhook（HTTP POST）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（イベントリスナー経由） |
| 優先度 | 高（コンテンツ配信に直結） |
| リトライ | 5回（テスト環境では0回） |

### 送信先決定ロジック

1. `webhooks`テーブルから`event = 'post.published'`のレコードを全件取得
2. `customIntegrations`プラン制限が有効な場合、`internal`タイプのIntegrationに紐づくWebhookのみ対象
3. 各Webhookの`target_url`に対してHTTPリクエストを送信

## 通知テンプレート

### Webhook通知の場合

| 項目 | 内容 |
|-----|------|
| HTTPメソッド | POST |
| Content-Type | `application/json` |
| Content-Version | `v{ghostVersion.safe}` |
| タイムアウト | 2000ms |

### ペイロード構造

```json
{
    "post": {
        "current": {
            "id": "5ddc9141c35e7700383b2937",
            "uuid": "a5aa9bd8-ea31-415c-b452-3040dae1e730",
            "title": "Published Post Title",
            "slug": "published-post-title",
            "html": "<p>Post content...</p>",
            "plaintext": "Post content...",
            "feature_image": "https://example.com/image.jpg",
            "featured": false,
            "status": "published",
            "visibility": "public",
            "created_at": "2024-01-01T00:00:00.000Z",
            "updated_at": "2024-01-02T00:00:00.000Z",
            "published_at": "2024-01-02T00:00:00.000Z",
            "tags": [...],
            "authors": [...]
        },
        "previous": {
            "status": "draft",
            "published_at": null,
            "updated_at": "2024-01-01T00:00:00.000Z"
        }
    }
}
```

### 署名ヘッダー（オプション）

Webhookに`secret`が設定されている場合：

```
X-Ghost-Signature: sha256={HMAC-SHA256(payload + timestamp)}, t={timestamp}
```

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| post.current | 公開後の投稿データ | Postモデル（シリアライズ済み） | Yes |
| post.previous | 公開前データ（変更フィールドのみ） | Postモデル._previousAttributes | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| モデルイベント | `post.published` | Webhookが登録されている | 投稿公開時に自動送信 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| Webhook未登録 | このイベントに対するWebhookが1件も登録されていない場合 |
| インポート時 | `options.importing === true`の場合は送信しない |
| プラン制限 | `customIntegrations`制限時、internalタイプ以外のIntegrationのWebhookは除外 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[status → published変更] --> B{importingフラグ?}
    B -->|true| C[終了]
    B -->|false| D[events.emit post.published]
    D --> E[WebhookTrigger.trigger]
    E --> F[getAll - Webhook取得]
    F --> G{Webhook数確認}
    G -->|0件| C
    G -->|1件以上| H[payloadシリアライズ]
    H --> I[各Webhookにループ]
    I --> J[HTTPリクエスト送信]
    J --> K{送信結果}
    K -->|成功| L[onSuccess]
    K -->|410| M[Webhook削除]
    K -->|エラー| N[onError]
    L --> O{次のWebhook}
    M --> O
    N --> O
    O -->|あり| I
    O -->|なし| C
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| webhooks | 登録Webhook取得 | `event = 'post.published'`でフィルタ |
| integrations | Integration情報 | プラン制限時の判定用 |
| posts | 投稿データ | イベント引数から取得 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| webhooks | UPDATE | 送信結果の記録 |
| webhooks | DELETE | 410レスポンス時の自動削除 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 410 Gone | Webhookエンドポイントが410を返した | Webhook自動削除 |
| タイムアウト | 2000ms以内にレスポンスなし | エラー記録、リトライ |
| ネットワークエラー | 接続失敗 | エラー記録、リトライ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 5回（本番）、0回（テスト環境） |
| リトライ間隔 | @tryghost/requestライブラリのデフォルト |
| リトライ対象エラー | タイムアウト、ネットワークエラー、5xxエラー |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | なし |
| 1日あたり上限 | なし |

### 配信時間帯

制限なし（イベント発生時に即時送信）

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

- Webhook URLはIntegration管理者のみが設定可能
- `secret`設定時はHMAC-SHA256署名をヘッダーに付与
- 公開コンテンツのため、データの機密性は低いが、URLの漏洩に注意

## 備考

- このイベントは`post.edited`とは別に発火する
- Slack通知（No.23）も同じ`post.published`イベントをリッスンしている
- 予約投稿の公開時も同様に発火する
- `post.published.edited`（No.29）は公開済み投稿の編集時に発火し、このイベントとは異なる

---

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

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

### 推奨読解順序

#### Step 1: イベント定義を確認する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | listen.js | `ghost/core/core/server/services/webhooks/listen.js` | 16行目、`post.published`イベント定義 |

#### Step 2: シリアライズ処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | serialize.js | `ghost/core/core/server/services/webhooks/serialize.js` | 10-12行目、posts用フォーマット設定 |

**読解のコツ**:
- `POST_FORMATS = ['html', 'plaintext']` でHTMLとプレーンテキストの両方を含む
- `POST_WITH_RELATED = ['tags', 'authors']` でタグと著者情報を含む

#### Step 3: Webhook送信処理（共通）

No.25「post.added」と同じ処理フローを参照。

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

```
Post status → published
    │
    └─ events.emit('post.published')
           │
           ├─ slackListener (No.23 Slack通知)
           │
           └─ processWebhookTrigger(model, options)
                  │
                  └─ webhookTrigger.trigger('post.published', model)
                         │
                         ├─ getAll('post.published')
                         │
                         ├─ payload(event, model)
                         │      └─ serialize(event, model)
                         │             ├─ formats: ['html', 'plaintext']
                         │             └─ withRelated: ['tags', 'authors']
                         │
                         └─ request(url, opts)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| listen.js | `ghost/core/core/server/services/webhooks/listen.js` | ソース | イベントリスナー登録 |
| webhook-trigger.js | `ghost/core/core/server/services/webhooks/webhook-trigger.js` | ソース | Webhook送信ロジック |
| serialize.js | `ghost/core/core/server/services/webhooks/serialize.js` | ソース | ペイロードシリアライズ |
| slack.js | `ghost/core/core/server/services/slack.js` | ソース | 同イベントをリッスン |
