← Back to Guides

🎮 HTML5 Game Integration

Add license validation to your browser game in under 10 minutes. Works with Canvas, WebGL, Phaser, Three.js, PixiJS, or any HTML5 game.

1 Quick Start

Copy the SDK file into your game folder, then add these 3 lines:

<script src="UnifiedLicensing.JavaScript.v4.0-Full.js"></script>
<script>
const manager = new UnifiedLicenseManager({
    vendorApiKey: 'ul_YOUR_API_KEY',
    productKey: 'YOUR_GAME_KEY'
});

const result = await manager.validateLicense('PLAYER-LICENSE-KEY');
if (result.valid) {
    startGame();
} else {
    showLicenseScreen();
}
</script>

That's it. The SDK handles caching, offline mode, and fingerprinting automatically.

2 Setup

Get your keys

  1. Log in to your vendor dashboard
  2. API Key — Settings tab, starts with ul_
  3. Product Key — Products tab, click your game, copy the key

Add the SDK to your project

Copy UnifiedLicensing.JavaScript.v4.0-Full.js into your game folder. It's one file, no dependencies.

my-game/
  index.html
  game.js
  UnifiedLicensing.JavaScript.v4.0-Full.js  <-- put it here
  assets/
Tip: The Full SDK gives you encrypted local storage, hardware binding, and offline support. If you just need basic validation, the Phase1 SDK is smaller.

3 License Screen

Show a license input screen before the game loads. The game should not start until the player has a valid license.

Here's a simple license screen pattern:

<div id="license-screen">
    <h2>Enter Your License Key</h2>
    <input id="license-key" placeholder="XXXX-XXXX-XXXX">
    <button onclick="activateLicense()">Activate</button>
    <p id="license-error" style="color:red"></p>
</div>

<canvas id="game-canvas" style="display:none"></canvas>

<script>
async function activateLicense() {
    const key = document.getElementById('license-key').value.trim();
    const result = await manager.validateLicense(key, null, null, true);
    
    if (result.valid) {
        localStorage.setItem('game_license', key);
        startGame();
    } else {
        document.getElementById('license-error').textContent = result.error || 'Invalid license';
    }
}
</script>

4 Validate on Load

When the page loads, check if the player already has a saved license. If yes, validate it. If not, show the license screen.

window.onload = async () => {
    const savedKey = localStorage.getItem('game_license');
    
    if (savedKey) {
        const result = await manager.validateLicense(savedKey);
        if (result.valid) {
            startGame();
            return;
        }
        // License expired or revoked - clear it
        localStorage.removeItem('game_license');
    }
    
    showLicenseScreen();
};
How this works: The SDK first checks its local cache (instant). If the cache is valid, the game starts immediately. In the background, it re-validates with the server. If the server says the license is revoked, the cache is invalidated.

5 Activate License

When a player enters a license key:

  1. Call validateLicense(key)
  2. If valid, save to localStorage and start the game
  3. If invalid, show an error message
async function activateLicense() {
    const key = document.getElementById('license-key').value.trim();
    
    if (!key) {
        showError('Please enter a license key');
        return;
    }
    
    const result = await manager.validateLicense(key, null, null, true);
    
    if (result.valid) {
        localStorage.setItem('game_license', key);
        startGame();
    } else if (result.quota_exceeded) {
        showError('Service temporarily busy. Please try again in a minute.');
    } else {
        showError('Invalid license key. Please check and try again.');
    }
}

6 Trial Mode

Let players try your game before buying. Trials are tracked on the server, so players can't restart by clearing their browser data.

Start a trial

async function startTrial() {
    const response = await fetch('https://api.unifiedlicensing.com/api/v1/start-trial', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            api_key: 'ul_YOUR_API_KEY',
            product_key: 'YOUR_GAME_KEY',
            machine_id: getMachineId(),
            platform: 'web_client'
        })
    });
    
    const data = await response.json();
    if (data.trial_token) {
        localStorage.setItem('trial_token', data.trial_token);
        startGame();
        showTrialBanner(data.remaining_days || 7);
    }
}

function showTrialBanner(days) {
    document.getElementById('trial-banner').textContent = `Trial: ${days} days remaining`;
    document.getElementById('trial-banner').style.display = 'block';
}

Check trial on load

// On page load, after checking for license
const trialToken = localStorage.getItem('trial_token');
if (trialToken) {
    const response = await fetch('https://api.unifiedlicensing.com/api/v1/check-trial', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            api_key: 'ul_YOUR_API_KEY',
            product_key: 'YOUR_GAME_KEY',
            trial_token: trialToken,
            machine_id: getMachineId(),
            platform: 'web_client'
        })
    });
    const data = await response.json();
    if (data.valid) {
        startGame();
        showTrialBanner(data.remaining_days);
    } else {
        showLicenseScreen(); // Trial expired, show license input
    }
}

7 Game Engine Integration

Phaser

// Start game after license validated
const game = new Phaser.Game({ /* your config */ });

// Pause until licensed
game.scene.scenes.forEach(scene => scene.scene.pause());

// After license validation succeeds:
function startGame() {
    document.getElementById('license-screen').style.display = 'none';
    document.getElementById('game-canvas').style.display = 'block';
    game.scene.scenes.forEach(scene => scene.scene.resume());
}

Three.js

let renderer, scene, camera;
let gameRunning = false;

function initGame() {
    renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('game-canvas') });
    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    gameRunning = true;
    animate();
}

function animate() {
    if (!gameRunning) return;
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
}

// Call initGame() only after license is validated

Raw Canvas

const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');

function startGame() {
    document.getElementById('license-screen').style.display = 'none';
    canvas.style.display = 'block';
    gameLoop();
}

function gameLoop() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    // Your game rendering here
    requestAnimationFrame(gameLoop);
}

8 Why Clearing Storage Doesn't Help

A common question: "What if the player just deletes localStorage?"

Here's the key insight: localStorage is NOT the source of truth.

In short: Clearing storage just means the player has to re-enter their license key. It doesn't give them a free game.

9 Complete Game Example

Here's a full working HTML page with license screen, trial, and a simple game:

<!DOCTYPE html>
<html>
<head>
    <title>My Licensed Game</title>
    <style>
        body { margin: 0; background: #1a1a2e; color: #EFEAE0; font-family: system-ui; }
        #license-screen { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; }
        #license-screen input { padding: 12px; font-size: 18px; width: 300px; margin: 10px; border-radius: 8px; border: 1px solid #C7A34C; background: #14263B; color: #EFEAE0; }
        #license-screen button { padding: 12px 24px; font-size: 16px; background: #C7A34C; color: #081220; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; }
        #license-screen button:hover { background: #E4C468; }
        #trial-banner { display: none; position: fixed; top: 0; left: 0; right: 0; background: #C7A34C; color: #081220; text-align: center; padding: 8px; font-weight: bold; z-index: 100; }
        #game-canvas { display: none; margin: 0 auto; }
        .error { color: #B5502E; margin-top: 8px; }
        .links { margin-top: 20px; }
        .links a { color: #C7A34C; margin: 0 10px; cursor: pointer; }
    </style>
</head>
<body>
    <div id="trial-banner"></div>

    <div id="license-screen">
        <h1>My Game</h1>
        <p>Enter your license key to play</p>
        <input id="license-key" placeholder="XXXX-XXXX-XXXX">
        <button onclick="activateLicense()">Play</button>
        <div class="links">
            <a onclick="startTrial()">Start Free Trial</a>
        </div>
        <p id="error" class="error"></p>
    </div>

    <canvas id="game-canvas" width="800" height="600"></canvas>

    <script src="UnifiedLicensing.JavaScript.v4.0-Full.js"></script>
    <script>
    const manager = new UnifiedLicenseManager({
        vendorApiKey: 'ul_YOUR_API_KEY',
        productKey: 'YOUR_GAME_KEY'
    });

    // On load: check for saved license or trial
    window.onload = async () => {
        const savedKey = localStorage.getItem('game_license');
        if (savedKey) {
            const result = await manager.validateLicense(savedKey);
            if (result.valid) { startGame(); return; }
            localStorage.removeItem('game_license');
        }

        const trialToken = localStorage.getItem('trial_token');
        if (trialToken) {
            // Check trial with server...
            // If valid, startGame()
        }
    };

    async function activateLicense() {
        const key = document.getElementById('license-key').value.trim();
        const result = await manager.validateLicense(key, null, null, true);
        if (result.valid) {
            localStorage.setItem('game_license', key);
            startGame();
        } else {
            document.getElementById('error').textContent = result.error || 'Invalid license';
        }
    }

    async function startTrial() {
        const resp = await fetch('https://api.unifiedlicensing.com/api/v1/start-trial', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                api_key: 'ul_YOUR_API_KEY',
                product_key: 'YOUR_GAME_KEY',
                machine_id: navigator.userAgent.substring(0, 16),
                platform: 'web_client'
            })
        });
        const data = await resp.json();
        if (data.trial_token) {
            localStorage.setItem('trial_token', data.trial_token);
            startGame();
        }
    }

    function startGame() {
        document.getElementById('license-screen').style.display = 'none';
        const canvas = document.getElementById('game-canvas');
        canvas.style.display = 'block';
        const ctx = canvas.getContext('2d');

        // Simple demo game - a bouncing ball
        let x = 400, y = 300, dx = 3, dy = 2;
        function loop() {
            ctx.fillStyle = '#0E1B2C';
            ctx.fillRect(0, 0, 800, 600);
            ctx.beginPath();
            ctx.arc(x, y, 20, 0, Math.PI * 2);
            ctx.fillStyle = '#C7A34C';
            ctx.fill();
            x += dx; y += dy;
            if (x < 20 || x > 780) dx = -dx;
            if (y < 20 || y > 580) dy = -dy;
            requestAnimationFrame(loop);
        }
        loop();
    }
    </script>
</body>
</html>

10 Itch.io & Game Distribution

If you host on itch.io, the game runs inside an iframe. Keep these things in mind:

Pro tip: Sell keys on Gumroad or Lemon Squeezy, then players paste them into your game on itch.io. Works great for indie devs.

11 Security Hardening

HTML5 games are fully client-side — the player's browser runs your code. No check is uncrackable, but you can make piracy annoying enough that most people pay.

11.1 — Gate on every level load, not just startup

Don't check the license once at the top. Check before each premium level or feature:

function loadLevel(levelId) {
  const level = levels[levelId];
  if (level.premium && !gameLicense.valid) {
    showUpgradeScreen();
    return;
  }
  startLevel(level);
}
Why: A pirate who removes the initial check still hits gates at every premium level. They'd have to rewrite your entire gating logic.

11.2 — Server-side content delivery

The strongest protection: don't put premium assets in the download. Load them from YOUR server only after validation:

// Only download premium assets if license is valid
if (gameLicense.valid) {
  const premiumAssets = await fetch('https://api.yourgame.com/assets/premium', {
    headers: { 'Authorization': `Bearer ${gameLicense.key}` }
  });
  // Load premium levels, characters, etc.
}

11.3 — Don't store game state in obvious places

Players can edit localStorage directly. Don't store save data that can be trivially modified:

// BAD — player can edit this
localStorage.setItem('player_coins', '999999');

// BETTER — validate server-side
const balance = await fetch('/api/balance', { headers: { auth: licenseKey } });

11.4 — Summary

LayerWhat it stopsEffort to bypass
Per-level gatingRemoving initial checkRewrite all gates
Server-side assetsCopying the downloadReverse-engineer your API
Server-side statelocalStorage editsFake server responses
Device fingerprintingAccount sharingSpoofer fingerprint

For HTML5 games: server-side asset delivery is the real moat. Everything else slows down casual pirates. If your game is premium enough, consider a thin server backend that serves levels dynamically.

12 Troubleshooting

ProblemFix
CORS error in consoleMake sure you're calling the full URL: https://api.unifiedlicensing.com/api/v1/validate-license. Don't use relative paths.
"Invalid license" for valid keyCheck that your vendorApiKey and productKey match what's in the dashboard.
Game loads before license checkPut your license check in window.onload, not inline. Make sure the canvas starts with display:none.
Quota exceeded errorYou've hit the daily API limit. The SDK will use cached data. Wait 24 hours or upgrade your plan.
Game runs without valid licenseYou're not checking the validation result before starting. Always gate startGame() behind a successful validation.
Trial restarted after clearing browserThis shouldn't happen. Trials are tracked by device fingerprint on the server. If it does, your platform might be set wrong.