Use webhooks
Last updated Sep 30, 2026 Webhooks notify your service when videos successfully finish processing and are ready to stream or if your video enters an error state.
Note
Webhooks works differently for live broadcasting. For more information, refer to Receive Live Webhooks.
Subscribe to webhook notifications
To subscribe to receive webhook notifications on your service or modify an existing subscription, generate an API token on the Account API tokens page of the Cloudflare dashboard.
The webhook notification URL must include the protocol. Only http:// or https:// is supported.
curl -X PUT --header 'Authorization: Bearer <API_TOKEN>' \
https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/stream/webhook \
--data '{"notificationUrl":"<WEBHOOK_NOTIFICATION_URL>"}'
Example responsejson
{
"result": {
"notificationUrl": "http://www.your-service-webhook-handler.com",
"modified": "2019-01-01T01:02:21.076571Z",
"secret": "85011ed3a913c6ad5f9cf6c5573cc0a7"
},
"success": true,
"errors": [],
"messages": []
}
Notifications
When a video on your account finishes processing, you will receive a POST request notification with information about the video.
Example POST request body sent in response to successful encodingjson
{
"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– The video's unique identifier.readytoStream– Returnstruewhen at least one quality level is encoded and ready to be streamed.status– The processing status.state– Returnsreadywhen a video is done processing and all quality levels are encoded.-
pctComplete– The percentage of processing that is complete. When this reaches100, all quality levels are available. TipIf you want to ensure the highest picture quality, enable video playback only when
stateisreadyandpctCompleteis100. -meta– Metadata associated with the uploaded file. -created– Timestamp indicating when the video record was created.
Error codes
If a video could not process successfully, the state field returns error, and the errReasonCode returns one of the values listed below.
ERR_NON_VIDEO– The upload is not a video.ERR_DURATION_EXCEED_CONSTRAINT– The video duration exceeds the constraints defined in the direct creator upload.ERR_FETCH_ORIGIN_ERROR– The video failed to download from the URL.ERR_MALFORMED_VIDEO– The video is a valid file but contains corrupt data that cannot be recovered.ERR_DURATION_TOO_SHORT– The video's duration is shorter than 0.1 seconds.ERR_UNKNOWN– If Stream cannot automatically determine why the video returned an error, theERR_UNKNOWNcode will be used.
In addition to the state field, a video's readyToStream field must also be true for a video to play.
Example error responsebash
{
"readyToStream": false,
"status": {
"state": "error",
"step": "encoding",
"pctComplete": "39",
"errReasonCode": "ERR_MALFORMED_VIDEO",
"errReasonText": "The video was deemed to be corrupted or malformed.",
}
}
Verify webhook authenticity
Cloudflare Stream will sign the webhook requests sent to your notification URLs and include the signature of each request in the Webhook-Signature HTTP header. This allows your application to verify the webhook requests are sent by Stream.
To verify a signature, you need to retrieve your webhook signing secret. This value is returned in the API response when you create or retrieve the webhook.
To verify the signature, get the value of the Webhook-Signature header, which will look similar to the example below.
Webhook-Signature: time=1230811200,sig1=60493ec9388b44585a29543bcf0de62e377d4da393246a8b1c901d0e3e672404
1. Parse the signature
Retrieve the Webhook-Signature header from the webhook request and split the string using the , character.
Split each value again using the = character.
The value for time is the current UNIX time ↗︎ when the server sent the request. sig1 is the signature of the request body.
At this point, you should discard requests with timestamps that are too old for your application.
2. Create the signature source string
Prepare the signature source string and concatenate the following strings:
- Value of the
timefield for example1230811200 - Character
. - Webhook request body (complete with newline characters, if applicable)
Every byte in the request body must remain unaltered for successful signature verification.
3. Create the expected signature
Compute an HMAC with the SHA256 function (HMAC-SHA256) using your webhook secret and the source string from step 2. This step depends on the programming language used by your application.
Cloudflare's signature will be encoded to hex.
4. Compare expected and actual signatures
Compare the signature in the request header to the expected signature. Preferably, use a constant-time comparison function to compare the signatures.
If the signatures match, you can trust that Cloudflare sent the webhook.
Limitations
- Webhooks will only be sent after video processing is complete, and the body will indicate whether the video processing succeeded or failed.
- Only one webhook subscription is allowed per-account.
- Cloudflare cannot send webhooks to
localhostor local IP addresses. A publicly accessible URL is required. For local testing, use a Quick Tunnel to expose your local server to the Internet. For a step-by-step walkthrough, refer to Test webhooks locally.
Examples
Golang
Using 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)
In JavaScript (for example, to use in 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("");
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/#page","headline":"Use webhooks","description":"Receive webhook notifications when Cloudflare Stream videos finish processing or encounter errors.","url":"https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/","inLanguage":"en","image":"https://developers.cloudflare.com/stream/manage-video-library/using-webhooks/og.png?v=e2ef3f7943c41b47","dateModified":"2026-09-30","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Access server-side video analytics for Cloudflare Stream via the dashboard or GraphQL API. title: Analytics image: https://developers.cloudflare.com/stream/getting-analytics/og.png?v=dae1d28bd6533852
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/stream/llms.txt
Use this file to discover all available pages before exploring further.
Docs