Guide · the iframe
Embed the player
One iframe puts the Wolvy player — with DRM, your theme and the watermark — on any page. This guide covers the URL, the attributes DRM needs, how domain rules read the Referer, and what to do when a player stays blank.
On this page
The embed URL#
https://embed.wolvy.stream/player host a1b2c3d4e5f60718293avideo id / NDI=base64(account id) ?vt=eyJ2aWQiOi…signed viewer token - Video id — the
idfrom the video object. - Account id — the number from
GET /v1/account, base64-encoded as text:42→NDI=. vt— optional unless enforcement is on, and always per viewer. How to sign one.
You rarely build it by hand: every video object carries embed_url, GET …/embed returns it with an iframe snippet, and POST …/playback-tokens returns it already signed.
Embed builder#
Fill in your ids to get a URL and markup you can paste. Signed URLs belong to one viewer, so in production generate them on your server at render time.
https://embed.wolvy.stream/player hosta1b2c3d4e5f60718293avideo id/ NDI=base64(account id)?vt=eyJ2aWQiOiJ1c2VyLTg4MTMiLCJleHAiOjE3OTAwMDAwMDB9.XBNWyMiuLw0y7WlQkzO5s2N-eI8Ltxhs4N0uvDHM4ZMsigned viewer token<div class="wolvy-player" style="aspect-ratio: 16 / 9;">
<iframe
src="https://embed.wolvy.stream/a1b2c3d4e5f60718293a/NDI=?vt=eyJ2aWQiOiJ1c2VyLTg4MTMiLCJleHAiOjE3OTAwMDAwMDB9.XBNWyMiuLw0y7WlQkzO5s2N-eI8Ltxhs4N0uvDHM4ZM"
title="Onboarding"
allow="autoplay; fullscreen; encrypted-media; picture-in-picture"
allowfullscreen
referrerpolicy="strict-origin-when-cross-origin"
loading="lazy"
style="width: 100%; height: 100%; border: 0;"
></iframe>
</div>The iframe#
<div class="wolvy-player">
<iframe
src="https://embed.wolvy.stream/a1b2c3d4e5f60718293a/NDI=?vt=…"
title="Onboarding — week one"
allow="autoplay; fullscreen; encrypted-media; picture-in-picture"
allowfullscreen
referrerpolicy="strict-origin-when-cross-origin"
loading="lazy"
></iframe>
</div> | Attribute | Why it is there |
|---|---|
allow="… encrypted-media …" | Lets the browser’s DRM module start inside a cross-origin frame. Without it, protected video cannot play. |
allow="autoplay; fullscreen; picture-in-picture" + allowfullscreen | Grants the player the features its controls offer. |
referrerpolicy="strict-origin-when-cross-origin" | Guarantees the Referer that domain rules check, even if the page — or a security plugin — sets Referrer-Policy: no-referrer. |
title | The accessible name screen readers announce for the frame. |
loading="lazy" | Optional. Defers players below the fold — worthwhile when a page has several. |
No sandbox | A sandbox without allow-same-origin blocks DRM outright, and one loose enough for DRM protects nothing. The iframe is already isolated by origin. |
Responsive sizing#
Give the wrapper the video’s shape and let the iframe fill it. The player letterboxes anything that does not match.
.wolvy-player {
position: relative;
aspect-ratio: 16 / 9; /* 9 / 16 for vertical video */
max-width: 960px;
background: #0b0616;
}
.wolvy-player iframe {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
border: 0;
} Domain rules & Referer#
The embed host checks which page is embedding a video before it serves the player. The rules live in Settings → Protection → Allowed domains and apply to every video on the account.
- A Referer must arriveNo Referer (or Origin) header — for example the URL opened directly, or a page with
Referrer-Policy: no-referrer— is refused with a 403 page. - Blocked domains are checked firstA match is refused, whatever the allowed list says.
- An empty allowed list lets every site embedOnce you add one domain, only listed domains work — plus
wolvy.net, andplay.wolvy.streamwhen Direct Play is on. - Matching is by hostname
academy.example.commatches only itself.*.example.commatches every subdomain but notexample.com— list both if you use both.
Domain tester#
This replays the embed host’s check with your rules. The defaults show the most common surprise: a wildcard that does not cover the bare domain.
- ✓Referer hostname is
example.com - –
academy.example.comdoes not matchexample.com - –
*.example.comdoes not matchexample.com
A wildcard covers subdomains only — addexample.comas its own line. - –
wolvy.net(added by Wolvy) does not matchexample.com - –
play.wolvy.stream(added by Wolvy) does not matchexample.com
Blank player or 403?#
Embeds fail quietly — a redirect to an error page inside the frame. It is almost always one of these:
| Symptom | Likely cause | Fix |
|---|---|---|
| 403 when you open the embed URL in the address bar | No Referer — the check working as designed | Test inside an iframe on an allowed page |
| 403 on your site, fine on another | Your hostname is not on the allowed list, or a wildcard without the bare domain | Add the exact hostname; try the domain tester |
| 403 on every embed after installing a security plugin | Referrer-Policy: no-referrer on the page | Add referrerpolicy="strict-origin-when-cross-origin" to the iframe |
| 403 only for some pages or viewers | Enforcement on, and the token is missing, expired or signed with an old secret | Sign every embed; check the stored secret |
| Player shows “No supported content protection system” | The iframe is sandboxed or lacks encrypted-media | Use the recommended markup |
| 400 page | Malformed URL — wrong path shape, or the account id not base64 | Rebuild it from embed_url |
| A “switch browser” or “open the app” screen | App-Only mode, on a device without hardware DRM | Expected — see App-Only mode |
Player settings#
How the player looks and behaves is set once per account in the dashboard — Settings → Player, Subtitles, Watermark and Protection — and applies to every embed. Nothing is configured per iframe, so an embed can never contradict the account.
| Setting | Where | Read it via the API |
|---|---|---|
| Controls, theme, accent colour | Settings → Player | theme, colors |
| Resolutions generated | Settings → Player | available_resolutions |
| Caption font, colours, size | Settings → Subtitles | subtitles |
| Watermark and enforcement | Settings → Watermark | watermark |
| Domains and App-Only mode | Settings → Protection | allowed_domains, blocked_domains, app_only_l3 |
All of it is readable in one call: GET /v1/settings/player. The API cannot change these — a key that could repaint every embed is a risk it deliberately does not carry.
App-Only mode & mobile apps#
Some browsers only have software DRM, which is easier to attack. With App-Only mode on (Settings → Protection → App-Only Mode, Multi-DRM plans), those viewers are not played in the browser — they are steered to a client with hardware-backed protection:
| Viewer is on | With App-Only mode on |
|---|---|
| Android — any browser | Offered to open the video in the Wolvy Player app, or install it from Google Play |
| Android — Wolvy Player app | Plays, with screen recording blocked |
| Windows — Edge with hardware DRM | Plays in the browser |
| Windows — other browsers | Asked to switch to Microsoft Edge |
| macOS — Safari | Plays in the browser |
| macOS — other browsers | Asked to switch to Safari |
| iPhone or iPad — Safari | Plays in the browser |
| iPhone or iPad — other browsers | Asked to open Safari |
| Linux, and anything else without hardware DRM | Shown guidance with a link to the Android app |
From your own Android app
The playback-token response includes deep_link — wolvy://play/{video_id}/{base64 account id}/{vt}.
Open it and the Wolvy Player app takes over, re-verifying the signed token on Wolvy’s side. Fetch the link from your backend; never mint tokens inside the app.
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
// deepLink comes from your backend: POST /v1/videos/{id}/playback-tokens → deep_link
fun openInWolvyPlayer(context: Context, deepLink: String) {
try {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(deepLink)))
} catch (e: ActivityNotFoundException) {
// Wolvy Player is not installed — send the viewer to Google Play.
context.startActivity(
Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.wolvy.player"))
)
}
} import { Linking } from "react-native";
// deepLink comes from your backend: POST /v1/videos/{id}/playback-tokens → deep_link
export async function openInWolvyPlayer(deepLink) {
try {
await Linking.openURL(deepLink); // wolvy://play/{video_id}/{base64 account id}/{vt}
} catch {
await Linking.openURL("https://play.google.com/store/apps/details?id=com.wolvy.player");
}
} Something wrong or unclear on this page? Email [email protected] — include the page name.