# Commanders Act Gateway

This document describes how to deploy **Commanders Act Gateway**, a **unified first-party gateway** that uses **one single setup and one single path on your domain** to power multiple tracking and hosting use cases.

Commanders Act Gateway includes **Google Tag Gateway**, but is **not limited to Google**.\
It is designed to serve and collect data for **all your marketing and analytics partners**, using the same first-party infrastructure.

One setup, one first-party path, three usages:

* Google Tag Gateway (GA4, Google Ads)
* First-party tracking to all server-side destinations
* First-party hosting of third-party libraries

If your goal is to implement Google Tag Gateway, you are in the right place.\
If your goal is to build a durable, vendor-agnostic first-party tracking architecture, you are also in the right place.

<figure><img src="https://1259070148-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mk6XpTQ2LaRLcr2tA-d%2Fuploads%2Fgit-blob-5c269a1cb309c24a7504446eef7a53b7aafd97d9%2Fschema_google_ads%20(4).png?alt=media" alt=""><figcaption></figcaption></figure>

***

## Why use Commanders Gateway?

### 1. Advantages of using a gateway

A gateway setup improves **data collection quality and completeness** across your marketing stack.

* Vendor scripts are served from your own domain, which redauces the likelihood of being blocked by adblockers.
* Browser restrictions (such as Safari’s ITP) often limit or block third-party cookies and some 1st party javascript cookies, but with a first-party server-side setup, measurement remains more reliable.
* This ensures **more accurate tracking**, providing partners with higher-quality signals for measurement, attribution, and optimization.

### 2. Advantages of using Commanders Gateway

On top of the benefits of any gateway approach, **Commanders Gateway** adds unique advantages:

* Not limited to Google Tag Gateway — the same durable setup applies to **all your partners** (Meta, Snapchat, Bing, Awin, etc.).
* Unified setup: a **single path** (`/metrics`) serves and proxies all vendor libraries.
* Obfuscated JavaScript filenames are automatically provided by Commanders Act, making detection by blocking lists far more difficult.
* With the same simple path, you can also activate other **first-party hosting and tracking features** such as: hosting your tag management containers, server-side event tracking, or anonymous CMP statistics. A single setup powers your whole first‑party hosting and tracking system.
* A centralized configuration simplifies deployment and maintenance while remaining **future-proof** against upcoming browser restrictions.

***

## Overview

**Commanders Gateway** lets you deploy marketing and measurement tags using your **own first-party infrastructure**, hosted on your website’s domain.\
This infrastructure sits between your website and your partners’ services (Google, Meta, Bing, Snapchat, Awin, etc.).

With Commanders Gateway:

* Google libraries (gtag.js / gtm.js) are loaded directly from your **first-party domain**.
* Other vendor libraries are served from `/metrics/js/` using **obfuscated filenames**.
* All measurement requests are proxied through your domain before being forwarded to the respective partner endpoints.

***

## Architecture

With **Commanders Gateway**, you reserve a **single path** on your domain, for example:

```
https://example.com/metrics/
```

* **Google scripts** (gtag.js / gtm.js) are loaded directly from `/metrics/`.
* **Other vendor scripts** (Meta, Snapchat, Bing, Awin, etc.) are served from `/metrics/js/` with an **obfuscated filename** generated by Commanders Act.

Example:

```
https://example.com/metrics/js/f4558899203.js
```

The mapping between each vendor and its obfuscated script filename is provided in the **Commanders Act First-Party Hosting interface**.

**Diagram (conceptual):**

```
Website  →  example.com/metrics/ (Google tags)
         →  example.com/metrics/js/f4558899203.js (Meta, Snap, Bing…)
         →  Commanders Gateway  →  Vendor endpoint
```

***

## Cookie filtering and governance

Some organizations, especially those with strict privacy policies, may have concerns about sending **first-party cookies to external partners** such as Google. Commanders Gateway supports **data minimization** and provides mechanisms to control which cookies can transit through the gateway. Two complementary approaches can be used :

#### Cookie blacklisting at the edge

Customers can filter cookies **directly at the CDN or edge layer** (Cloudflare Worker, Fastly Compute, etc.). This can be done by **simply configuring the Worker code provided in this guide** (see CloudFlare free or Faslty tab below) to remove specific cookies before the request is forwarded to Commanders Gateway.

This allows specific cookies to be removed from the request **before it reaches Commanders Gateway**, ensuring that only the cookies approved by the organization leave its infrastructure.

#### Cookie whitelisting before forwarding to partners

Commanders Gateway can also enforce a **cookie whitelist when forwarding requests to partners**.

For example, when forwarding measurement requests to Google, the gateway can be configured to **only include Google-related cookies** (such as `_ga` or `_gcl_*`).\
All other cookies are automatically excluded from the request sent to Google.

## Before you begin

This guide assumes your website is already configured with:

* A tag management system (Commanders Act, Google Tag Manager, or equivalent).
* A CDN or load balancer (Cloudflare, Akamai, Fastly, Nginx, etc.) that can forward requests to external endpoints.

***

## Step 1: Choose the tag serving path

You must reserve **one path** on your website domain.

Example:

```
/metrics
```

Caution: This setup reroutes all traffic with the chosen path. To avoid affecting your website, choose a path that's not already in use.

***

## Step 2: Route traffic

{% tabs %}
{% tab title="Cloudflare Enterprise" %}
To serve your tag in Commanders Gateway, you will create a CNAME entry for a new subdomain, create an [Origin Rule](https://developers.cloudflare.com/rules/origin-rules/) to forward requests, and create a [Transform Rule](https://developers.cloudflare.com/rules/transform/) to include geolocation information. To complete this setup, you will need to have a Cloudflare Enterprise plan. If you don't have an Enterprise plan, consider using the Cloudflare automated setup instead.

**Create CNAME entry**

**Note:** Tags won't use this CNAME entry; Cloudflare uses it to route requests internally.

Choose a subdomain to reserve for the CNAME entry. This CNAME is never exposed outside your Cloudflare configuration, so the name is arbitrary.

```
CNAME subdomain: metrics
Target: s1234.commander4.com
```

1. In the **DNS** tab, open the **Records** section.
2. Add a new record with:
   * **Type**: CNAME
   * **Name**: metrics
   * **Target**: s1234.commander4.com => replace 1234 by your site (aka workspace) id
3. Save the CNAME record.

**Create the Origin Rule**

1. In the **Rules** tab, open **Origin Rules** and create a new rule.
2. Enter a rule name, such as *Route measurement*.
3. Match incoming requests based on a custom filter expression:

```
(http.host eq "example.com" and starts_with(http.request.uri.path, "/metrics"))
```

4. Update the **Host Header** → Rewrite to: `s1234.commander4.com.`
5. Update the **DNS Record** → Override to: `metrics.example.com`.
6. Save the Origin Rule.

**Include geolocation information (optional)**

1. In the **Rules** tab, open **Settings**.
2. Enable the **Add visitor location headers** option.
3. Wait a few minutes for propagation.

You can verify by navigating to:

```
https://example.com/metrics/healthy
```

It should return `ok`.
{% endtab %}

{% tab title="Cloudflare Free" %}
When using Cloudflare Free, the setup relies on a **simple Worker** that proxies all traffic from your chosen path (e.g. `/metrics`) to Commanders Gateway infrastructure.

**Step 1: Create the Worker**

1. In the Cloudflare dashboard, go to **Workers & Pages** → **Create application** → **Worker**.
2. Copy/paste the following code:

```javascript
const prefix = "/metrics"; // Example path, replace with the path you choose in the previous step
const sid = "12345"; // Example workspace ID (aka site ID), replace with your own ID

// List of cookie names that must NOT be forwarded to Commanders Gateway. You can add your technical cookies if needed
const blacklistedCookies = [
  "PHPSESSID",
  "JSESSIONID"
];

addEventListener("fetch", event => {
  event.respondWith(handleRequest(event.request));
});

function filterCookieHeader(cookieHeader, blacklist) {
  if (!cookieHeader) return "";

  const blacklistSet = new Set(blacklist);

  const filteredCookies = cookieHeader
    .split(";")
    .map(cookie => cookie.trim())
    .filter(cookie => {
      const cookieName = cookie.split("=")[0];
      return !blacklistSet.has(cookieName);
    });

  return filteredCookies.join("; ");
}

async function handleRequest(request) {
  const url = new URL(request.url);
  if (url.pathname.startsWith(prefix)) {
    // Construct target URL (it replaces ${sid} with your workspace/site ID above)
    const targetUrl = `https://s${sid}.commander4.com${url.pathname}${url.search}`;

    // Clone request headers
    const newHeaders = new Headers(request.headers);
    newHeaders.set("X-Forwarded-Host", url.host);

    const country = request.cf?.country || "";
    const region = request.cf?.region || "";
    if (country) newHeaders.set("X-Forwarded-Country", country);
    if (region) newHeaders.set("X-Forwarded-Region", region);
    if (country && region) {
      newHeaders.set("X-Forwarded-CountryRegion", `${country}-${region}`);
    }

    // Filter the Cookie header before proxying the request.
    const cookieHeader = newHeaders.get("Cookie");
    const filteredCookies = filterCookieHeader(cookieHeader, blacklistedCookies);

    if (filteredCookies) {
      newHeaders.set("Cookie", filteredCookies);
    } else {
      newHeaders.delete("Cookie");
    }

    // Remove Host header to avoid conflicts
    newHeaders.delete("host");

    // Proxy request to Commanders Gateway infra
    const proxyRequest = new Request(targetUrl, {
      method: request.method,
      headers: newHeaders,
      body: request.body,
      redirect: "follow"
    });
    return fetch(proxyRequest);
  }
  return new Response("Not Found", { status: 404 }); // Return 404 if request path does not match prefix
}
```

This Worker proxies requests while adding extra headers (`X-Forwarded-Host`, `X-Forwarded-Country`, `X-Forwarded-Region`).

**Step 2: Bind the Worker to the path**

1. In Cloudflare, open your domain settings.
2. Navigate to **Workers Routes**.
3. Add a new route with:
   * **URL pattern**: `www.example.com/metrics*`
   * **Worker**: select the Worker created in step 1.

Once saved, all requests to `/metrics` will be proxied to Commanders Gateway.
{% endtab %}

{% tab title="Akamai" %}
{% hint style="warning" %}
Commanders Gateway with Akamai is in **beta**. If you have a question or issue with your setup, reach out the support
{% endhint %}

**Create the redirect rule**

1. Create a new version of your delivery configuration in **Property Manager**.
2. Under the **Property Configuration Settings** section, add a new Rule:
   * Name it: *Route measurement*
3. Add a new **Match**:
   * Match type: `Path`
   * Condition: *is one of*
   * Value: `/metrics/*`
4. Add a new **Behavior**:
   * Select *Standard Property Behavior* and choose **Origin Server** behavior.
   * Set **Origin Server Hostname** to `s1234.commander4.com.`
   * Set **Forward Host Header** to *Origin Hostname*.
5. Save the new rule and deploy your changes.
   * ⚠️ Test the redirect rule in your **staging environment** before rolling out to production.
   * Ensure no other rules modify/remove outgoing response headers (e.g., *Content-Type*) as this may break scripts.

***

**Include geolocation information**

1. Navigate to the **Property Variables** section and add the following variables:

| Variable name | Security settings |
| ------------- | ----------------- |
| USER\_REGION  | Hidden            |
| USER\_COUNTRY | Hidden            |

2. Choose your **Redirect rule** (created above) under Property Configuration Settings.
3. Add two new **Set Variable** behaviors (one per variable):

| Variable              | Create Value From | Get Data From  | Edgescape Field | Operation |
| --------------------- | ----------------- | -------------- | --------------- | --------- |
| PMUSER\_USER\_REGION  | Extract           | Edgescape Data | Region Code     | None      |
| PMUSER\_USER\_COUNTRY | Extract           | Edgescape Data | Country Code    | None      |

4. Add two new **Modify Outgoing Request Header** behaviors:

| Action | Select Header Name | Custom Header Name  | Header Value                   |
| ------ | ------------------ | ------------------- | ------------------------------ |
| Add    | Other...           | X-Forwarded-Region  | {{user.PMUSER\_USER\_REGION}}  |
| Add    | Other...           | X-Forwarded-Country | {{user.PMUSER\_USER\_COUNTRY}} |

5. Save the new rule and deploy your changes.
6. Verify the setup:
   * Navigate to: `https://example.com/metrics/healthy` → should display `ok`.
   * Test geolocation headers: `https://example.com/metrics/?validate_geo=healthy` → should also display `ok`.
     {% endtab %}

{% tab title="Fastly" %}
{% hint style="warning" %}
Fastly support for Commanders Gateway is currently in **beta**. The steps below are intended for technical users familiar with Fastly Compute (Compute Services). Depending on your Fastly account setup (domains, TLS, products enabled), some production binding steps can vary.
{% endhint %}

When using Fastly, the setup is different from Cloudflare. You deploy a **Compute service** (Wasm) and configure it mainly via the **Fastly API and CLI** from a terminal.

**Prerequisites**

1. Create an API token in the Fastly interface (scopes must allow Compute, services, backends, and deployments).
2. Export the token in your terminal environment:

```
export FASTLY_API_TOKEN=XXXXXXXXXXXX
```

3. Install prerequisites:

* Node.js
* Fastly CLI

**Step 1: Create the Compute service**

Create a new Compute service:

```
fastly service create --name "CA Gateway" --type wasm
```

Fastly returns a **service ID**, for example:

```
dyBxiT8wpc2c8ZQg2KRrMN
```

Save it, you will need it for backend creation and deployments.

**Step 2: Create a local Compute project (starter kit)**

Generate a local project from the default JavaScript starter kit:

```
npm create @fastly/compute@latest -- --language=javascript --default-starter-kit
```

This creates a project structure similar to:

```
.
├── README.md
├── fastly.toml
├── package.json
└── src
    ├── index.js
    └── welcome-to-compute.html
```

**Step 3: Configure the service ID in fastly.toml**

Edit `fastly.toml` and set the service ID:

```
# fastly.toml
service_id = "YOUR_SERVICE_ID"
```

**Step 4: Create the backend (Commanders Gateway origin)**

Create a backend that points to the Commanders Gateway infrastructure (replace `1234` with your workspace/site ID):

```
fastly backend create \
  --service-id YOUR_SERVICE_ID \
  --version 1 \
  --name commander_gateway \
  --address s1234.commander4.com \
  --use-ssl \
  --port 443 \
  --ssl-sni-hostname s1234.commander4.com
```

Notes:

* `--version 1` is a typical starting point. If your service already has versions, use the version you intend to deploy.
* The backend address must be `s1234.commander4.com` (your own workspace/site ID).

**Step 5: Implement the routing logic in src/index.js**

Replace the content of `src/index.js` with the following worker code.

You must update:

* `prefix` (your customer path, example: `/metrics`)
* `sid` (your Commanders workspace/site ID, example: `s1234`)

```javascript
/// <reference types="@fastly/js-compute" />

import { env } from "fastly:env";
import { includeBytes } from "fastly:experimental";

const prefix = "/PATHACHANGER";              // TODO: replace with your chosen gateway path (e.g. "/metrics")
const sid = "s123456";                       // TODO: replace with your workspace/site ID (e.g. "s1234")
const BACKEND = "commander_gateway";         // Fastly backend name pointing to sid.commander4.com

const STRIP_PREFIX = true;                   // If true, removes the prefix from the forwarded path
const PREPEND_PATH = "/gateway";             // Internal gateway entrypoint on Commanders side (do not change)

// List of cookie names that must NOT be forwarded to Commanders Gateway. Add your technical cookies if needed
const blacklistedCookies = [
  "PHPSESSID",
  "JSESSIONID"
];

addEventListener("fetch", (event) => event.respondWith(handleRequest(event.request)));

function filterCookieHeader(cookieHeader, blacklist) {
  if (!cookieHeader) return "";

  const blacklistSet = new Set(blacklist);

  const filteredCookies = cookieHeader
    .split(";")
    .map(cookie => cookie.trim())
    .filter(cookie => {
      const cookieName = cookie.split("=")[0];
      return !blacklistSet.has(cookieName);
    });

  return filteredCookies.join("; ");
}

async function handleRequest(request) {

  const url = new URL(request.url);

  // Only proxy requests that match the configured prefix
  if (!url.pathname.startsWith(prefix)) {
    return new Response("Not Found", { status: 404 });
  }

  // Build the path that will be forwarded to Commanders Gateway
  let fwdPath = url.pathname;

  // Optionally strip the customer prefix (e.g. "/metrics") so the origin receives "/"
  if (STRIP_PREFIX) {
    fwdPath = fwdPath.slice(prefix.length) || "/";
  }

  // Prepend Commanders internal entrypoint (required)
  if (PREPEND_PATH) {
    fwdPath = PREPEND_PATH + fwdPath;
  }

  // Final origin URL on Commanders infrastructure
  const target = `https://${sid}.commander4.com${fwdPath}${url.search}`;

  // Clone headers and add forwarding info
  const headers = new Headers(request.headers);
  headers.set("X-Forwarded-Host", url.host);

  // Forward geo information when available (optional but recommended)
  const country = (request.geo && request.geo.country_code) ? request.geo.country_code.toUpperCase() : "";
  const region  = (request.geo && request.geo.region) ? request.geo.region : "";
  if (country) headers.set("X-Forwarded-Country", country);
  if (region)  headers.set("X-Forwarded-Region", region);
  if (country && region) headers.set("X-Forwarded-CountryRegion", `${country}-${region}`);

  // Filter the Cookie header before proxying the request.
  const cookieHeader = headers.get("Cookie");
  const filteredCookies = filterCookieHeader(cookieHeader, blacklistedCookies);

  if (filteredCookies) {
    headers.set("Cookie", filteredCookies);
  } else {
    headers.delete("Cookie"); // Remove the Cookie header entirely if all cookies were filtered out
  }

  // Avoid Host header conflicts at origin
  headers.delete("host");

  // Rebuild the request for the origin
  const originReq = new Request(target, {
    method: request.method,
    headers,
    body: request.body
  });

  // Bypass cache to ensure measurement hits are never cached
  const co = new CacheOverride("pass");

  // Send request to the configured Fastly backend
  return fetch(originReq, { backend: BACKEND, cacheOverride: co });

}
```

Important:

* Replace `s1234.commander4.com` with your real workspace/site ID endpoint.
* Keep the same `prefix` as the path you reserve on the customer domain (example: `/metrics`).
* Do NOT add a trailing slash at the end of the path in customer-side URLs.

**Step 6: Deploy**

Deploy the Compute service:

```
npm run deploy
```

**Step 7: Test**

After deployment, Fastly provides a temporary domain for testing, for example:

```
https://plainly-rested-bison.edgecompute.app/metrics/healthy
```

It should return:

```
ok
```

**Production binding (customer domain)**

At this stage, the Compute service runs on a Fastly-provided test domain. To go live on the customer domain (example: `https://example.com/metrics/`), you still need to bind the service to the production domain and ensure TLS is in place.

This typically involves, depending on the customer setup:

* Adding the customer domain to the Fastly service, and configuring TLS for it (managed TLS or customer certificate).
* Creating the required DNS record (often a CNAME) so `example.com` points to Fastly.
* Ensuring the Compute service is the one receiving requests for the chosen path (example: `/metrics*`) on that domain.

Because the exact steps depend on the Fastly products enabled on the account and how the customer manages TLS and DNS, treat this as a beta step and reach out to support if you need the exact commands for your specific setup.
{% endtab %}
{% endtabs %}

***

## Step 3: Update the scripts in your tag management system or your website

Replace vendor script URLs with the new **first-party paths**.

Examples:

### Google

```html
<!-- Instead of -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-12345"></script>

<!-- Use -->
<script async src="/metrics/"></script>
```

### Meta (Facebook Pixel)

```html
<!-- Instead of -->
<script src="https://connect.facebook.net/en_US/fbevents.js"></script>

<!-- Use (obfuscated path provided in the Commanders Act interface) -->
<script src="/metrics/js/f4558899203.js"></script>
```

### Snapchat

```html
<script src="/metrics/js/a82b99df732.js"></script>
```

### Bing (UET)

```html
<script src="/metrics/js/c77ac91be11.js"></script>
```

Each obfuscated filename is automatically generated and available in the **Commanders Act First-Party Hosting interface**.

### OneTag

You can manually change the domain of your cact() setup with the `collectionDomain` propery Exemple :

```javascript
cact(..., {collectionDomain: "www.youdomain.com/metrics"});
```

Warning : do NOT add a `/` at the end of the path

***

## Step 4: Verify setup

* For the global path, check the health endpoint:
  * `https://example.com/metrics/healthy` → should return `ok`
* Use browser DevTools to verify that:
  * Google scripts are loaded from `/metrics/`
  * Other vendor scripts are loaded from `/metrics/js/{obfuscated}.js`
  * Requests are made to your **first-party domain**.
* Ensure events appear in the respective partner dashboards (Google Analytics, Facebook Events Manager, etc.).

***

## Benefits

* **Durability**: Tracking continues to work even with Safari ITP and third-party cookie restrictions.
* **Resilience**: Serving scripts from your domain with obfuscated filenames makes it more difficult for blocking rules to interfere.
* **Centralized setup**: A single path (`/metrics`) manages all vendors.
* **Future-proof**: Adapts to privacy sandbox and upcoming browser restrictions.
*

## Configure first party data collection for Commanders Act features (via Gateway)

This chapter explains how to route Commanders Act data collection through your **first party gateway path** (for example `/metrics`) for the main Commanders Act features.

Important notes:

* The gateway path shown in examples (`/metrics`) is only an example. Customers choose their own path when setting up the gateway in their CDN or edge tool (Cloudflare, Akamai, etc.).
* All examples below assume your gateway is healthy: `https://example.com/metrics/healthy` returns `ok`.

***

### 1. Server-side destinations via the gateway (example: Meta Facebook CAPI)

Commanders Act server-side tracking relies on **oneTag** tags. Typically, you will have one oneTag per event you want to collect, for example:

* `page_view`
* `add_to_cart`
* `purchase`

To route these oneTag events through the gateway, you must update the **oneTag tag configuration** so that the `cact()` setup uses your first party collection domain and path.

In your oneTag tag (or in the shared snippet used by your oneTag tags), set `collectionDomain`:

```javascript
cact(..., { collectionDomain: "www.yourdomain.com/metrics" });
```

Notes:

* Replace `www.yourdomain.com/metrics` with your own domain and the path you configured in your gateway.
* Do NOT add a trailing `/` at the end of the path.
* Once this is set, all oneTag events (page\_view, add\_to\_cart, purchase, etc.) will be collected via your first party gateway path.

***

### 2. CDP, Campaign Analytics and CMP collection via the gateway

*(Data Activation, Campaign Analytics, CMP statistics and proof of consent)*

These three features rely on the same routing mechanism. To send their data through the gateway, you must define the variable **`tC.clientCollectDns`** either:

* directly inside each relevant tag, **or**
* in a **global configuration tag** that runs before all Commanders Act tags (recommended).

Example:

```javascript
tC.clientCollectDns = "www.yourdomain.com/metrics";
```

Behavior:

* As soon as `tC.clientCollectDns` is defined, collection for **Data Activation, Campaign Analytics, and CMP-related tracking** will be made via the gateway.
* `metrics` is only an example. Customers can use any path they configured in their gateway setup.

Implementation options:

* **Option A (simple):** add the line directly inside the Data Activation / Campaign Analytics / CMP tag.
* **Option B (recommended):** add it in a global configuration tag that runs before all Commanders Act tags.

***

### Verification checklist

After applying the changes above, verify:

* The gateway health endpoint: `https://example.com/metrics/healthy` returns `ok`.
* In browser DevTools (Network tab), Commanders Act collection requests go to your first party domain and path (for example `https://example.com/metrics/...`).
* Events and data appear as expected in:
  * Server-side destination dashboards (example: Meta Events Manager for CAPI)
  * Data Activation flows
  * CMP statistics and proof of consent reporting (when applicable)
  * Campaign Analytics reporting
