1. Home
  2. Content Management
  3. Content Protection
  4. Content Protection Policies
  1. Home
  2. Security & Privacy
  3. Content Protection Policies

Content Protection Policies


Some content is only meant for specific audiences. Use content protection to:

  • Prevent unauthorized websites from embedding your content;
  • Limit your exclusive, premium or confidential content to your target audience only.

In this article, we’ll guide you through creating and applying content protection policies to your mediaclips and channels.

1.0 | What is a Content Protection Policy?

A policy can consist of one or more of the following types of rules:

    • Country: specify which countries are allowed or blocked from viewing your content.
    • Domain: choose which websites (URLs) are allowed or blocked to play your content.
    • IP: restrict video access to specific IP addresses.
  • Token: use tokens to provide advanced access control. Tokens are useful when you need more granular control. In contrast to a country, domain or IP policy, the backend of your website/application needs to be able to “generate” a valid token. Read more about generating tokens in the last section of this article (see 5.0 | Advanced – Using Token Secrets).

2.0 | Create a Content Protection Policy

To create a Content Protection policy:

  • In the OVP, click “Publication Settings” in the left menu panel;
  • Select “Content Protection” and add a new Content Protection Policy;

  • Name your policy and enter a description. This helps keep track of what the policy is for. The provided name and description will appear throughout the OVP.

2.1 | Create a Rule Set

A rule set determines under what conditions viewers are allowed to watch your content:

  • Select a type:
    • Country
    • Domain
    • IP
    • Token (advanced use only; to create a rule set for a token, you will need to create a ‘secret’ first: see 5.0 Advanced – Using Token Secrets)
  • Configure a rule by using “is” or “is not” statements :
    • Isis any of: use to allow one or more countries, domains, or IPs addresses
    • Is not / is none of: use to exclude one or more countries, domains, or IP addresses.

    Example 1: only allow views from Spain:

    Example 2: only allow views from www.my-exclusive-channel.com:

    Example 3: allow views from all countries except from Poland and US:

    • Combine multiple rules using “and” and “or” conditions:
      • And: use to ensure all conditions are met before allowing content to be viewed. 
      • Or: use to ensure only one condition needs to be true.

    Example 1: only allow views if the visit is from France and if the visit is on www.my-daily-news.fr.

    Example 2: allow views if the visit is from France or if the visitor’s IP address is 12.2345.678.910.

    2.2 | Blocked Content Display: Media Clip

    Choose what viewers see when your content protection policy blocks a media clip:

    • Show content, but prevent it from playing 

    • Don’t show content (the background color set in the playout settings will be shown):

    2.3 | Blocked Content Display: Channel

    Choose what viewers see when your content protection policy blocks a channel:

    • Show content, but prevent it from playing 

    • Don’t show content: a blank screen will be shown

    3.0 | Apply a Content Protection Policy

    3.1 | Media clip

    To apply a Content Protection Policy to a media clip:

    • In the OVP, click “Media Library” in the left menu panel;
    • Select “Media clips” and open the “Content Protection” tab in your media clip;

    • Select a Content Protection Policy in the dropdown menu

    The name, description and rules defined in the policy are displayed as a helpful reminder of the content protection settings. Use the shortcut to the policy settings if adjustments are needed.

    3.2 | Channel

    Channel Protection Limitations

    Applying content protection to a channel will prevent the channel from being displayed. However, the media clips it contains are not automatically protected as well. Make sure to apply the same content protection to individual media clips to ensure a full content protection.

    To apply a Content Protection Policy to a channel:

    • In the OVP, click “Media Library” in the left menu panel;
    • Select “Channels” and open the “General” tab;
    • Scroll down to “Content Protection”
    • Select a policy in the dropdown menu:

    .

    channel content protection

    .

    The name, description and rules defined in the policy are displayed as a helpful reminder of the content protection settings. Use the shortcut to the policy settings if adjustments are needed.

    4.0 | Test Your Content Protection Policy

    Embed your clip to test your policy settings. If the conditions are not met, a rejection is displayed in the player (as set in 3.0 | Apply a Content Protection Policy).

    4.1 | Country

    4.2 | Domain

    4.3 | IP Address

    4.4 | Token

    5.0 | Advanced – Using Token Secrets

    For advanced use cases, use tokens to authenticate viewers accessing your content. This allows for more granular access control than country, domain, or IP rules alone.

    When using token protection, content requires the website or application to generate a token. A shared secret derived from a Blue Billywig “Secret” is necessary for token calculation. The shared secret remains known only to the client and Blue Billywig platform, ensuring only authorized websites can calculate valid tokens.

    The shared secret itself is not included in the request and is therefore only known to the client and the Blue Billywig platform. This way only authorized websites are able to calculate a token.

    There are two token types:
    • TOTP (RPC Token)
      A time-based one-time password that rotates automatically. Used for simple integrations where you only need time-based access control.
    • JWT
      A signed JSON payload with custom claims and expiration. Used for advanced integrations requiring per-user or per-content access control.
    An interactive JWT demo is available here.
    How it works
    1. Your backend retrieves the shared secret (stored securely server-side)
    2. Your backend generates a TOTP or JWT token
    3. The token is passed to the player via query parameter or HTTP header
    4. The OVP validates the token against the shared secret
    5. If valid, video playback is allowed; if invalid, playback is blocked
    Before implementing token-based content protection, ensure you have:
    • A Blue Billywig OVP publication
    • Access to the OVP management console (specifically: Publication settings > Secrets and Publication settings > Content protection)
    • A Content Protection Policy with a Token rule configured (see Section 5.2).
    • A backend server capable of generating tokens (the shared secret must never be exposed client-side)

    5.1 | Create a token

    To create a token:

    • In the OVP sidebar, navigate to Publication settings > Secrets.
    • On the Secrets overview page, click the Create new content protection token button.

    • Fill in the form:
      • Label (required): A descriptive name for your secret (e.g., “Production JWT Token”).
      • Description (optional): Additional context about the secret’s purpose.
      • Expiration settings: Set the token validity duration using the Days, Hours, and Minutes fields. The expiration must be at least 1 minute. After the expiration duration expires, the token becomes invalid and a new token needs to be generated.
      • Click Save & Activate to create and immediately activate the secret, or click Save to create it in an inactive state
      • After saving, the secret is generated. You can find it in the Secret field on the detail page (click Show secret to reveal the value).
      • The Details panel on the right shows:
        • ID: The numeric secret identifier (you will need this for TOTP tokens).
        • Secret type: Confirms “Content protection”.
        • Label, Created, and Modified metadata.
      • Copy the secret value and store it securely in your backend (e.g., in environment variables or a secrets manager).
    Important:

    The shared secret must never be exposed in client-side code, HTML source, or browser-accessible locations.

    Note:

    Playback is not interrupted when a token expires mid-session. A new token is only required when the page is reloaded.

    Note:

    To deactivate or delete a secret, open it from the Secrets list and use the Deactivate or Delete actions at the bottom of the detail page.

    5.2 | Create a Content Protection Policy with a Token Rule

    • In the OVP sidebar, navigate to Publication settings > Content protection.
    • On the Content protection policies overview page, click Create new content protection policy.
    • Under Policy name, fill in a Title and optional Description.
    • Under Select policy type, configure the rules:
      • Click the Add rule dropdown and select Token.
      • Choose an operator (e.g., is, is not, is empty, is not empty).
      • If using is or is not, select the content protection token you created in Section 5.1.
    • You can combine multiple rules:
      • Click Add rule to add an additional condition within the same rule set (AND logic).
      • Click Add rule set to add an alternative set of conditions (OR logic).
    • Click Save to create the policy.

    Blocked Content Display

    When a token is invalid or missing, the policy determines how blocked content appears to viewers:

    • Media clips: Either show the player with playback disabled, or hide the content entirely (a background
      color is displayed in its place).
    • Channels: Either show the channel with playback disabled, or hide it completely (a blank screen appears).

    These display options are configured when creating the policy (see Section 2.2 – Blocked Content
    Display.

    Apply the Policy

    After creating the policy, assign it to your content:

    • Media clips: Navigate to Media library > Media clips, open a clip, go to the
      Content Protection tab, and select your policy.
    • Channels: Navigate to Media library > Channels, open a channel, go to the General tab, scroll
      to Content Protection, and select your policy.
    Important:

    Applying content protection to a channel will prevent the channel from being displayed. However, the
    media clips it contains are not automatically protected as well. You must apply the policy to individual media clips separately if they also need to be protected.

    5.3 | TOTP Token

    Authenticating a request using a TOTP token requires two elements:

    • The ID of the Secret
    • The generated token itself

    TOTP (Time-based One-Time Password) tokens use the [RFC 6238] (https://datatracker.ietf.org/doc/html/rfc6238) standard. The token value rotates automatically based on the configured time step (expiration window).

    Token Format

    The generated token must use a time-step duration matching the secret’s expiration time in seconds. Append the generated token to the ID with a hyphen:

    {SECRET_ID}-{GENERATED_TOTP_CODE}

    For example: 42-839205 Include this string in the rpctoken header or query parameter.

    Generating a TOTP Token

    Node.js (using `speakeasy`)

    const speakeasy = require("speakeasy");
    const SECRET_ID = "42";
    const SHARED_SECRET = "your-shared-secret";
    const EXPIRATION = 86400; // 24 hours in seconds (must match OVP setting)
    
    function generateRpcToken() {
       const totp = speakeasy.totp({ secret: SHARED_SECRET, digits: 6, step: EXPIRATION });
       return;
       `${SECRET_ID}-${totp}`;
    }
    const token = generateRpcToken(); // e.g. "42-839205"
    

    Python (using `pyotp`)

    import pyotp 
    import base64 
    
    SECRET_ID = "42" 
    SHARED_SECRET = "your-shared-secret" 
    EXPIRATION = 86400 # 24 hours in seconds 
    
    def generate_rpc_token(): # pyotp expects a base32-encoded secret 
        secret_b32 = base64.b32encode(SHARED_SECRET.encode()).decode() 
        totp = pyotp.TOTP(secret_b32, digits=6, interval=EXPIRATION) 
        code = totp.now() 
        return f"{SECRET_ID}-{code}" 
    
    token = generate_rpc_token() # e.g. "42-839205"/

    PHP (using `OTPHP`)

    <?php use OTPHP\TOTP; $secretId = '42'; $sharedSecret = 'your-shared-secret'; $expiration = 86400; // 24 hours function generateRpcToken(string $secretId, string $secret, int $expiration): string { $totp = TOTP::createFromSecret($secret); $totp->setPeriod($expiration);
        $totp->setDigits(6);
        $code = $totp->now();
    
        return "{$secretId}-{$code}";
    }
    
    $token = generateRpcToken($secretId, $sharedSecret, $expiration); // e.g. "42-839205" 

    Passing the TOTP Token

    The token can be sent as a query parameter or HTTP header:

    • Query parameter (appended to the embed URL): https://{publication}.bbvms.com/p/{playout}/c/{clipId}.js?rpctoken=42-839205
    • HTTP header rpctoken: 42-839205

    5.4 | JWT Token

    When authenticating a request using a JWT token, sign the payload with the shared secret. JWT tokens offer more flexibility than TOTP: you can include custom claims such as expiration time, subject identifiers, and entity-level access restrictions.

    JWT Structure

    The JWT must be signed using HMAC SHA-256 (HS256) with the shared secret. The payload should include at minimum:

    {
       "sub": "user-identifier",
       "iat": 1700000000,
       "exp": 1700000900
    }
    ClaimDescriptionRequired
    subSubject identifier (e.g., user ID or session ID)Recommended
    iatIssued-at timestamp (Unix epoch seconds)Recommended
    expExpiration timestamp (Unix epoch seconds)Yes
    entity_accessEntity-level access restrictions (see Section 5.6)Optional
    Note

    The `exp` claim must fall within the secret’s configured expiration window.

    Generating a JWT Token

    Node.js (using `jsonwebtoken`)

    const jwt = require("jsonwebtoken");
    const SHARED_SECRET = "your-shared-secret";
    
    function generateJwtToken(userId) {
       const now = Math.floor(Date.now() / 1000);
       const payload = { sub: userId, iat: now, exp: now + 900 }; // 15 minutes
    
       return jwt.sign(payload, SHARED_SECRET, { algorithm: "HS256" });
    }
    
    const token = generateJwtToken("user-123");
    

    Python (using `pyotp`)

    import jwt
    import time
    
    SHARED_SECRET = "your-shared-secret"
    
    
    def generate_jwt_token(user_id: str) -> str:
        now = int(time.time())
        payload = {
            "sub": user_id,
            "iat": now,
            "exp": now + 900,  # 15 minutes
        }
        return jwt.encode(payload, SHARED_SECRET, algorithm="HS256")
    
    
    token = generate_jwt_token("user-123")
    

    PHP (using `OTPHP`)

    <?php use Firebase\JWT\JWT; $sharedSecret = 'your-shared-secret'; function generateJwtToken(string $userId): string { global $sharedSecret; $now = time(); $payload = [ 'sub' => $userId,
            'iat' => $now,
            'exp' => $now + 900, // 15 minutes
        ];
    
        return JWT::encode($payload, $sharedSecret, 'HS256');
    }
    
    $token = generateJwtToken('user-123');
    

    Passing the JWT Token

    The calculated JWT can be provided via the Authorization header as a Bearer token, or through the jwt header or query parameter. These are checked in the following order:
    • Query parameter (appended to the embed URL):
      https://{publication}.bbvms.com/p/{playout}/c/{clipId}.js?jwt=eyJhbGciOi…
    • HTTP header jwt: eyJhbGciOi…
    • Authorization Bearer header Authorization: Bearer eyJhbGciOi…

    5.5 | Embedding with Tokens

    In a typical integration, your backend generates the token and appends it as a query parameter to the embed URL. Blue Billywig supports several embed methods (read more about embedding). Below are the most common approaches with token authentication.
    JavaScript Embed

    Append the token as a query parameter to the .js embed URL:

    <!-- TOTP -->
    <script
      type="text/javascript"
      src="https://{publication}.bbvms.com/p/{playout}/c/{clipId}.js?rpctoken=42-839205"
      async="true"
    ></script>
    
    <!-- JWT -->
    <script
      type="text/javascript"
      src="https://{publication}.bbvms.com/p/{playout}/c/{clipId}.js?jwt=eyJhbGciOi..."
      async="true"
    ></script>

    iFrame Embed
    Append the token to the .html embed URL in the src attribute:

    <iframe
      onload="
      this.src +=
    '#!referrer=' + encodeURIComponent(location.href) + '&realReferrer=' + encodeURIComponent(document.referrer)"
      src="https://{publication}.bbvms.com/p/{playout}/c/{clipId}.html?inheritDimensions=true&jwt=eyJhbGciOi..."
      width="720"
      height="405"
      frameborder="0"
      allowfullscreen
      allow="autoplay; fullscreen"
    ></iframe>

    Server-Side Token Injection
    For maximum security, have your backend generate the token and inject it into the embed URL so that the token never appears in static HTML source code:

    // Express.js example — renders a page with the token embedded in the player URL
    const jwt = require("jsonwebtoken");
    
    app.get("/video/:clipId", (req, res) => {
      const token = generateJwtToken(req.user.id);
      const embedUrl = `https://${PUBLICATION}.bbvms.com/p/${PLAYOUT}/c/${req.params.clipId}.js?jwt=${token}`;
      res.send(`
    <html>
      <body>
        <script type="text/javascript" src="${embedUrl}" async="true"></script>
      </body>
    </html>
    `);
    });

    Note:

    The token is appended as a regular query parameter alongside any other [override parameters] (e.g.,
    ?jwt=eyJ…&autoPlay=true).

    5.6 | Entity-Level Access Control (JWT)

    JWT tokens support fine-grained access control through the entity_access claim. This allows you to restrict a token to specific media clips, clip lists, or projects.

    Claim Structure

    The entity_access claim is an object where keys are entity types and values are arrays of allowed entity IDs:

    {
      "sub": "user-123",
      "iat": 1700000000,
      "exp": 1700000900,
      "entity_access": { "mediaclip": ["12345", "67890"], "mediacliplist": ["101"], "project": ["42"] }
    }
    Entity TypeDescription
    mediaclipIndividual video clips
    mediacliplistClip lists / playlists
    projectProjects

    Behavior

    • If the entity_access claim is omitted or empty, the token grants access to all content protected by the policy (no entity-level restriction).
    • If  entity_access is present, the OVP checks whether the requested content matches one of the allowed entities. Access is granted if any entity type/ID pair matches.
    • An empty instances array (“mediaclip“: []) matches all instances of that entity type.

    Example: Restricting to a Single Clip

    const jwt = require("jsonwebtoken");
    
    function generateClipToken(userId, clipId) {
      const now = Math.floor(Date.now() / 1000);
      const payload = { sub: userId, iat: now, exp: now + 900, entity_access: { mediaclip: [String(clipId)] } };
      return jwt.sign(payload, SHARED_SECRET, { algorithm: "HS256" });
    } // This token only grants access to clip 12345
    
    const token = generateClipToken("user-123", 12345);

    Was this article helpful?

    Related Articles

    Contact Support
    Can't find the answer you're looking for?
    Contact Support