← Cloudflare Stream / stream / examples
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:
- Spusťte Cloudflare Tunnel a získejte tak veřejnou URL adresu pro své lokální prostředí.
- Tuto URL adresu zaregistrujte jako svůj koncový bod webhooku. Tím získáte webhookový tajný klíč.
- Vytvořte Cloudflare Worker, který přijímá webhookové události Stream a ověřuje jejich podpisy.
Předpoklady
- A účet Cloudflare ↗ se zapnutou funkcí Stream
- Node.js ↗ (v18 nebo novější)
- Wrangler CLI nainstalovaný (
npm install -g wrangler)
1. Vytvořte projekt Worker
Vytvořte nový projekt Worker, který bude přijímat webhookové požadavky:
npm create cloudflare@latest stream-webhook-handler2. 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:8787cloudflared vypíše veřejnou URL adresu podobnou této:
https://example-words-here.trycloudflare.comZkopí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é:Stream Write
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:
{
"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:
WEBHOOK_SECRET=85011ed3a913c6ad5f9cf6c5573cc0a7Hodnotu 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.
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 devWrangler 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 deployPoté 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é:Stream Write
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"
}'