INTEGRITY Dokumentace

Otestujte webhooky lokálně

Cloudflare Stream neumí odesílat webhooková oznámení na localhost ani lokální IP adresy. Pro testování webhooků během místního vývoje potřebujete veřejně přístupnou URL adresu, která přeposílá požadavky na váš lokální počítač.

Tento příklad ukazuje, jak:

  1. Spusťte Cloudflare Tunnel a získejte tak veřejnou URL adresu pro své lokální prostředí.
  2. Tuto URL adresu zaregistrujte jako svůj koncový bod webhooku. Tím získáte webhookový tajný klíč.
  3. Vytvořte Cloudflare Worker, který přijímá webhookové události Stream a ověřuje jejich podpisy.

Předpoklady

1. Vytvořte projekt Worker

Vytvořte nový projekt Worker, který bude přijímat webhookové požadavky:

npm create cloudflare@latest stream-webhook-handler

2. Spusťte Cloudflare Tunnel

Před registrací URL adresy webhooku potřebujete veřejnou URL adresu, která odkazuje na váš lokální počítač. V terminálu spusťte quick tunnel která přesměrovává na výchozí port vývojového serveru Wrangler (8787):

npx cloudflared tunnel --url http://localhost:8787

cloudflared vypíše veřejnou URL adresu podobnou této:

https://example-words-here.trycloudflare.com

Zkopírujte tuto URL adresu. Při každém restartu tunelu se mění.

3. Zaregistrujte URL adresu tunelu jako koncový bod webhooku

Pomocí Stream API nastavte URL tunelu jako svou oznamovací URL webhooku. Odpověď API obsahuje secret pole: budete jej potřebovat k ověření podpisů webhooků.

Požadovaná oprávnění API tokenu

Alespoň jeden z následujících oprávnění tokenu je povinné:
Vytváření webhooků
curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/stream/webhook" \
	--request PUT \
	--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
	--json '{
		"notificationUrl": "https://example-words-here.trycloudflare.com"
	}'

Odpověď bude obsahovat secret pole:

Ukázková odpověď
{
	"result": {
		"notificationUrl": "https://example-words-here.trycloudflare.com",
		"modified": "2024-01-01T00:00:00.000000Z",
		"secret": "85011ed3a913c6ad5f9cf6c5573cc0a7"
	},
	"success": true,
	"errors": [],
	"messages": []
}

Uložte secret hodnotu. Použijete ji v dalším kroku.

4. Uložte webhook secret pro místní vývoj

Vytvořte .dev.vars soubor v kořenovém adresáři vašeho projektu Worker a přidejte webhook secret z odpovědi API:

.dev.vars
WEBHOOK_SECRET=85011ed3a913c6ad5f9cf6c5573cc0a7

Hodnotu nahraďte skutečným tajným klíčem z kroku 3. Wrangler automaticky načítá .dev.vars při spouštění wrangler dev.

5. Přidejte obslužnou rutinu webhooku

Nahraďte obsah src/index.ts ve svém projektu Worker následujícím kódem. Tento Worker přijímá webhook POST požadavky, ověřuje podpis, a zaznamená datový obsah (payload) do protokolu.

src/index.ts
export interface Env {
	WEBHOOK_SECRET: string;
}

async function verifyWebhookSignature(
	request: Request,
	secret: string,
): Promise<{ valid: boolean; body: string }> {
	const signatureHeader = request.headers.get("Webhook-Signature");
	if (!signatureHeader) {
		return { valid: false, body: "" };
	}

	const body = await request.text();

	// Parse "time=<unix_ts>,sig1=<hex_signature>"
	const parts = Object.fromEntries(
		signatureHeader.split(",").map((part) => {
			const [key, value] = part.split("=");
			return [key, value];
		}),
	);

	const time = parts["time"];
	const receivedSig = parts["sig1"];

	if (!time || !receivedSig) {
		return { valid: false, body };
	}

	// Build the source string: "<time>.<body>"
	const sourceString = `${time}.${body}`;
	const encoder = new TextEncoder();

	const key = await crypto.subtle.importKey(
		"raw",
		encoder.encode(secret),
		{ name: "HMAC", hash: "SHA-256" },
		false,
		["sign"],
	);

	const signature = await crypto.subtle.sign(
		"HMAC",
		key,
		encoder.encode(sourceString),
	);

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

	// Use a timing-safe comparison.
	// Do not return early when lengths differ — that leaks the expected
	// signature's length through timing.  Compare against self and negate instead.
	const expectedBytes = encoder.encode(expectedSig);
	const receivedBytes = encoder.encode(receivedSig);

	const lengthsMatch = expectedBytes.byteLength === receivedBytes.byteLength;
	const signaturesMatch = lengthsMatch
		? crypto.subtle.timingSafeEqual(expectedBytes, receivedBytes)
		: !crypto.subtle.timingSafeEqual(expectedBytes, expectedBytes);

	return { valid: signaturesMatch, body };
}

export default {
	async fetch(request: Request, env: Env): Promise<Response> {
		if (request.method !== "POST") {
			return new Response("Method not allowed", { status: 405 });
		}

		if (!env.WEBHOOK_SECRET) {
			console.error("WEBHOOK_SECRET is not set");
			return new Response("Server misconfigured", { status: 500 });
		}

		const { valid, body } = await verifyWebhookSignature(
			request,
			env.WEBHOOK_SECRET,
		);

		if (!valid) {
			console.error("Invalid webhook signature");
			return new Response("Invalid signature", { status: 403 });
		}

		console.log("Webhook signature verified successfully");

		const payload = JSON.parse(body);

		console.log("Stream webhook received:", JSON.stringify(payload, null, 2));
		console.log("Video UID:", payload.uid);
		console.log("Status:", payload.status?.state);
		console.log("Ready to stream:", payload.readyToStream);

		// Add your own processing logic here — for example, update a database
		// or notify a downstream service.

		return new Response("OK", { status: 200 });
	},
} satisfies ExportedHandler<Env>;

6. Spusťte lokální vývojový server

V samostatném terminálu (tunel ponechte spuštěný) spusťte Worker lokálně pomocí Wrangler:

npx wrangler dev

Wrangler načte WEBHOOK_SECRET z vašeho .dev.vars soubor automaticky.

7. Vyvolejte testovací událost

Nahrajte video do Stream a tím spustíte webhookovou událost. Jakmile se zpracování videa dokončí, uvidíte payload webhooku zaznamenaný v terminálu, ve kterém běží wrangler dev, spolu s potvrzením, že podpis byl ověřen.

Přechod do produkce

Jakmile dokončíte místní testování, nasaďte Worker a aktualizujte adresu URL webhooku na svůj produkční endpoint:

npx wrangler deploy

Poté aktualizujte odběr webhooku tak, aby směřoval na adresu URL nasazeného Workeru:

Požadovaná oprávnění API tokenu

Alespoň jeden z následujících oprávnění tokenu je povinné:
Vytváření webhooků
curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/stream/webhook" \
	--request PUT \
	--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
	--json '{
		"notificationUrl": "https://your-worker.your-subdomain.workers.dev"
	}'