> This page is for プラットフォーム, version レガシー.
> For other versions, use one of these documentation indexes:
> - V4 (default): https://next.developer.frame.io/platform/v4/llms.txt
> - V4 Experimental: https://next.developer.frame.io/platform/v4-experimental/llms.txt
> - レガシー: https://next.developer.frame.io/platform/v2/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://next.developer.frame.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://next.developer.frame.io/_mcp/server.

# Webhook の概要

<Info title="サンプルアプリケーション">
Frame.io Webhook 用に独自のコンシューマーを構築したい場合は、[GitHub 上のサンプルアプリ](https://github.com/Frameio/webhooks-example-app)を入手して拡張してください。
</Info>
## 利用開始
Webhook は、Frame.io 内で発生するイベントを活用して、外部システムに処理、API コールバック、そして最終的にはワークフローの自動化用に送信できる通知に変換する方法を提供します。
## セットアップ 
Webhook は[開発者サイトの webhook エリア](https://developer.frame.io/app/webhooks)で設定できます。Webhook には以下が必要です。

* 名前 - 開発者サイトにのみ表示されます。
* URL - イベントの配信先。
* チーム - この Webhook を追加するチーム。
* イベント - Webhook をトリガーするイベント。

## サポートされているイベント
1 つの Webhook で、次の、任意の数のイベントにサブスクライブできます。

### プロジェクト

| イベント| トリガー|
|----------|----------|
| `project.created`| 新しいプロジェクトが作成された|
| `project.updated`| プロジェクトの設定が更新された|
| `project.deleted`| プロジェクトが削除された|

### アセット
| イベント| トリガー|
|----------|----------|
| `asset.created`| アセットが最初に Frame.io で追加／作成されたが、アセットが完全にアップロードされる前の可能性がある|
| `asset.copied`| アセットがコピーされた|
| `asset.updated`| アセットの説明、名前、または他のファイル情報が変更された|
| `asset.deleted`| アセットが（手動または他の方法で）削除された|
| `asset.ready`| アセットがアップロードおよび処理された後、すべてのトランスコードが完了した|
| `asset.label.updated`| アセットのステータスラベルが設定、変更または削除された|
| `asset.versioned`| アセットがバージョン管理された|

<Warning title="アセットバージョン管理">
`asset.versioned` イベントが発生すると、バージョンスタック自体ではなく、バージョン管理されたアセットの ID を含むペイロードが返されます。したがって、その `id` をバージョンスタックの ID とみなして別の関数に渡せると考えている場合は、まずその特定の &apos;parent&apos; リソースを検索して突き止める必要があります。
</Warning>

<Warning title="アセットラベルの更新">
パブリック API（`BES-408`）を介して `/v2/assets/:id` エンドポイントへの `PUT` 呼び出しによってステータスラベルが変更されても、`asset.label.updated` イベントは発生しません。ただし、ステータスラベルの更新にネイティブの Frame.io アプリと統合（web、iOS、Premiere、After Effects、FCPX など）が使用された場合は発生します。
</Warning>

### コメント
| イベント| トリガー|
|----------|----------|
| `comment.created`| 新しいコメントまたは返信が作成された|
| `comment.updated`| コメントが編集された|
| `comment.deleted`| コメントが削除された|
| `comment.completed`| コメントが完了した|
| `comment.uncompleted`| コメントが未完了|

### レビューリンク
| イベント| トリガー|
|----------|----------|
| `reviewlink.created`| 新しいレビューリンクが作成された|
### 共同作業者

| イベント| トリガー|
|----------|----------|
| `collaborator.created`| 共同作業者がアカウントに追加された|
| `collaborator.deleted`| アカウントから共同作業者が削除された|

### チームメンバー
| イベント| トリガー|
|----------|----------|
| `teammember.created`| チームメンバーがアカウントに追加された|
| `teammember.deleted`| アカウントからチームメンバーが削除された|

## ペイロード
Frame.io は、JSON ペイロードを、指定された Webhook エンドポイントに配信します。asset.created イベントのペイロードの例を次に示します。 **

```json
{
  "type": "asset.created",
  "resource": {
    "type": "asset",
    "id": "<asset-id>"
  },
  "user": {
    "id": "<user-id>"
  },
  "team": {
    "id": "<team-id>"
  }
}
```

すべてのペイロードには、`type` フィールド（発生するイベントのタイプを示します）と `resource` オブジェクトが含まれています。`resource` オブジェクトは、このイベントに関連するリソースの `type` と `id` を指定します。

上記の *asset.created* イベントの例では、これは、新しく作成されたアセットの `id` になります。さらに、`user` および `team` オブジェクトも含まれます。これらは、イベントをトリガーしたユーザーと、リソースのチームコンテキストを参照します。

ユーザーとチームの直接的なコンテキストを除き、**サブスクライブされたリソースに関する追加情報は含まれません**。アプリケーションで追加情報やコンテキストが必要な場合、HTTP API を使用してフォローアップリクエストを行うことをお勧めします。
## 再試行
サービスへの Webhook の配信中にエラー（200 以外のステータスコード応答）またはタイムアウトが発生した場合、ペイロードが 3 回再試行され、合計 4 回の配信試行が行われます。
## セキュリティ
デフォルトでは、すべての Webhook に署名キーが提供されます。これは設定できません。このキーを使用して、リクエストが Frame.io から発生していることを確認できます。
### Webhook 署名の確認
中間者攻撃や反射攻撃から統合を保護するには、Webhook 署名を確認することが不可欠です。確認により、Webhook ペイロードが実際に Frame.io によって送信され、トランスポートでペイロードコンテンツが変更されていないことが確認されます。

`POST` リクエストには、次のヘッダーが含まれます。

| 名前| 説明|
|----------|----------|
| `X-Frameio-Request-Timestamp`| Webhook 配信の時刻|
| `X-Frameio-Signature`| 計算された署名|

**タイムスタンプ**は、Frame.io のシステムからの配信の時刻です。これは、リプレイ攻撃を防ぐために使用できます。この時刻が現地時間から 5 分以内であることを確認することをお勧めします。

**署名**は、Webhook が最初に作成されたときに提供された署名キーを使用する HMAC SHA256 ハッシュです。

署名を確認するには、次の手順に従います。

1. HTTP ヘッダーから署名を抽出します。
2. 次のようにバージョン、配信時刻、およびリクエスト本文を組み合わせることで、署名するメッセージを作成します：`v0:timestamp:body`
3. 署名シークレットを使用して、HMAC SHA256 署名を計算します。  _注意：指定された署名には、接頭辞 `v0=` が付きます。現在、Frame.io で署名リクエスト用に用意されているのはこれだけです。この接頭辞が計算された署名の先頭に追加されていることを確認してください。_
4. 比較します。

**`Python`**

```python title="Python"
import hmac
import hashlib

def verify_signature(curr_time, req_time, signature, body, secret):
    """
    Verify Webhook signature
    :Args:
        curr_time (float): Current epoch time
        req_time (float): Request epoch time
        signature (str): Signature provided by the Frame.io API for the given request
        body (str): Webhook body from the received POST
        secret (str): The secret for this Webhook that you saved when you first created it
    """
    if int(curr_time) - int(req_time) < 500:
        message = 'v0:{}:{}'.format(req_time, body)
        calculated_signature = 'v0={}'.format(hmac.new(
            bytes(secret, 'latin-1'),
            msg=bytes(message, 'latin-1'),
            digestmod=hashlib.sha256).hexdigest())
        if calculated_signature == signature:
            return True
    return False
```

```js
const crypto = require('crypto');

// Capture the signature, secret, timestamp and payload from a new webhook event:
const 
signature = 'v0=a77ce6856e609c884575c2fd211d07a9ad1c3f72e19c06ff710e8f086ffca883', 
secret = 'yxSE59T0gtZOFZxw6UhLwTkhd2m8ntNSdSWnApQ0xOnMEzSoXbD8sGFP4bzb7MbS',
timestamp = 1604004499,  // UNIX timestamp in seconds
payload = {
	"project": {
		"id": "f348e9f4-f142-42f9-b3bf-478d93f0feb4"
	},
	"resource": {
		"id": "6aad9151-c216-4d6f-b5e9-530df551a426",
		"type": "asset"
	},
	"team": {
		"id": "aa891687-4b1e-4150-9b6d-9e4911c5b436"
	},
	"type": "asset.label.updated",
	"user": {
		"id": "59c9ade1-311b-4c3b-8231-b9d88e9a1a85"
	}
},
body = JSON.stringify(payload),

// Validate that caught payload is not older than 5 minutes
currentTimeUTC = (new Date()).getTime(),
currentTimestamp = currentTimeUTC / 1000, // JavaScript uses milliseconds whereas Unix Time is in seconds.
minutes = 5, 
expired = (currentTimestamp - timestamp) > minutes*60
hmac1 = crypto.createHmac('sha256', secret),
generateSignature = hmac1.update(`v0:${timestamp}:${body}`).digest('hex')

// Evaluates to true if the webhook is verified
console.log(!expired && signature === `v0=${generateSignature}`)
```

```go
// Full Go sample code: https://github.com/Frameio/webhooks-example-app/blob/master/main.go

func handler(w http.ResponseWriter, r *http.Request) {
	out, err := httputil.DumpRequest(r, true)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	log.Println(string(out))

	// Verify the message has been delivered in the last 5 minutes.
	timestampStr := r.Header.Get("X-Frameio-Request-Timestamp")
	timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	if time.Since(time.Unix(timestamp, 0)) > 5*time.Minute {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// Verify request signature.
	expected := r.Header.Get("X-Frameio-Signature")
	signature, _ := computeSignature(r, timestamp, secretKey)
	if expected != signature {
		w.WriteHeader(http.StatusUnauthorized)
		return
	}

	var event *Event
	decoder := json.NewDecoder(r.Body)
	err = decoder.Decode(&event)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	// Handle webhook here.
	log.Println(event.ID)

	w.WriteHeader(http.StatusOK)
}

// The request includes headers to enable the recipient to validate
// that the request is from Frame.io and that it's been delivered within
// the expected time range. To learn more about how this works, take a
// look at our docs https://docs.frame.io/docs/webhooks#section-security.
func computeSignature(r *http.Request, timestamp int64, secret string) (string, error) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return "", err
	}
	copy := body[:]
	r.Body = ioutil.NopCloser(bytes.NewReader(copy))

	msg := fmt.Sprintf("%s:%d:%s", version, timestamp, string(body))

	key := []byte(secret)
	h := hmac.New(sha256.New, key)
	h.Write([]byte(msg))

	result := fmt.Sprintf("%s=%s", version, hex.EncodeToString(h.Sum(nil)))

	return result, nil
}
```