On this page

Proxy Web Experiment

You might prefer not to load third-party experimentation scripts directly from vendor-hosted domains, or you might want to perform client-side experiment evaluation. Common reasons include security policies, privacy controls, and tighter infrastructure ownership.

A proxy-based experimentation architecture lets teams keep full control over script delivery, evaluation, and exposure tracking while using a centralized experimentation platform.

This document outlines common proxy patterns and the implementation details that support them.

Script delivery proxy

Customers host a lightweight endpoint that proxies requests for the experimentation JavaScript SDK. End users load the script from a customer-owned domain rather than a third-party domain.

This pattern lets you:

  • Reduce third-party script exposure.
  • Align with internal security or compliance requirements.

CDN configuration

  • Origin: https://cdn.amplitude.com
  • Path rewrite: /script/{API_KEY}.experiment.js → /script/{API_KEY}.experiment.js
  • Cache TTL: 1–5 minutes (balances freshness with performance)
  • Cache key: Full URL including API_KEY

Before (direct to Amplitude):

html
<script src="https://cdn.amplitude.com/script/abc123def456.experiment.js"></script>

After (through your proxy):

html
<script src="https://experiments.acme.com/script/abc123def456.experiment.js"></script>

Remote evaluation proxy

The Web Experiment script calls a single endpoint for remote evaluation. An experiment requires remote evaluation when its targeting rules reference at least one property that isn't available locally on the client.

This pattern lets you:

  • Centralize control over evaluation logic.
  • Enrich requests with server-side context.

Expose your proxy evaluation endpoint

Create and expose an HTTPS endpoint for the Web Experiment SDK to call for remote evaluation:

Example URLs:

  • Full path: https://experiments.acme.com/sdk/v2/flags?delivery_method=web
  • Base URL: https://experiments.acme.com/

The SDK uses this base URL for all evaluation requests.

Configure the Web Experiment script

Configure the Web Experiment script or SDK to call your new endpoint instead of the Amplitude-hosted URLs (flag.lab.amplitude.com or flag.lab.eu.amplitude.com). Add the following code above the Web Experiment script.

html
<script>
  window.experimentConfig = {
    flagsServerUrl: "https://experiments.acme.com/",
  };
</script>

Implement proxy logic

For each incoming browser request, the proxy must:

Accept request

  • Method: GET.
  • Query parameters: delivery_method=web.

Forward to Amplitude

  • US projects: https://flag.lab.amplitude.com/sdk/v2/flags?delivery_method=web.
  • EU projects: https://flag.lab.eu.amplitude.com/sdk/v2/flags?delivery_method=web.

Inject headers

Return response: Return Amplitude's JSON response unchanged to the browser.


Example request flow

Browser to proxy:

plaintext
GET https://experiments.acme.com/sdk/v2/flags?delivery_method=web
X-Amp-Exp-User: eyJ1c2VyX2lkIjoidXNlciIsImRldmljZV9pZCI6ImRldmljZSJ9

Proxy to Amplitude:

plaintext
GET https://flag.lab.amplitude.com/sdk/v2/flags?delivery_method=web
Authorization: Api-Key <PROJECT_API_KEY>
X-Amp-Exp-User: eyJ1c2VyX2lkIjoidXNlciIsImRldmljZV9pZCI6ImRldmljZSJ9

Proxy to browser: Returns Amplitude's evaluation response.

Response format

The endpoint returns a JSON array of experiment flag objects pre-evaluated for the user.

Flag object schema:


Example response

json
[
  {
    "key": "example",
    "metadata": {
      "deliveryMethod": "web",
      "deployed": true,
      "evaluationMode": "local",
      "experimentKey": "exp-1",
      "exposureEvent": "$impression",
      "flagType": "experiment",
      "flagVersion": 5
    },
    "segments": [
      {
        "bucket": {
          "allocations": [
            {
              "distributions": [
                { "range": [0, 21474837], "variant": "control" },
                { "range": [21474836, 42949673], "variant": "treatment" }
              ],
              "range": [0, 100]
            }
          ],
          "salt": "G0U0BTSK",
          "selector": ["context", "user", "web_exp_id"]
        },
        "conditions": [
          [
            {
              "op": "is",
              "selector": ["context", "user", "device_category"],
              "values": ["desktop"]
            }
          ]
        ],
        "metadata": { "segmentName": "Segment 1" },
        "variant": "off"
      },
      {
        "metadata": { "segmentName": "All Other Users" },
        "variant": "off"
      }
    ],
    "variants": {
      "control": {
        "key": "control",
        "payload": [{ "action": "mutate", "data": { "mutations": [] } }],
        "value": "control"
      },
      "off": { "key": "off", "metadata": { "default": true } },
      "treatment": {
        "key": "treatment",
        "payload": [
          {
            "action": "mutate",
            "data": {
              "mutations": [
                {
                  "action": "set",
                  "attribute": "html",
                  "metadata": {
                    "scope": ["89e01141-6e35-4bc0-bb66-0e1bc0fd4823"],
                    "type": "text"
                  },
                  "selector": ".md\\:max-w-max:nth-child(1)",
                  "value": "updated text"
                }
              ]
            }
          }
        ],
        "value": "treatment"
      }
    }
  }
]

Impression (exposure) event forwarding

Customer-controlled backend systems send impression (exposure) events to the experimentation platform rather than the browser sending them directly.

This pattern lets you:

  • Unify event pipelines.
  • Simplify compliance with data governance policies.

If you already use Amplitude's Analytics SDK, update the tracking endpoint for the browser SDK through serverUrl.

If you aren't using Amplitude's Analytics SDK, add the following impression event forwarding script above the Web Experiment script.

html
<script>
  // TODO: Replace this object with your tracker implementation.
  const customTracker = {
    track: (eventType, eventProperties) => {
      // TODO: Send eventType and eventProperties to your analytics tool.
      console.log({ eventType, eventProperties });
    },
  };

  window.experimentIntegration = {
    getUser: () => {
      // TODO: Return user.
      return {
        user_id: "user",
        device_id: "device",
      };
    },

    track: (e) => {
      // TODO: Track event
      customTracker.track(e.eventType, e.eventProperties);
      return true;
    },
  };
</script>

Was this helpful?