Streaming Dog · Security setup Open Settings

Client guide

Connect your website, server or app securely

Follow these steps so your videos play only on your own domains, only for the viewers your server lets in, and saved or shared links stop working.

  • About 20 minutes
  • Needs: your panel login, your DNS, your server code
  • Panel address: https://streamingdog.net

How it fits together

Two secrets do the work: your embed key, which only your server knows, and the list of verified domains, which you prove with DNS.

Yours Your server Checks that the viewer is logged in or has paid, then signs a link with your embed key.
Yours Your web page or app Shows our player in an iframe, or your own player, using that signed link.
Streaming Dog Panel Checks the signature, the expiry, your domain, stream limits and your account status.
Streaming Dog Video servers Serve the video only through short-lived links made for that one playback.
What we already do for you Everything is served over HTTPS. Every video file link is signed and expires. There is no download link for any video, not even for your original upload. Encryption keys go only to valid players and are never cached. We slow, then ban, addresses that pull a video much faster than it plays. New accounts start with encryption, signed embed links and only our embed player on; open them up in Settings if you need to.
What no setting can stop A viewer can still film or screen-record their own screen. Only DRM prevents that, and only partly. The steps below make links useless outside your site and make leaked recordings traceable (watermark).

1Secure your account and the embed key

Your panel login controls everything. The embed key lets anyone who has it sign links to your videos.

Panel account

  • Use a long, unique password for the panel. Don't reuse it anywhere else.
  • Give the login only to people who manage videos and settings. Your developers need the embed key, not the password.
  • Sign out on shared or public computers.

Embed key

Find it under Settings → Embed key. Treat it like a database password:

Keep it hereNever put it here
An environment variable on your server (for example STREAMINGDOG_EMBED_KEY), or your host's secret manager Browser JavaScript, HTML, a mobile or TV app, a URL, a Git repository, logs, screenshots, support tickets
If the key may have leaked Use Settings → Embed key → Rotate. The new key works at once, and every link signed with the old key stops working, including Stream API tokens. Put the new key on your server first, then rotate, or players fail until your server has it. Also rotate when someone who knew the key leaves your team.

2Lock the player to your domains

With signed embed links off and no verified domain, the player can be embedded on any site. When one domain is verified, it plays only on verified domains.

Add and verify each domain

  1. Open Settings → Websites and add the exact host your pages are on.
    You enterPlayer allowed on
    example.comexample.com only. Not www.example.com.
    www.example.comwww.example.com only
    *.example.comexample.com and every subdomain (www., app., learn., …)
    localhostYour own computer while developing. Counts as verified with no DNS record.

    Enter just the host: no https://, no path, no port.

  2. At your DNS provider, add the TXT record the panel shows:
    Domain you addedTypeNameValue
    example.comTXT_streamingdog.example.com streamingdog-verify=…
    www.example.comTXT_streamingdog.www.example.com streamingdog-verify=…
    *.example.comTXT_streamingdog.example.com streamingdog-verify=…

    Copy the value from the panel; it is different for every domain. Many DNS panels (GoDaddy, Namecheap, cPanel, Cloudflare) add your domain to the name for you. There, type only _streamingdog (or _streamingdog.www).

  3. Check that the record is visible, then press Verify:
    Terminal
    nslookup -type=TXT _streamingdog.example.com
    # or
    dig +short TXT _streamingdog.example.com

    New records can take a few minutes to show up. If the panel says the record isn't there yet, wait and press Verify again. A verification stays once it is made, so you can delete the TXT record afterwards.

What the lock does

  • Browsers refuse to show the player inside any page that isn't on a verified domain. This covers every frame up the chain, so a verified page framed by another site doesn't play either.
  • The panel refuses playback when the page's address isn't on a verified domain: “This video can only be played on approved websites.”
  • From a browser, the Stream API (step 4) answers only pages on your verified domains.
  • With Only browser players (step 5), the video servers serve files only to players on your verified domains.
Your pages must send the page address (Referer) The lock reads which page the player is on. The browser default (strict-origin-when-cross-origin) works. Don't send Referrer-Policy: no-referrer from your site, and don't put referrerpolicy="no-referrer" on the iframe. Without the address, a locked player refuses to play.
Is your page inside another site? Examples are a course platform (LMS) or a page builder's preview. Verify that site's domain too, or the browser blocks the player there.

3Sign links on your server

This is how you decide who may watch. Your server checks the viewer first, then signs a link that expires. A copied link stops working at that time.

  1. Put the embed key in an environment variable on your server (step 1).
  2. Keep Settings → Playback security → Require signed embed links on (the default for new accounts). The player loads only with a valid token and expires.
  3. In your page code, after your own login or payment check, build the player link with the code below.

The token

PartRule
expiresUnix time in seconds (10 digits at most) when the link stops loading. One hour is a good default. Keep your server's clock synced (NTP).
v1token = sha256_hex(embedKey + videoId + expires)
v2Use v2 when you send a viewer id uid and/or watermark text wm (up to 128 characters each):
token = sha256_hex(embedKey + videoId + expires + "|" + len(uid) + ":" + uid + "|" + len(wm) + ":" + wm)
len is the UTF-8 byte length. A value you don't send is the empty string, written 0:.
Linkhttps://streamingdog.net/embed/{videoId}?token={token}&expires={expires}[&uid={uid}&wm={wm}], with uid and wm URL-encoded.

uid is your own id for the viewer. Use a user id, never a password or session token. It counts as the viewer for stream limits and fills {uid} in the watermark. wm replaces the watermark text for that link, for example the buyer's name or email. Both are trusted only inside a signed link: if someone edits them, the token no longer matches.

Node.js 18+ · on your server
import { createHash } from "node:crypto";

const APP_URL = "https://streamingdog.net";
const EMBED_KEY = process.env.STREAMINGDOG_EMBED_KEY; // Settings → Embed key. Never in browser code.

const sha256 = (s) => createHash("sha256").update(s, "utf8").digest("hex");
const field = (v) => `${Buffer.byteLength(v, "utf8")}:${v}`; // UTF-8 byte length + ":" + value

export function signedEmbedUrl(videoId, { ttl = 3600, uid = "", wm = "" } = {}) {
  const expires = Math.floor(Date.now() / 1000) + ttl; // seconds, not milliseconds
  let data = EMBED_KEY + videoId + expires;
  if (uid || wm) data += "|" + field(uid) + "|" + field(wm); // v2
  let url = `${APP_URL}/embed/${videoId}?token=${sha256(data)}&expires=${expires}`;
  if (uid) url += `&uid=${encodeURIComponent(uid)}`;
  if (wm) url += `&wm=${encodeURIComponent(wm)}`;
  return url;
}

// Example (Express): sign only after YOUR check that this viewer may watch.
app.get("/lessons/:id", requireLogin, async (req, res) => {
  const lesson = await db.lessons.find(req.params.id);
  if (!(await canWatch(req.user, lesson))) return res.status(403).send("Not included in your plan");
  const src = signedEmbedUrl(lesson.videoId, { uid: String(req.user.id), wm: req.user.email });
  res.render("lesson", { src }); // <iframe src="{{src}}" …></iframe>
});

Put the signed link in your page

HTML · keeps 16:9 at any width
<div style="position:relative;padding-top:56.25%">
  <iframe src="SIGNED_LINK_FROM_YOUR_SERVER"
    style="position:absolute;inset:0;width:100%;height:100%;border:0"
    allow="autoplay; fullscreen; picture-in-picture; encrypted-media"
    allowfullscreen loading="lazy"></iframe>
</div>
  • Make the link when the page is requested, not once at build time. A page cached for everyone (CDN, page cache) gives every visitor the same link: exclude those pages from caching, or keep the cache shorter than the link lifetime.
  • A viewer who has started watching can keep watching for the Playback link lifetime (step 5), even after expires has passed. expires only limits when the player can be opened.
  • Never sign links in browser JavaScript. If the key is in the browser, anyone can sign links to all your videos.

4Your own player or app (Stream API)

For hls.js, Video.js, Shaka, or a mobile or TV app. You get fresh signed video links after the same checks as our player.

GET https://streamingdog.net/api/v1/stream/{videoId}?token={token}&expires={expires}[&uid=…&wm=…&ip=…]

  • token and expires are always required. Sign them exactly like an embed link (step 3). The Stream API works even when signed embeds are off.
  • From a browser on one of your verified domains: the links are bound to the viewer's own address. Other sites get a 403.
  • From your server: add ip= with the viewer's IP address, so IP locks and stream limits apply to the viewer and not to your server.
  • The same rules as the embed apply: domain lock, stream limit, IP lock, watermark and account status. The answer is never cached, so ask again when expiresAt is near.
  • With Only our embed player selected (step 5), the Stream API answers 403.
Node.js · on your server
// sha256() and EMBED_KEY as in step 3.
const expires = Math.floor(Date.now() / 1000) + 3600;
const token = sha256(EMBED_KEY + videoId + expires);
// The viewer's real IP. Behind a CDN or load balancer, read it from the header YOUR proxy sets
// (e.g. CF-Connecting-IP, or Express with "trust proxy"), never from a header the client could send.
const viewerIp = req.ip;
const r = await fetch(
  `${APP_URL}/api/v1/stream/${videoId}?token=${token}&expires=${expires}&ip=${encodeURIComponent(viewerIp)}`,
);
if (!r.ok) throw new Error(`Stream API ${r.status}: ${await r.text()}`);
const stream = await r.json(); // hand stream.hls (or the whole object) to your app or page
Mobile and TV apps Apps don't send a browser's headers, so they need Anyone with a valid link. Call the Stream API from your server with ip=, never from the app with the key inside it. Then turn on the IP lock and a short link lifetime.

5Protect the video files

Settings → Download protection and Settings → Playback security. These make saved or shared file links useless.

Who can play your video links

OptionPlays inRefused
Anyone with a valid link Any player until the link expires: browsers, VLC, TV and mobile appsExpired or tampered links
Only browser players Our player, and your own browser player (Stream API) on your verified domains Links opened in a tab, download managers, media players, apps, other websites
Only our embed player (default) The Streaming Dog embed only All of the above, and the Stream API. Its links last minutes and renew while the player is open, and the encryption key goes only to that open player, itself encrypted.

A stricter choice applies at once, also to links you handed out before: their master and quality playlists are refused outside the allowed players. Audio-only links and video files a player has already loaded keep working until they expire. With Only browser players, your own player must use hls.js or Media Source with crossorigin="anonymous". Safari's built-in HLS is refused (hls.js plays on current Safari).

The other switches

SettingWhat it doesWatch out for
Encrypt new videos (AES-128) On by default for new accounts. Each piece of the video is encrypted. The key goes only to valid players. Applies only to uploads made while it is on. H.264 only, no MP4 fallback. Very old iPhones (before iOS 17.1) can't play them inside our embed player.
Lock playback links to the viewer's IP address A copied video link stops working on another network. Viewers who switch from Wi-Fi to mobile data reload the player. Leave it off if many viewers have both IPv4 and IPv6.
Simultaneous streams per viewer How many players one viewer may have open at once (0 = unlimited). A viewer is the signed uid, otherwise the IP address. Without uid, people on the same office network count as one viewer.
Show a moving watermark Semi-transparent text moves over the video. {uid} is the signed viewer id and {ip} the viewer's IP. A signed wm replaces the text. Picture-in-picture is off while the watermark is on.
Playback link lifetime How long the file links inside a player work: 1 hour, 6 hours (default), 24 hours or 7 days. Shorter is safer. Viewers who pause for longer reload the page. With only our embed player, links last 5 minutes and renew by themselves instead.
MP4 fallback (Encoding) Plain MP4 copies for very old browsers. A single MP4 file is much easier to save than a stream. Leave it off unless you need it.

Which level you need

Start from the row that matches your videos. Each level includes the one before it.

Public videos

Marketing, blog, product pages

  • Verified domains (step 2)
  • Nothing else needed

Members only

Behind your login

  • Signed embed links with uid
  • Only browser players
  • Lifetime 1–6 hours

Paid content

Courses, premium videos

  • Only our embed player
  • Encrypt new videos
  • 1–2 streams per viewer
  • Watermark with the buyer (wm)
  • Lifetime 1 hour
You haveTurn on
Your own web playerStream API from your verified domain, Only browser players
A mobile or TV appStream API from your server with ip=, Anyone with a valid link, IP lock, short lifetime

6Test your setup

Each test should be refused. If one plays, the setting it checks isn't on yet.

Try thisExpectedChecks
Open https://streamingdog.net/embed/VIDEO_ID in a new tab, without a token“This link has expired or is invalid.”Signed links
Sign a link with ttl = 60, wait two minutes, then open it “This link has expired or is invalid.”Expiry, server clock
Change one letter of uid or wm in a signed link “This link has expired or is invalid.”v2 signature
Paste your iframe into an online HTML playground (any domain you haven't verified) The browser refuses the frame, or “…only be played on approved websites.”Domain lock
While a video plays, copy a .m3u8 address from DevTools → Network and open it in a new tab or in VLC403Only browser players
Set streams per viewer to 1, then open the same signed uid in two tabs The second one: “Too many streams for this viewer.”Stream limit
Search your built front-end and app for the first characters of the embed key Nothing foundKey stays on your server

When something is refused

The player shows the message. The Stream API returns it with this status.

StatusMessageWhat to do
403This link has expired or is invalid. Sign a new link. Check the embed key, the video id, that expires is in seconds, and that uid/wm are the same as when you signed.
403This video can only be played on approved websites. Verify the domain the page is on (mind www.), and make sure the page sends a Referer.
403…play only in the Streaming Dog player… Stream API with Only our embed player selected: use the embed, or allow browser players.
404This video doesn't exist. Wrong id, or the video was deleted.
409This video is still processing. Wait until it is Ready.
402This video is temporarily unavailable. Your account has an unpaid balance (Billing).
429Too many streams for this viewer. The viewer closes another player, or you raise the limit.
503No server is available to play this video right now. Temporary. Try again in a minute.

With Only browser players on, a video that starts but stays black on Safari means your own player uses Safari's built-in HLS: switch to hls.js. Still stuck? Open a support ticket. Never paste your embed key into a ticket.

Checklist

Tick items as you go. Your ticks are saved only in this browser.