Protect your content with signed URLs

Prevent unauthorized viewers from downloading your content or embedding your player on sites that you do not own.

JWP allows you to secure your media and players by requiring requests to use signed URLs. These URLs are valid for only a short period of time, preventing unauthorized downloading of your media or embedding of your player on unapproved sites.

Signed URLs are generated using three inputs: the resource path, an expiration time, and a property’s secret key.

When a signed request reaches JWP:

  • From one of your properties, a request is sent to JWP that includes an expiration and a signature.
  • JWP validates the signature.
  • If the signature is valid, the request succeeds. Otherwise, the request is denied.

JWP's resources support the following signing methods.

Method Description
JSON web token (JWT) URL secured with a JSON Web Token (JWT) that encodes claims (like path, expiration, and optional parameters) and signed with your property’s secret key using HMAC-SHA256
Non-JWT URL secured with a hash signature generated by applying MD5 to the path, expiration, and your property’s secret key


Available resource routes

Most resource routes require JWT signing. A few routes require non-JWT signing. If a route supports both methods, we strongly recommend using JWT signing.

The table below lists each supported resource route and its signing method.

Signing method Routes
Advertising schedule
JWT /v2/advertising/schedules/{ad_schedule_id}.{ad_schedule_extension}
Media
JWT /v2/sites/{site_id}/media/{media_id}/playback.json
JWT /v2/media/{media_id}
Players
Non-JWT /libraries/{player_id}.js
Non-JWT /thumbs/{media_id}-{thumb_width}.jpg
Non-JWT /players/{content_id}-{player_id}.{embed_type}
Playlist
JWT /v2/playlists/{playlist_id}
JWT /v2/playlists/{playlist_id}/drm/{policy_id}
Poster image
JWT /v2/media/{media_id}/poster.jpg
Non-JWT /previews/{content_id}-{player_id}
Streaming manifests
Non-JWT /manifests/{media_id}.{manifest_extension}
Text tracks
Non-JWT /tracks/{track_id}.{track_extension}
Video files
Non-JWT /videos/{media_id}-{template_id}.{media_extension}


Prerequisite

Item Description
Signing secret Token used to sign the URL to prevent unauthorized content downloads and embedding

Follow these steps to obtain the secret:
  1. On the Delivery API tab, click Show URL signing secret for the relevant property. The Secret appears.
  2. Copy the Secret.


Create a signed URL

JWT

🚧

When generating a signed URL, note the following:

  • The following code samples are provided for guidance and may not work in your environment. If you use any of these samples, be sure to test the functionality in a development environment before deploying it into a production environment.
  • For security reasons, JWT-signed URLs MUST always be generated on the server side.

Follow these steps to sign a URL:

  1. Copy the following snippet to your code.

    📘

    The following code snippets use the /v2/sites/{site_id}/media/{media_id}/playback.json resource route. Adjust the samples accordingly for the resource route used.

    import jwt from 'jsonwebtoken';
    
    // Configuration (edit these values)
    const signing_secret = 'SIGNING_SECRET';
    const site_id = 'SITE_ID';
    const media_Id = 'MEDIA_ID';
    const path = `/v2/sites/${site_id}/media/${media_id}/playback.json`;
    
    function jwtSignedUrl(path) {
      const host = 'https://cdn.jwplayer.com';
      const now = Math.floor(Date.now() / 1000);
    
      const payload = {
        resource: path,
        // Put any accepted query params here, not in the URL, for example:
        // related_media_id: 'RltV8MtT',
        exp: Math.ceil((now + 3600) / 300) * 300, // valid for 1h
      };
    
      const token = jwt.sign(payload, signing_secret, {
        algorithm: 'HS256',
        noTimestamp: true, // omit "iat" to maximize cacheability
      });
    
      return `${host}${path}?token=${token}`;
    }
    
    // Generate the signed URL
    const url = jwtSignedUrl(path);
    console.log(url);
    
    
    import math
    import time
    import os
    import requests
    from jose import jwt  # pip install python-jose requests
    
    signing_secret = os.environ.get("SIGNING_SECRET")
    
    # Define your variables
    site_id = "SITE_ID"
    media_id = "MEDIA_ID"
    path = f"/v2/sites/{site_id}/media/{media_id}/playback.json"
    
    
    def jwt_signed_url(path):
        """
        Generate a signed URL with JWT.
        """
        host = "https://cdn.jwplayer.com"
        now = int(time.time())
    
        payload = {
            "resource": path,
            # Add any supported query parameters here
            # "related_media_id": "RltV8MtT", 
            "exp": math.ceil((now + 3600) / 300) * 300,
        }
    
        token = jwt.encode(payload, signing_secret, algorithm="HS256")
        return f"{host}{path}?token={token}"
    
    
    # Generate the signed URL
    url = jwt_signed_url(path)
    
  2. Define the signing_secret, site_id, media_id, and path values.

  3. (Optional) Add supported route parameters to the payload.

    🚧

    All URL parameters that you want to include must be included in the payload as separate parameters.

    Do not append them to the path. Any URL parameter added to a JWT-signed request will be ignored if it is not within the payload.

  4. Optional) Adjust the exp calculation or enter a UNIX timestamp in seconds.

    🚧

    Note the following when setting exp:

    • Typical range: 1 minute to several hours. Shorter durations make signed content more secure but can cause playback issues if the URL expires during playback (especially for long videos or playlists).
    • On high-traffic sites, cache signed URLs to prevent performance issues related to generating signed URLs. For example, cache URLs in intervals of five minutes. Signed requests do not need to be unique.
    • Expirations under one minute may cause playback errors due to clock drift or network delays.

Now, you can enable URL signing functionality for your property.



Non-JWT

🚧

When generating a signed URL, note the following:

  • The following code samples are provided for guidance and may not work in your environment. If you use any of these samples, be sure to test the functionality in a development environment before deploying it into a production environment.
  • For security reasons, JWT-signed URLs should always be generated on the server side.

Follow these steps to sign a URL:

  1. Copy the following snippet to your code.

    📘

    The following snippets use the /players/{content_id}-{player_id}.{embed_type} resource route. Adjust the samples accordingly for the resource route used.

    import MD5 from 'crypto-js/md5';
    
    // Configuration (edit these values)
    const signing_secret = 'SIGNING_SECRET';
    const content_id = 'CONTENT_ID';
    const player_id = 'PLAYER_ID';
    const embed_type = 'js'; // e.g., 'js' | 'html' (as supported)
    
    /**
     * Build a non-JWT signed URL for:
     *   /players/{content_id}-{player_id}.{embed_type}
     * Link valid ~1 hour, normalized to 5 minutes for caching.
     */
    function signedPlayerEmbedUrl(content_id, player_id, embed_type) {
      const host = 'https://cdn.jwplayer.com';
      const path = `players/${content_id}-${player_id}.${embed_type}`;
      const now = Math.floor(Date.now() / 1000);
      const expires = Math.ceil((now + 3600) / 300) * 300; // 1h, 5m rounding
    
      const base = `${path}:${expires}:${signing_secret}`;
      const signature = MD5(base).toString();
    
      return `${host}/${path}?exp=${expires}&sig=${signature}`;
    }
    
    // Generate the signed URL
    const url = signedPlayerEmbedUrl(content_id, player_id, embed_type);
    console.log(url);
    
    import hashlib
    import math
    import os
    import time
    
    # Configuration (edit these values)
    signing_secret = os.environ.get("SIGNING_SECRET")
    content_id = "CONTENT_ID"   # Replace with your media ID
    player_id = "PLAYER_ID" # Replace with your player ID
    embed_type = "js"      # e.g., "js" | "html" (as supported)
    
    
    def signed_player_embed_url(content_id, player_id, embed_type):
        """
        Build a non-JWT signed URL for:
            /players/{content_id}-{player_id}.{embed_type}
        Link valid ~1 hour, normalized to 5 minutes for caching.
        """
        host = "https://cdn.jwplayer.com"
        path = f"players/{content_id}-{player_id}.{embed_type}"
        now = int(time.time())
        expires = math.ceil((now + 3600) / 300) * 300  # 1h, 5m rounding
    
        base = f"{path}:{expires}:{signing_secret}"
        signature = hashlib.md5(base.encode("utf-8")).hexdigest()
    
        return f"{host}/{path}?exp={expires}&sig={signature}"
    
    
    # Generate the signed URL
    url = signed_player_embed_url(content_id, player_id, embed_type)
    print(url)
    
  2. Define the signing_secret, content_id, player_id, and embed_type values.

  3. Optional) Adjust the expires calculation or enter a UNIX timestamp in seconds.

    🚧

    Note the following when setting expires:

    • Typical range: 1 minute to several hours. Shorter durations make signed content more secure but can cause playback issues if the URL expires during playback (especially for long videos or playlists).
    • On high-traffic sites, cache signed URLs to prevent performance issues related to generating signed URLs. For example, cache URLs in intervals of five minutes. Signed requests do not need to be unique.
    • Expirations under one minute may cause playback errors due to clock drift or network delays.
    • For /players/{content_id}-{player_id}.html and /previews/{content_id}-{player_id}.html, JWP only enforces protection up to three hours in the future. However, for DRM content, the protection window will be the same as the DRM policy's license duration.

Now, you can enable URL signing functionality for your property.



Enable URL signing functionality

After you have created signed URLs for all of your content, you must enable URL signing functionality for your properties.

616

URL signing section

Follow these steps to enable the URL signing functionality:

  1. On the Properties page, click the name of a property. The settings page for the property appears.
  2. On the Content & ad enhancements, click Content protection. The Content protection tab appears.
  3. Under URL signing, enable one or both protection options:
    • Secure video URLs
    • Secure player embeds & HLS playlists

The following table explains the secure signing behavior when one or both settings are enabled.

Content Secure video URLs: ON

Secure player embeds & HLS playlists: OFF
Secure video URLs: OFF

Secure player embeds & HLS playlists: ON
Secure video URLs: ON

Secure player embeds & HLS playlists: ON
Playlists (JSON/MRSS) Must be signed N/A Must be signed and have signed link in response
HLS playlists (.m3u8) No signing required Must be signed Must be signed
MPEG-DASH manifests (.mpd) No signing required Must be signed Must be signed
Progressive videos (.mp4, JWP-hosted) Must be signed N/A Must be signed and have signed link in response
Cloud-hosted player libraries N/A Must be signed, but no signed link in response required Must be signed and have signed link in response
Single-line embed players N/A Must be signed, but no signed link in response required Must be signed and have signed link in response
Images, text tracks No signing required No signing required No signing required


Error handling

Error codeError messagePossible conditions
200SuccessContent is requested via a signed URL when URL signing is enabled for all content.
403Access forbiddenContent is requested via an unsigned URL when URL signing is enabled for all publisher content.

Content is requested via an incorrectly signed URL when URL signing is enabled for all content.


FAQ

Does URL token signing work the same for JWP hosted and externally hosted (registered) media?

No.

For JW Platform hosted media, both the request to the Delivery API and all media URLs will be signed.

For externally hosted (registered) media, only the request to the Delivery API and the returned media URL will be signed. The signed media URL from the Delivery API response will redirect (HTTP status code 302) to the externally hosted media URL. The JW Platform is not currently able to sign the externally hosted content URL.



© 2007- Longtail Ad Solutions, Inc.