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>"}'{
"result": {
"notificationUrl": "http://www.your-service-webhook-handler.com",
"modified": "2019-01-01T01:02:21.076571Z",
"secret": "85011ed3a913c6ad5f9cf6c5573cc0a7"
},
"success": true,
"errors": [],
"messages": []
}アカウント上の動画の処理が完了すると、その動画の情報を含む POST リクエスト通知を受け取ります。
{
"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 である必要があります。
{
"readyToStream": false,
"status": {
"state": "error",
"step": "encoding",
"pctComplete": "39",
"errReasonCode": "ERR_MALFORMED_VIDEO",
"errReasonText": "The video was deemed to be corrupted or malformed.",
}
}Cloudflare Stream は、通知 URL へ送る Webhook リクエストに署名し、各リクエストの署名を Webhook-Signature HTTP ヘッダーに含めます。これにより、アプリケーションは Webhook リクエストが Stream から送られたことを検証できます。
署名を検証するには、Webhook の署名用シークレットを取得します。この値は、Webhook の作成時または取得時の API レスポンスに含まれます。
署名を検証するには、Webhook-Signature ヘッダーの値を取得します。次の例に近い形式です。
Webhook-Signature: time=1230811200,sig1=60493ec9388b44585a29543bcf0de62e377d4da393246a8b1c901d0e3e672404
Webhook リクエストから Webhook-Signature ヘッダーを取得し、, で文字列を分割します。
各値を、さらに = で分割します。
time の値は、サーバーがリクエストを送った時点の UNIX time ↗ です。sig1 はリクエスト本文の署名です。
この時点で、アプリケーションにとって古すぎるタイムスタンプのリクエストは破棄してください。
署名元の文字列を用意し、次の文字列を連結します。
timeフィールドの値(例:1230811200)- 文字
. - Webhook のリクエスト本文(該当する場合は改行文字も含む)
署名検証を成功させるには、リクエスト本文の各バイトを変更してはいけません。
ステップ 2 の元文字列と Webhook シークレットを使い、SHA256 関数による HMAC(HMAC-SHA256)を計算します。 この手順は、アプリケーションのプログラミング言語によって異なります。
Cloudflare の署名は hex でエンコードされます。
リクエストヘッダーの署名と、期待する署名を比較します。可能であれば、定数時間の比較関数を使います。
署名が一致すれば、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("");