Disclaimer: This website requires Please enable JavaScript in your browser settings for the best experience.

The availability of features may depend on your plan type. Contact your Customer Success Manager if you have any questions.

Dev GuideAPI Reference
Dev GuideAPI ReferenceUser GuideLegal TermsGitHubDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

Snippet integrity check with Cloudflare

How to create a system that verifies the integrity of your Optimizely Web Experimentation snippet using Cloudflare Workers

You can verify the integrity of your Optimizely Web Experimentation snippet using Cloudflare Workers to ensure the snippet is running properly.

The snippet contains two parts.

  • Static code – The core Optimizely library code and functionality. It only changes when Optimizely releases updates to their SDK.
  • Dynamic code – Your specific experiment configurations, variations, custom code, and targeting rules. This changes frequently as you create, update, and conclude experiments.

The integrity check system verifies the static code. You cannot check the dynamic code effectively with a simple hash comparison because the code changes constantly. Instead, you must manage changes to the dynamic code through internal governance processes.

What this system does

  • Downloads the snippet – Automatically fetches the latest version of your snippet from Optimizely's CDN.
  • Splits the snippet – Divides it into static (core functionality) and dynamic (experiment configuration) parts.
  • Calculates a hash – Creates a cryptographic "fingerprint" (a SHA-256 hash) of the static part.
  • Compares hashes – Checks if the current hash matches a previously stored hash.
  • Tracks revisions – Monitors Optimizely's revision numbers to detect changes.
  • Stores data – Uses Cloudflare Workers KV (a key-value store) to save the trusted hash and revision number.
  • Reports results – Provides a clear success or failure response, indicating whether the static part of the snippet is valid.

Prerequisites

  • A Cloudflare account
  • The Wrangler CLI installed (npm install -g wrangler)
  • Your Optimizely Project ID (found in your Optimizely snippet's filename)

Steps

Configure the project

  1. Create a Cloudflare Workers project.

    Bash  
    wrangler init optimizely-integrity-check
    
  2. Change to the project directory.

    Bash  
    cd optimizely-integrity-check
    

Create a Cloudflare Workers KV Namespace

Open the wrangler.toml file in your project and update it.

name = "optimizely-integrity-check"
type = "javascript"
account_id = "YOUR_ACCOUNT_ID"  # Replace with your Cloudflare Account ID
workers_dev = true
route = ""
zone_id = ""
compatibility_date = "2023-11-21" # Or a more recent date

kv_namespaces = [
  { binding = "OPTIMIZELY_INTEGRITY", id = "YOUR_KV_NAMESPACE_ID" } # Replace with your KV Namespace ID
]
  • account_id – Replace YOUR_ACCOUNT_ID with your Cloudflare account ID.
  • kv_namespaces – Replace YOUR_KV_NAMESPACE_ID with the ID of the KV namespace you created. The binding name (OPTIMIZELY_INTEGRITY) must match the name used in the code.

Create the Worker code (index.js)

Create a file named index.js in your project directory and paste the following code:

// --- Configuration ---
const OPTIMIZELY_CDN_URL = "https://cdn.optimizely.com/js/";
const OPTIMIZELY_PROJECT_ID = "YOUR_PROJECT_ID";  // Replace with *your* Project ID

// --- Helper Functions ---

async function sha256(message) {
    const msgBuffer = new TextEncoder().encode(message);
    const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    return hashHex;
}

function splitOptimizelySnippet(snippet) {
    const splitPoint = `"layers": [`;
    const splitIndex = snippet.indexOf(splitPoint);
    if (splitIndex === -1) {
        throw new Error("Could not find 'layers' marker in Optimizely snippet.");
    }
    return {
        staticCode: snippet.substring(0, splitIndex),
        dynamicCode: snippet.substring(splitIndex)
    };
}

function extractRevision(snippet) {
    const revisionRegex = /"revision":\s*"(\d+)"/;
    const match = snippet.match(revisionRegex);
    return (match && match[1]) ? match[1] : null;
}

// --- Cloudflare Workers KV Interaction ---

async function getStoredHashAndRevision() {
    const hash = await OPTIMIZELY_INTEGRITY.get("staticHash");
    const revision = await OPTIMIZELY_INTEGRITY.get("revision");
    return { hash, revision };
}

async function storeHashAndRevision(hash, revision) {
    await OPTIMIZELY_INTEGRITY.put("staticHash", hash);
    await OPTIMIZELY_INTEGRITY.put("revision", revision);
}


// --- Main Event Handler ---
addEventListener('fetch', event => {
    event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
    try {
        const snippetUrl = `${OPTIMIZELY_CDN_URL}${OPTIMIZELY_PROJECT_ID}.js`;
        const snippetResponse = await fetch(snippetUrl);
        if (!snippetResponse.ok) {
            throw new Error(`Failed to fetch snippet: ${snippetResponse.status}`);
        }
        const snippetText = await snippetResponse.text();

        const currentRevision = extractRevision(snippetText);
        if (!currentRevision) {
            throw new Error("Could not extract revision number.");
        }

        const { staticCode, dynamicCode } = splitOptimizelySnippet(snippetText);
        const currentStaticHash = await sha256(staticCode);

        const { hash: previousStaticHash, revision: previousRevision } = await getStoredHashAndRevision();

        let status = "success";
        let message = "Integrity check passed.";

        if (!previousStaticHash) { // First run (Baseline Establishment)
            message = "Baseline established.  First-time run.";
            await storeHashAndRevision(currentStaticHash, currentRevision);
        } else if (currentStaticHash !== previousStaticHash) {
            status = "failure";
            message = "Integrity check FAILED. Static code has changed.";
             // Do *not* update the stored hash here!
        } else if (currentRevision === previousRevision) {
            message = "No changes detected (same revision).";
        }
         else{
             // Hash is the same, but revision changed (dynamic content update)
            message = "Dynamic content updated (new revision). Static code integrity verified.";
            await storeHashAndRevision(currentStaticHash, currentRevision); // Update stored revision

         }

        return new Response(JSON.stringify({
            status,
            message,
            revision: currentRevision,
            currentStaticHash,
            previousStaticHash,
            snippetUrl
        }), { headers: { 'Content-Type': 'application/json' } });

    } catch (error) {
        return new Response(JSON.stringify({ status: "error", message: error.message, error: error.stack }), { status: 500 });
    }
}

OPTIMIZELY_PROJECT_ID – Replace YOUR_PROJECT_ID with your actual Optimizely Project ID. This is usually part of the filename of your Optimizely snippet, such as 1234567890.js.

How the code works

  • Configuration – Sets the URL for Optimizely's CDN and your project ID.
  • sha256() – Calculates the SHA-256 hash (a cryptographic fingerprint) of a given string.
  • splitOptimizelySnippet() – Splits the Optimizely snippet into static and dynamic parts based on the "layers": [ marker.
  • extractRevision() – Finds the revision number within the snippet using a regular expression.
  • KV Interaction – getStoredHashAndRevision() and storeHashAndRevision() handle reading and writing the trusted hash and revision to Cloudflare Workers KV.
  • handleRequest() – Runs when the worker is triggered. This is the main function and does the following:
    • Fetches the Optimizely snippet.
    • Extracts the revision number.
    • Splits the snippet.
    • Calculates the hash of the static code.
    • Retrieves the previously stored hash and revision from KV.
    • Compares the current hash and revision with the stored values.
    • Returns a JSON response indicating success or failure, along with relevant details.
    • Handles the first run (baseline establishment) by storing the initial hash and revision.

Deploy

  1. Log in to the Wrangler CLI.

    Bash  
    wrangler login
    
  2. Deploy the worker.

    Bash  
    wrangler publish
    

Establish the initial baseline

After deploying your Optimizely snippet and the Cloudflare Worker, follow these steps:

  1. Ensure you deploy your initial Optimizely snippet through your standard, secure deployment process. This is the snippet you are trusting.
  2. Make a request to your deployed worker's URL (you get this URL from Wrangler after the publish command). You can use a browser, curl, or any HTTP client.
  3. Verify the response. The worker's response should include the following:
    • status – success
    • message – Baseline established. First-time run.
    • The currentStaticHash and revision values. These are now your baseline.
  4. (Optional but recommended) Manually download the snippet from the snippetUrl in the response and compare it to what you expect from your initial Optimizely configuration.

Ongoing use

  1. Webhook integration (separate system) – You need a separate system, such as another Cloudflare Worker or a serverless function, to listen for Optimizely's snippet change webhooks. When a webhook is received, this system should trigger your integrity check worker by making an HTTP request to it.
  2. Deployment integration – Your deployment pipeline should only deploy new Optimizely snippets if the integrity check worker returns a success response.
  3. Alerting – Configure alerts to notify you if the worker reports a failure (integrity violation of the static code).

Governance of the dynamic code

Because the dynamic part of the snippet changes frequently, you need a strong internal governance process to manage these changes:

  • Change control – Implement a formal process for reviewing and approving changes to experiments, targeting rules, and custom code within Optimizely.
  • Auditing – Use Optimizely's built-in audit logs (if available) to track who made changes and when.
  • Testing – Test all changes thoroughly in a staging environment before deploying them to production.
  • Approvals – Require multiple approvals for significant changes to experiments.
  • Documentation – Maintain clear documentation of your experiment configurations and custom code.
  • Staging environment – Use a staging or preview environment before pushing into production.

Legitimate static code updates

If you intentionally change the static part of your Optimizely snippet, like when Optimizely releases a new SDK version, follow these steps:

  1. Make changes. Update your Optimizely snippet as needed.
  2. Expect failure. The integrity check (correctly) fails because the static code's hash changes.
  3. Re-establish the basline after carefully verifying your changes.
    • Option 1 (Recommended) – Temporarily disable the integrity check in your deployment pipeline, deploy the new snippet, and trigger the worker again. This updates the stored hash. Re-enable the integrity check.
    • Option 2 – Manually update the staticHash value in your Cloudflare Workers KV storage after you verify the new snippet.