Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

Webhook を使う

最終更新 Markdown で表示Agent セットアップ

Webhook は、動画の処理が正常に終わり再生できる状態になったとき、または動画がエラー状態になったときに、サービスへ通知します。

Webhook 通知を購読する

サービスで Webhook 通知を受け取る、または既存の購読を変更するには、Cloudflare ダッシュボードの Account API tokens ページで API トークンを生成します。

Account API tokens を開く ↗

Webhook 通知 URL にはプロトコルを含めます。使えるのは http:// または https:// のみです。

curl -X PUT --header 'Authorization: Bearer <API_TOKEN>' \
https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/stream/webhook \
--data '{"notificationUrl":"<WEBHOOK_NOTIFICATION_URL>"}'
レスポンスの例json
{
	"result": {
		"notificationUrl": "http://www.your-service-webhook-handler.com",
		"modified": "2019-01-01T01:02:21.076571Z",
		"secret": "85011ed3a913c6ad5f9cf6c5573cc0a7"
	},
	"success": true,
	"errors": [],
	"messages": []
}

通知

アカウント上の動画の処理が完了すると、その動画の情報を含む POST リクエスト通知を受け取ります。

エンコード成功時に送られる POST リクエスト本文の例json
{
	"uid": "6b9e68b07dfee8cc2d116e4c51d6a957",
	"creator": null,
	"thumbnail": "https://customer-f33zs165nr7gyfy4.cloudflarestream.com/6b9e68b07dfee8cc2d116e4c51d6a957/thumbnails/thumbnail.jpg",
	"thumbnailTimestampPct": 0,
	"readyToStream": true,
	"status": {
		"state": "ready",
		"pctComplete": "39.000000",
		"errorReasonCode": "",
		"errorReasonText": ""
	},
	"meta": {
		"filename": "small.mp4",
		"filetype": "video/mp4",
		"name": "small.mp4",
		"relativePath": "null",
		"type": "video/mp4"
	},
	"created": "2022-06-30T17:53:12.512033Z",
	"modified": "2022-06-30T17:53:21.774299Z",
	"size": 383631,
	"preview": "https://customer-f33zs165nr7gyfy4.cloudflarestream.com/6b9e68b07dfee8cc2d116e4c51d6a957/watch",
	"allowedOrigins": [],
	"requireSignedURLs": false,
	"uploaded": "2022-06-30T17:53:12.511981Z",
	"uploadExpiry": "2022-07-01T17:53:12.511973Z",
	"maxSizeBytes": null,
	"maxDurationSeconds": null,
	"duration": 5.5,
	"input": {
		"width": 560,
		"height": 320
	},
	"playback": {
		"hls": "https://customer-f33zs165nr7gyfy4.cloudflarestream.com/6b9e68b07dfee8cc2d116e4c51d6a957/manifest/video.m3u8",
		"dash": "https://customer-f33zs165nr7gyfy4.cloudflarestream.com/6b9e68b07dfee8cc2d116e4c51d6a957/manifest/video.mpd"
	},
	"watermark": null
}
  • uid – 動画の一意な識別子です。
  • readytoStream – 少なくとも 1 つの画質レベルがエンコードされ、再生できる状態になると true を返します。
  • status – 処理ステータスです。
    • state – 動画の処理が終わり、すべての画質レベルがエンコードされると ready を返します。
    • pctComplete – 処理の完了割合です。100 になると、すべての画質レベルが利用できます。
  • meta – アップロードしたファイルに紐づくメタデータです。
  • created – 動画レコードが作成された日時です。

エラーコード

動画を正常に処理できなかった場合、state フィールドは error を返し、errReasonCode は次のいずれかの値を返します。

  • ERR_NON_VIDEO – アップロードが動画ではありません。
  • ERR_DURATION_EXCEED_CONSTRAINT – 動画の長さが、Direct Creator Upload で定義した制約を超えています。
  • ERR_FETCH_ORIGIN_ERROR – URL からの動画のダウンロードに失敗しました。
  • ERR_MALFORMED_VIDEO – ファイルとしては有効ですが、復旧できない破損データを含んでいます。
  • ERR_DURATION_TOO_SHORT – 動画の長さが 0.1 秒未満です。
  • ERR_UNKNOWN – Stream がエラーの原因を自動判定できない場合は、ERR_UNKNOWN コードを使います。

動画を再生するには、state フィールドに加えて、readyToStream フィールドも true である必要があります。

エラーレスポンスの例bash
{
  "readyToStream": false,
  "status": {
    "state": "error",
    "step": "encoding",
    "pctComplete": "39",
    "errReasonCode": "ERR_MALFORMED_VIDEO",
    "errReasonText": "The video was deemed to be corrupted or malformed.",
  }
}

Webhook の真正性を検証する

Cloudflare Stream は、通知 URL へ送る Webhook リクエストに署名し、各リクエストの署名を Webhook-Signature HTTP ヘッダーに含めます。これにより、アプリケーションは Webhook リクエストが Stream から送られたことを検証できます。

署名を検証するには、Webhook の署名用シークレットを取得します。この値は、Webhook の作成時または取得時の API レスポンスに含まれます。

署名を検証するには、Webhook-Signature ヘッダーの値を取得します。次の例に近い形式です。

Webhook-Signature: time=1230811200,sig1=60493ec9388b44585a29543bcf0de62e377d4da393246a8b1c901d0e3e672404

1. 署名を解析する

Webhook リクエストから Webhook-Signature ヘッダーを取得し、, で文字列を分割します。

各値を、さらに = で分割します。

time の値は、サーバーがリクエストを送った時点の UNIX time です。sig1 はリクエスト本文の署名です。

この時点で、アプリケーションにとって古すぎるタイムスタンプのリクエストは破棄してください。

2. 署名元の文字列を作る

署名元の文字列を用意し、次の文字列を連結します。

  • time フィールドの値(例: 1230811200
  • 文字 .
  • Webhook のリクエスト本文(該当する場合は改行文字も含む)

署名検証を成功させるには、リクエスト本文の各バイトを変更してはいけません。

3. 期待する署名を作る

ステップ 2 の元文字列と Webhook シークレットを使い、SHA256 関数による HMAC(HMAC-SHA256)を計算します。 この手順は、アプリケーションのプログラミング言語によって異なります。

Cloudflare の署名は hex でエンコードされます。

4. 期待する署名と実際の署名を比較する

リクエストヘッダーの署名と、期待する署名を比較します。可能であれば、定数時間の比較関数を使います。

署名が一致すれば、Webhook は Cloudflare から送られたと判断できます。

制限

  • Webhook は動画処理の完了後にのみ送られます。本文で、処理の成功または失敗が分かります。
  • Webhook の購読は、アカウントあたり 1 つだけです。
  • Cloudflare は localhost やローカル IP アドレスへ Webhook を送れません。公開アクセスできる URL が必要です。ローカルテストでは、Quick Tunnel でローカルサーバーをインターネットに公開します。手順は Webhook をローカルでテストする を参照してください。

Golang

crypto/hmac を使います。

package main

import (
 "crypto/hmac"
 "crypto/sha256"
 "encoding/hex"
 "log"
)

func main() {
 secret := []byte("secret from the Cloudflare API")
 message := []byte("string from step 2")

 hash := hmac.New(sha256.New, secret)
 hash.Write(message)

 hashToCheck := hex.EncodeToString(hash.Sum(nil))

 log.Println(hashToCheck)
}

Node.js

var crypto = require("crypto");

var key = "secret from the Cloudflare API";
var message = "string from step 2";

var hash = crypto.createHmac("sha256", key).update(message);

hash.digest("hex");

Ruby

    require 'openssl'

    key = 'secret from the Cloudflare API'
    message = 'string from step 2'

    OpenSSL::HMAC.hexdigest('sha256', key, message)

JavaScript(例: Cloudflare Workers で使う場合)

const key = "secret from the Cloudflare API";
const message = "string from step 2";

const getUtf8Bytes = (str) =>
	new Uint8Array(
		[...decodeURIComponent(encodeURIComponent(str))].map((c) =>
			c.charCodeAt(0),
		),
	);

const keyBytes = getUtf8Bytes(key);
const messageBytes = getUtf8Bytes(message);

const cryptoKey = await crypto.subtle.importKey(
	"raw",
	keyBytes,
	{ name: "HMAC", hash: "SHA-256" },
	true,
	["sign"],
);
const sig = await crypto.subtle.sign("HMAC", cryptoKey, messageBytes);

[...new Uint8Array(sig)].map((b) => b.toString(16).padStart(2, "0")).join("");

役に立ちましたか?