Wolvy DOCS

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.

Host embed.wolvy.stream Endpoints GET /v1/videos/{id}/embed · POST …/playback-tokens
On this page

The embed URL#

https://embed.wolvy.stream/player host a1b2c3d4e5f60718293avideo id /  ?vt=eyJ2aWQiOi…signed viewer token
  • Video id — the id from the video object.
  • Account id — the number from GET /v1/account, base64-encoded as text: 42NDI=.
  • 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.

EMBED_BUILDER.EXE
base64 → NDI=
Mint one per viewer on your server. The sample is for illustration only.
Shape
URL anatomy
https://embed.wolvy.stream/player hosta1b2c3d4e5f60718293avideo id/ ?vt=eyJ2aWQiOiJ1c2VyLTg4MTMiLCJleHAiOjE3OTAwMDAwMDB9.XBNWyMiuLw0y7WlQkzO5s2N-eI8Ltxhs4N0uvDHM4ZMsigned viewer token
HTML
<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#

Recommended markup
<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>
AttributeWhy 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" + allowfullscreenGrants 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.
titleThe accessible name screen readers announce for the frame.
loading="lazy"Optional. Defers players below the fold — worthwhile when a page has several.
No sandboxA 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.

CSS
.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.

  1. 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.
  2. Blocked domains are checked firstA match is refused, whatever the allowed list says.
  3. An empty allowed list lets every site embedOnce you add one domain, only listed domains work — plus wolvy.net, and play.wolvy.stream when Direct Play is on.
  4. Matching is by hostnameacademy.example.com matches only itself. *.example.com matches every subdomain but not example.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.

DOMAIN_TESTER.EXE
One per line, as in Settings → Protection. Empty means any site.
Checked before the allowed list.
Full URL or hostname — this becomes the Referer.
Refused · 403
  • Referer hostname is example.com
  • academy.example.com does not match example.com
  • *.example.com does not match example.com
    A wildcard covers subdomains only — add example.com as its own line.
  • wolvy.net (added by Wolvy) does not match example.com
  • play.wolvy.stream (added by Wolvy) does not match example.com

Blank player or 403?#

Embeds fail quietly — a redirect to an error page inside the frame. It is almost always one of these:

SymptomLikely causeFix
403 when you open the embed URL in the address barNo Referer — the check working as designedTest inside an iframe on an allowed page
403 on your site, fine on anotherYour hostname is not on the allowed list, or a wildcard without the bare domainAdd the exact hostname; try the domain tester
403 on every embed after installing a security pluginReferrer-Policy: no-referrer on the pageAdd referrerpolicy="strict-origin-when-cross-origin" to the iframe
403 only for some pages or viewersEnforcement on, and the token is missing, expired or signed with an old secretSign every embed; check the stored secret
Player shows “No supported content protection system”The iframe is sandboxed or lacks encrypted-mediaUse the recommended markup
400 pageMalformed URL — wrong path shape, or the account id not base64Rebuild it from embed_url
A “switch browser” or “open the app” screenApp-Only mode, on a device without hardware DRMExpected — 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.

SettingWhereRead it via the API
Controls, theme, accent colourSettings → Playertheme, colors
Resolutions generatedSettings → Playeravailable_resolutions
Caption font, colours, sizeSettings → Subtitlessubtitles
Watermark and enforcementSettings → Watermarkwatermark
Domains and App-Only modeSettings → Protectionallowed_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 onWith App-Only mode on
Android — any browserOffered to open the video in the Wolvy Player app, or install it from Google Play
Android — Wolvy Player appPlays, with screen recording blocked
Windows — Edge with hardware DRMPlays in the browser
Windows — other browsersAsked to switch to Microsoft Edge
macOS — SafariPlays in the browser
macOS — other browsersAsked to switch to Safari
iPhone or iPad — SafariPlays in the browser
iPhone or iPad — other browsersAsked to open Safari
Linux, and anything else without hardware DRMShown guidance with a link to the Android app

From your own Android app

The playback-token response includes deep_linkwolvy://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"))
        )
    }
}