Connect GA4

This step (titled Connect GA4 in the app) binds the GA4 Test Server property — the property that will receive events forwarded by your tagging server during Build verification. The Primary GA4 property was already bound during Audit and is shown read-only here.

The Test Server property is meant for validation, not as a permanent destination. During Connect and Publish you compare events landing in the Test Server property against what the Primary property captures, to confirm the server pipeline matches reality before you flip production traffic over. After Go Live the Test Server property can be retired or repurposed — it's not a long-lived second source of truth.

Happy path

  1. Step 1 — confirm GA4 discovery has populated. Discovery is shared with Audit; if it's empty, refresh it from Audit → Discover.
  2. Step 2 — pick a GA4 property in the picker and click Save Test Server property, OR click Create a new Test Server property and let GSS make one with settings copied from the Primary property.
  3. Step 3 — click Check; all 9 requirements turn green (or two land in awaiting attestation if the GA4 Admin API doesn't return retention or enhanced-measurement data).
  4. Continue to Cloud Run Services.
Glossary
GA4 property
A Google Analytics 4 property — the top-level container for data streams, settings, and reports. Identified by an internal ID like properties/123456789.
Primary property
Your existing production GA4 property — the one your real users send events to from the browser. Bound during Audit; not changeable here. (Also called the "browser" property in older docs.)
Test Server property
A separate GA4 property used during Build verification to receive events forwarded by the server container. Lets you validate the server pipeline without touching production data.
Data stream
The point of ingestion inside a property. Web data streams have a measurement ID (G-XXXXXXX) and are what the GTM/gtag config tags reference.
Measurement ID (MID)
The G-XXXXXXX string that identifies a specific GA4 web data stream.
Enhanced measurement
The set of automatic event toggles on a web stream — scrolls, outbound clicks, file downloads, etc. Must match between properties so the comparison isn't apples-to-oranges.

Not covered on this page: changing the Primary property (Audit Discovery), creating GA4 properties manually (analytics.google.com), or enabling the Analytics Admin API in your GCP project (GCP Project).

Before you start

  • Complete GCP Project — Analytics Admin API must be enabled.
  • Complete Connect GTM Containers — both GTM containers bound and the Step 3 requirements pass (this page's nav from the previous step requires it).
  • Primary GA4 property bound during Audit Discovery.
  • At least Editor access on the GA4 account that holds the Primary property — needed to create a Test Server property in the same account.

Decision record

Row Where it comes from What "done" looks like
Primary property Inherited from Audit Discovery. Read-only. Property name + measurement ID shown.
Test Server property Set in Step 2 — pick existing or create a new Test Server property. Property name + measurement ID shown, distinct from Primary.
Requirements Rollup of Step 3. 9 of 9 verified after Step 3's Check button has run (two checks may show awaiting attestation if GA4 doesn't return retention or enhanced-measurement data).

1 Discover GA4 properties available to your account

GA4 discovery is shared with the Audit stage — this page doesn't run discovery itself. When discovery has been populated, the status line shows how many GA4 properties were found and how many are candidates for the Test Server role (the Primary property is filtered out). When discovery is empty, the step shows "Pending" with a link to Audit → Discover; run or refresh discovery there and return.

If a property you expect doesn't appear after discovery has run, your Google account doesn't have access to it — fix in GA4 Admin → Account/Property Access Management, then re-run discovery from Audit.

2 Which GA4 property should receive server-side events?

The Primary property is shown above the picker as inherited context (you can only change it from Audit/Discover). The picker below lists every GA4 property your Google account can see, except the Primary property. You can pick an existing one to reuse, or create a new Test Server property whose settings are copied from the Primary property to keep the comparison meaningful.

This step stays locked with a "Locked" banner until both prerequisites are met: a Primary GA4 property is bound in Audit, and discovery has populated. The Save button stays disabled until you pick a row different from what's already saved; switching to a different Test Server property clears Step 3's requirement results and you'll need to re-run Check.

Option A — Pick an existing GA4 property

Click a row in the picker and then Save Test Server property. The picker excludes the Primary property automatically, so you can't accidentally bind the same property to both slots. GSS records this binding with created_by: user metadata.

Option B — Create a new Test Server property

Click the Create a new Test Server property link below the picker (or Create Test Server property if no candidate properties were found). The modal shows three fields:

  • Name — pre-filled as Test Server - <primary property name>, with a numeric suffix if that name is taken in the account. Editable.
  • Stream URL (read-only) — copied from the Primary property's web stream.
  • Account (read-only) — same Analytics account as the Primary property.

Under the hood, GSS also copies timezone, currency, data retention, and enhanced-measurement settings from the Primary property at creation time so the alignment checks in Step 3 pass without manual matching. These copies are best effort — confirm them in GA4 Admin if any of the alignment checks fail later.

Click Create. GSS creates the property + web stream, binds both to the project, closes the modal, and re-renders Step 2 with the new Test Server property selected. Provenance is recorded as created_by: gss on both bindings — the classifier reads this when deciding whether changes need elevated tier handling later. If the API call fails, the error is shown inline next to the Create button — retry or pick an existing property instead.

If property creation succeeds but stream creation fails, the property is bound and a warning is logged but the stream binding stays empty. The Step 3 check will then fail 2.4; create the stream manually in GA4 and re-run Check.

Option C — Create manually in GA4, then bind

If you'd rather create the property in GA4 directly:

  1. In GA4, open AdminCreate Property.
  2. Set name, timezone, and currency to match your Primary property.
  3. Create a web data stream with the same site URL.
  4. Match data retention and enhanced-measurement settings to the Primary property's stream.
  5. Return to the app, re-run discovery in Audit Discovery so the new property appears, then pick it in this page's Step 2 picker and click Save Test Server property.

Rebinding the Test Server property later

Changing the Test Server property clears ga4_stream_server (it has to be re-derived from the new property) and resets this stage's check rows. Your Primary bindings, GTM bindings, GCP project, and Cloud Run services are not affected. The big destructive cascade lives upstream in Audit Discovery — changing the Primary property or web container there is what wipes downstream bindings including this Test Server property.

3 Are both properties valid and configured to match?

Click Check to run 9 requirements: 5 confirm both properties exist and have web streams with distinct measurement IDs; 4 confirm the alignment of timezone, currency, data retention, and enhanced measurement between the two properties. GSS makes up to 6 GA4 Admin API calls per run.

Step 3 stays locked until both the Audit Primary property and Step 2's Test Server property are bound. Two checks (data retention + enhanced measurement) can return awaiting attestation instead of pass/fail when the GA4 Admin API doesn't return the data — usually due to permission scope or API limits. That status doesn't block progression; treat it as "GSS couldn't verify automatically, please confirm manually."

After a successful Check the page nav at the bottom unlocks the link to Cloud Run Services.

Property & stream existence (2.1–2.5)

2.1Primary property exists

The Primary GA4 property bound during Audit must resolve via the Analytics Admin API.

Where to check: GA4 → Admin → Property Settings.

Fix: If it was deleted or you lost access, go back to Audit Discovery and pick a different Primary property.

Deep dive

What it checks. analyticsadmin.properties.get(primary_property_id) returns a non-null property object.

How GSS performs the check. One GA4Provider.get_property call. The response is reused by 2.6 (timezone) and 2.7 (currency).

What counts as pass. API returns the property.

Why it might fail. Property deleted in GA4. Account access revoked. Analytics Admin API not enabled in GCP (this would be caught by 2.5 of GCP Project earlier — but credential expiry can cause it here too).

How to recover. If the property is gone, re-run Audit Discovery to pick a different Primary property — that's the only place to change this binding. If access was revoked, ask a property admin for at least Viewer.

Note: If 2.1 or 2.2 fails, GSS short-circuits — checks 2.3 onward don't run for this Check cycle (no point comparing settings when one of the properties is unreachable).

2.2Test Server property exists

The Test Server GA4 property bound in Step 2 must resolve via the Analytics Admin API.

Where to check: GA4 → Admin → Property Settings.

Fix: If it was deleted, pick a different Test Server property in Step 2, or create a new one.

Deep dive

What it checks. Same logic as 2.1 but for the Test Server property ID.

How GSS performs the check. Second get_property call. Response reused by 2.6 (timezone), 2.7 (currency).

What counts as pass. API returns the property.

Why it might fail. Test Server property deleted (rare unless you intentionally cleaned up after Build). Account access lost. Tier 2 cascade cleared the binding (then this check shows needs_config, not fail).

How to recover. Re-run Step 2 — pick or create. If the cascade cleared it, the upstream Audit Discovery selection probably needs reconfirming first.

2.3Primary property has one web stream

The Primary property must have exactly one web stream with a G-XXXXXXX measurement ID. This MID is what the GTM Primary Google Tag will reference.

Where to check: GA4 → Admin → Data Streams. Should show one Web stream.

Fix: If zero web streams: create one via Admin → Data Streams → Add Stream → Web. If multiple: GSS can't pick automatically — you'd need to delete or merge until exactly one remains. Re-run Check after.

Deep dive

What it checks. list_data_streams(primary_id) returns exactly one stream with a non-empty measurement_id.

How GSS performs the check. One API call. The MID and stream name are extracted for use by 2.5 (distinctness) and 2.9 (enhanced measurement).

What counts as pass. Exactly 1 web stream. Zero or 2+ both fail.

Why it might fail. Property has no web data stream (rare — GA4 typically prompts to create one when you create a property). Multiple web streams (legacy multi-domain setup; needs consolidation).

How to recover. In GA4 Admin, ensure exactly one Web stream exists for the Primary property. Delete or repoint extras.

2.4Test Server property has one web stream

The Test Server property must have exactly one web stream with a measurement ID. Use a Web stream type even though events arrive via the server container — the destination type is independent of how events are sent.

Where to check: GA4 → Admin → Data Streams (in the Test Server property).

Fix: Create one via Add Stream → Web. Or use Step 2's Create option, which makes the stream automatically.

Deep dive

What it checks. Same as 2.3 but for the Test Server property.

How GSS performs the check. Second list_data_streams call.

What counts as pass. Exactly 1 web stream.

Why it might fail. Test Server property has no stream — happens if you bound an existing property without one, or if Step 2's Create flow had its property creation succeed but stream creation fail.

How to recover. Add a Web data stream in the Test Server property. Check doesn't write the stream binding back even after this passes — that only happens via Step 2's Create flow. So if you created the stream manually in GA4 after a partial Create, the ga4_stream_server binding may stay empty until you rebind via Step 2.

2.5Measurement IDs are different

The two properties must have different measurement IDs. If they share an MID, server-forwarded events would land in the same property as Primary events — defeating the whole point of the validation comparison.

Where to check: Compare the MIDs shown in the decision record at the top of the page. They should differ.

Fix: If MIDs are the same, you've bound the same property twice somehow. Pick a different Test Server property in Step 2.

Deep dive

What it checks. Both MIDs are non-empty AND not equal.

How GSS performs the check. No additional API call — uses MIDs collected during 2.3 and 2.4.

What counts as pass. Both present, both different.

Why it might fail. Same MID on both properties (impossible normally, since the picker excludes the Primary property — would only happen via legacy direct-bind paths). Either MID empty (caught by 2.3 or 2.4 first).

How to recover. Pick a different Test Server property in Step 2.

Settings alignment (2.6–2.9)

These checks compare settings between the two properties. Mismatches don't break the pipeline but make the data comparison apples-to-oranges. Step 2's Create flow copies all of these from the Primary property at creation time, so existing-property bindings are the usual culprits when these fail.

2.6Timezone matches between properties

Both properties must use the same reporting timezone. Mismatched timezones cause session boundaries and event timestamps to disagree.

Where to check: GA4 → Admin → Property Settings → Reporting time zone.

Fix: Edit the Test Server property's timezone to match the Primary property.

Deep dive

What it checks. The two properties' time_zone fields are equal.

How GSS performs the check. No new API call — uses the property objects from 2.1 and 2.2.

What counts as pass. Strings equal. Timezone names like America/New_York are case-sensitive.

Why it might fail. Test Server property created with default timezone (usually America/Los_Angeles) while Primary is something else.

How to recover. Edit the Test Server property's timezone in GA4 Admin to match. Re-run Check.

2.7Currency matches between properties

Both properties must use the same reporting currency. Mismatched currencies make revenue comparison meaningless.

Where to check: GA4 → Admin → Property Settings → Currency.

Fix: Edit the Test Server property's currency to match the Primary property.

Deep dive

What it checks. The two properties' currency_code fields are equal.

How GSS performs the check. Reuses property objects from 2.1/2.2.

What counts as pass. Strings equal (e.g., both USD).

Why it might fail. Test Server property created with default currency.

How to recover. Edit currency in GA4 Admin.

2.8Data retention matches between properties

Both properties must use the same event-data retention period. Mismatched retention causes gaps when comparing historical data.

Where to check: GA4 → Admin → Data Settings → Data Retention.

Fix: Edit the Test Server property's retention to match. Default is 2 months; production properties commonly use 14 months.

Deep dive

What it checks. Both properties' event_data_retention values are equal.

How GSS performs the check. Two get_data_retention API calls.

What counts as pass. Values equal (e.g., both FOURTEEN_MONTHS).

Why it might fail or land in awaiting attestation. Different values = fail. If either API call returns null (permission scope, GA4 API limitation), the check returns awaiting_attestation with "Retention settings not available via API" — meaning GSS can't tell, please confirm in the GA4 UI yourself.

How to recover. Edit retention in GA4 Admin if it's a fail. If awaiting attestation, manually verify the Test Server property's retention matches the Primary property's — there's no app action to dismiss the attestation status; it's informational.

2.9Enhanced measurement matches between streams

Enhanced measurement settings on both web streams must be identical. These control automatic events for scrolls, outbound clicks, file downloads, etc. — different settings would mean one property auto-collects events the other doesn't.

Where to check: GA4 → Admin → Data Streams → the web stream → Enhanced measurement.

Fix: Toggle settings on the Test Server property's stream to match the Primary stream's. If you plan to add manual scroll/click tags via GTM, disable the overlapping enhanced-measurement events on both streams to avoid double-counting, or use distinct event names.

Deep dive

What it checks. The full enhanced-measurement settings dicts for both web streams are deep-equal.

How GSS performs the check. Two get_enhanced_measurement calls (one per stream). Stream names come from 2.3/2.4 — if either stream is missing, this check goes to awaiting_attestation rather than fail.

What counts as pass. Full dict equality. Any single toggle differing fails.

Why it might fail or land in awaiting attestation. Different settings = fail. Either stream missing OR either API call returns null = awaiting_attestation.

How to recover. Match settings in GA4 Admin. Step 2's Create flow does this automatically; existing-property bindings often need manual matching here.

Common errors & failure modes

Symptom Likely cause Where to fix
Stream binding
Connect stage Primary MID role fails — but 2.3 here passed The Primary stream binding (ga4_stream_browser) is auto-derived by Audit Discovery's save flow, not by this page. If discovery hasn't run since the property was bound, the stream binding may be stale. Audit Discovery → re-save the Primary property to retrigger autobind.
Connect stage Server MID role fails after picking an existing Test Server property The server stream binding (ga4_stream_server) is only written by Step 2's Create flow, never by Save or Check. Existing-property bindings leave the stream binding empty. Step 2 — re-bind via the Create link, OR check the Connect stage's server validation: it pulls the MID directly from the property if no stream binding exists, so this only matters when the property has multiple streams.
Cascade after upstream changes
All 9 checks show needs_config No Test Server property bound — either Step 2 hasn't run, OR the Tier 2 cascade in Audit Discovery cleared ga4_property_server. If Audit was recently changed: redo Audit Discovery to confirm the Primary property, then return here and rebind the Test Server property in Step 2.
Validation
Checks 2.6–2.9 fail after picking an existing property Existing properties don't have settings copied from the Primary. The Create flow copies settings; bind-existing does not. Match the failing settings in GA4 Admin. Or rebind via Step 2's Create flow to start fresh with copied settings.
2.8 or 2.9 shows awaiting_attestation instead of pass/fail GA4 Admin API returned no data for retention or enhanced measurement (permission/scope or API limitation). Manually verify in GA4 Admin that the settings match. There's no app action to clear the attestation; it's informational and doesn't block progression.
2.1 fails right after Audit Discovery completed Primary property deleted in GA4 since binding, or your account lost access. Audit Discovery — pick a different Primary property. There is no way to repoint the Primary binding from this page.
"Validation failed — please try again." shows next to the Check button Network or credential failure during the requirements check. Non-2xx so HTMX skips the swap and existing rows are preserved. Retry once. If it persists, reconnect Google access in the app, then re-run Check.

Next step

Once both properties are validated, the next step is Cloud Run Services — deploying the preview and tagging Cloud Run services that will host your GTM server container.