Webhook 会在视频成功完成处理并可供流式传输时,或视频进入错误状态时通知您的服务。
要在您的服务上订阅 webhook 通知或修改现有订阅,请在 Cloudflare 仪表板的 Account API tokens(账户 API 令牌) 页面生成 API token。
Go to 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– 当至少一个质量级别已编码并可供流式传输时返回true。 -
status– 处理状态。-
state– 当视频处理完成且所有质量级别均已编码时返回ready。 -
pctComplete– 处理完成的百分比。当此值达到100时,所有质量级别均可用。
-
-
meta– 与上传文件关联的元数据。 -
created– 表示视频记录创建时间的时间戳。
如果视频无法成功处理,state 字段返回 error,errReasonCode 返回以下列出的值之一。
ERR_NON_VIDEO– 上传的不是视频。ERR_DURATION_EXCEED_CONSTRAINT– 视频时长超过创作者直接上传中定义的约束。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 时间 ↗。sig1 是请求体的签名。
此时,您应丢弃对应用程序而言时间戳过旧的请求。
准备签名源字符串并连接以下字符串:
time字段的值,例如1230811200- 字符
. - Webhook 请求体(如适用,包含换行符)
请求体中的每个字节必须保持未更改,才能成功验证签名。
使用 SHA256 函数(HMAC-SHA256)和 webhook 密钥以及步骤 2 中的源字符串计算 HMAC。 此步骤取决于您的应用程序使用的编程语言。
Cloudflare 的签名将编码为十六进制。
将请求头中的签名与预期签名进行比较。建议使用恒定时间比较函数来比较签名。
如果签名匹配,您可以信任该 webhook 由 Cloudflare 发送。
- Webhook 仅在视频处理完成后发送,请求体将指示视频处理是否成功或失败。
- 每个账户仅允许一个 webhook 订阅。
- Cloudflare 无法向
localhost或本地 IP 地址发送 webhook。需要可公开访问的 URL。对于本地测试,使用快速隧道将本地服务器暴露到 Internet。分步教程请参阅在本地测试 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("");