跳转到内容
搜索文档

使用 webhook

最后更新 查看 MarkdownAgent 设置

Webhook 会在视频成功完成处理并可供流式传输时,或视频进入错误状态时通知您的服务。

订阅 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>"}'
示例响应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 – 当至少一个质量级别已编码并可供流式传输时返回 true

  • status – 处理状态。

    • state – 当视频处理完成且所有质量级别均已编码时返回 ready

    • pctComplete – 处理完成的百分比。当此值达到 100 时,所有质量级别均可用。

  • meta – 与上传文件关联的元数据。

  • created – 表示视频记录创建时间的时间戳。

错误代码

如果视频无法成功处理,state 字段返回 errorerrReasonCode 返回以下列出的值之一。

  • 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 才能播放。

错误响应示例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 时间sig1 是请求体的签名。

此时,您应丢弃对应用程序而言时间戳过旧的请求。

2. 创建签名源字符串

准备签名源字符串并连接以下字符串:

  • time 字段的值,例如 1230811200
  • 字符 .
  • Webhook 请求体(如适用,包含换行符)

请求体中的每个字节必须保持未更改,才能成功验证签名。

3. 创建预期签名

使用 SHA256 函数(HMAC-SHA256)和 webhook 密钥以及步骤 2 中的源字符串计算 HMAC。 此步骤取决于您的应用程序使用的编程语言。

Cloudflare 的签名将编码为十六进制。

4. 比较预期签名和实际签名

将请求头中的签名与预期签名进行比较。建议使用恒定时间比较函数来比较签名。

如果签名匹配,您可以信任该 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("");

这篇文档对您有帮助吗?