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):
<script src="https://cdn.amplitude.com/script/abc123def456.experiment.js"></script>
After (through your proxy):
<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.
<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
| Header name | Value | Notes |
|---|---|---|
| Authorization | Api-Key <AMPLITUDE_PROJECT_API_KEY> | Store and manage only on the server. Never expose to the browser. |
| X-Amp-Exp-User | <base64-encoded user object> | Forward from browser as-is. Contains {"user_id": "...", "device_id": "..."} |
Return response: Return Amplitude's JSON response unchanged to the browser.
Example request flow
Browser to proxy:
GET https://experiments.acme.com/sdk/v2/flags?delivery_method=web
X-Amp-Exp-User: eyJ1c2VyX2lkIjoidXNlciIsImRldmljZV9pZCI6ImRldmljZSJ9
Proxy to Amplitude:
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:
| Field | Description |
|---|---|
| key | Unique flag key that identifies the experiment. |
| segments | Targeting and bucketing rules pre-evaluated with the user's properties. Amplitude includes conditions that are true and removes others. |
| variants | Available variants including key, metadata, and web experiment actions (mutations, custom code, URL redirects). |
| metadata | Experiment metadata: version, deployment status, and re-run indicators. |
Example response
[
{
"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.
<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?