Guide: Stream security
Restrict which sites can embed your videos, block hotlinking, and require signed tokens on every embed.
Every project has a Security page in the dashboard with four controls that apply to the whole project — no code changes needed for the first three. They complement the per-session options described in Protect your videos.
The honest ceiling applies here too: none of this makes downloading or screen recording impossible. These controls decide where playback is allowed to start, and raise the effort for everyone else.
Direct play
The master switch for anonymous playback. While it is on (the default), any
video you set to public or unlisted plays on its hosted watch page, in
iframe embeds, and through oEmbed.
Turn it off and the whole anonymous surface goes dark for this project — the
hosted pages show "unavailable" and the embed mint refuses, exactly as if every
video were private. Sessions minted by your server through
playback.createSession keep working, so a members-only site loses nothing.
Allowed and blocked domains
Two lists of domains, checked against the page that embeds or links your video:
- Allowed domains — when the list is non-empty, only these sites may play your videos. An empty list allows every site.
- Blocked domains — always refused, checked before the allowlist.
*.example.com matches every subdomain and example.com itself. You can
paste a full URL — https://app.example.com/courses is stored as
app.example.com.
Two details worth knowing:
- When an allowed list is set, requests that arrive with no referring page at all are refused too. Browsers always send a referrer for embedded players, so real viewers are unaffected; stripped-referrer scrapers are not real viewers.
- Your own dashboard previews are exempt — you can always watch your own videos while logged in, whatever the lists say.
The lists also become the default referrerAllowlist for sessions your server
mints with an API key. Passing an explicit referrerAllowlist on
createSession overrides the project list for that session; passing [] opts
that session out entirely.
Block direct URL file access
When on, media requests that carry no Referer header at all are refused —
video URLs pasted into a bare tab, download managers, and most scripted
fetchers. Playback from a real page keeps working because pages always refer
their own media requests.
Embed view token authentication
The strongest gate: when enabled, the embedded player only loads when the embed URL carries a token your server signed. Nobody can start playback from a URL you did not generate — even with the video ID in hand.
The dashboard shows your project's embed token key (rotatable at any time). Sign like this:
token = hex( HMAC-SHA256( key, videoId + "|" + expires ) )
expires = unix seconds when the link stops workingThen append both to the hosted watch or embed URL:
https://cdn.videohati.com/embed/VIDEO_ID?token=TOKEN&expires=1900000000Node
import { createHmac } from "node:crypto";
function signEmbedUrl(videoId: string, key: string, ttlSeconds = 3600): string {
const expires = Math.floor(Date.now() / 1000) + ttlSeconds;
const token = createHmac("sha256", key)
.update(`${videoId}|${expires}`)
.digest("hex");
return `https://cdn.videohati.com/embed/${videoId}?token=${token}&expires=${expires}`;
}PHP
function signEmbedUrl(string $videoId, string $key, int $ttlSeconds = 3600): string
{
$expires = time() + $ttlSeconds;
$token = hash_hmac('sha256', $videoId . '|' . $expires, $key);
return "https://cdn.videohati.com/embed/{$videoId}?token={$token}&expires={$expires}";
}Rotate the key from the dashboard whenever you suspect it leaked: tokens signed with the old key stop verifying immediately, and every embed URL you generate after that must use the new key.
What to combine
| Goal | Controls |
|---|---|
| "Only my site can embed my videos" | Allowed domains |
| "That one site keeps stealing embeds" | Blocked domains |
| "No hotlinking the video files" | Block direct URL file access |
| "Only logged-in members of my app" | Embed token authentication, short expires |
| "Nothing public at all" | Direct play off + server-minted sessions |