# GA4 Integration

Connect a GA4 property so you can use existing GA4 events as goals. OAuth flow, property selection, sync schedule, and limits.

Connect a Google Analytics 4 web stream so your existing GA4 events can drive privacy-safe A/B test goals — no conversion reinstrumentation required.

If your team already tracks purchases, leads, or signups in GA4, you can plug Otter into that data instead of reinstrumenting the conversion. Once a project is connected to a GA4 web stream, you can add a **GA4 Event** goal to any test and pick from the events GA4 is already collecting.

The connection is project-scoped — one GA4 web stream per Otter project. If you run tests across multiple websites or stores, create one project per site and connect each one to the matching GA4 property.

## Connecting GA4

1. **Open the project's integrations tab** — In Otter, open the project you want to connect and head to **Settings → Integrations → Google Analytics 4**. Click **Connect GA4**.
2. **Authorize with Google** — You'll be sent to Google's OAuth consent screen. Otter requests read-only access to your GA4 properties (`analytics.readonly`) and nothing else — no ads, no write access, no other Google services.
3. **Pick a web stream** — Otter fetches the accessible GA4 properties and their web data streams. Pick the stream whose domain and Measurement ID match the website running the experiment. The server validates the choice against Google rather than trusting browser labels.
4. **Register the experiment dimensions in GA4** — This step happens in GA4, not Otter, and results cannot sync without it. In GA4 go to **Admin → Data display → Custom definitions → Create custom dimension** and register two event-scoped custom dimensions: one with event parameter `experiment_id`, one with event parameter `variant_id` (any display names). Otter's SDK sends both parameters on every `experiment_impression`, but GA4 only lets reports filter on parameters that are registered in your property — and because Otter's access is read-only, we can't create them for you. GA4 applies custom dimensions to data collected from registration onward and can take up to 48 hours to surface them, so register these before launching your first GA4-backed test.
5. **Add a GA4 goal to a test** — On the Goals step of the test wizard, choose **GA4 Event** and enter the event name exactly as it appears in GA4 (e.g. `purchase`, `generate_lead`, `sign_up`). Names are case-sensitive.

## How sync works

A background job runs every five minutes for projects with a running, paused, or recently completed GA4-backed test. For each goal and variant, Otter asks GA4 for a closed funnel: an `experiment_impression` with the matching experiment and variant parameters, followed by the selected conversion event for the same GA4 user.

If the connection is ready but no test with a GA4 goal is running, the project shows **Waiting for a GA4 test** and nothing syncs. Launch a test that uses a GA4 event goal and the first sync runs within about five minutes.

Matching happens inside the connected GA4 property. Otter never exports visitor IDs or customer identity data, and no high-cardinality visitor-level dimension is ever needed — only the two low-cardinality event-scoped custom dimensions (`experiment_id` and `variant_id`) described in the setup steps. If they're missing, every refresh fails with a setup message naming them until they're registered. Every refresh rechecks the complete experiment window, so delayed GA4 data is reconciled instead of being lost behind a short watermark.

Completed tests continue refreshing for seven days and keep their end time fixed. Otter stores hourly aggregate snapshots plus refresh health, sampling warnings, report count, total converters, and the last error. A failed refresh never partially publishes new totals; the previous successful totals remain visible.

GA4 goals return all-user aggregate totals. Otter's local audience segments and event-time trend charts are unavailable for those goals because GA4 does not return the underlying visitor events. If Google samples a funnel report, the UI labels the totals as estimates.

## Connection states

| State | Description |
| --- | --- |
| `pending_property_selection` | You've authorized with Google but haven't picked a web stream yet. The connection exists; refreshes haven't started. Pick a stream to move to active. |
| `active` | A web stream is selected and funnel refreshes run every 5 minutes while a GA4-backed test is active or finalizing. This is the healthy state. |
| `expired` | Google revoked the OAuth token — usually because the user disconnected our app from their Google account permissions, or because the token couldn't be refreshed. Reconnect to resume syncing. |
| `revoked` | You disconnected via the Otter UI. No syncs run. Reconnect at any time. |

> **Mid-test rules:**
>
> - **Stream changes are blocked while data is reconciling.** Otter refuses to switch streams while a GA4 test is running, paused, or within its seven-day finalization period because switching would mix incompatible attribution data.
> - **Disconnect is blocked on the same schedule.** Complete the test and allow its seven-day finalization period to finish first. Existing result snapshots remain available after a later disconnect.
> - **Pick the right stream the first time.** Verify the domain and Measurement ID on the selection screen so a staging stream is not used for production traffic. If a test is paused or resumed, Otter excludes the paused interval while GA4 still deduplicates users across the active run windows.

## Frequently asked questions

### Why connect GA4 at all if Otter already tracks conversions?

Use it when your conversions already live in GA4 and you don't want to duplicate the tracking on the Otter side. GA4 goals let you measure the same purchase, lead, or signup event that the rest of your business reports on, so you don't have to reconcile two numbers. If you're starting fresh, the built-in pageview / click / event / revenue goal types are simpler.

### What permissions does Otter need on my Google account?

Read access to your GA4 properties (analytics.readonly) and access to the property list. We don't request write permissions, ad permissions, or access to anything beyond GA4. You can revoke at any time from your Google account permissions page or by clicking Disconnect in Otter.

### How do I select which GA4 property to sync?

After the OAuth handshake, Otter fetches the list of GA4 properties your Google account can read. You pick the one that matches the website where you're running tests. One Otter project maps to one GA4 property — if you run multiple sites, create one project per site and connect each one separately.

### How often does Otter pull events from GA4?

Every 5 minutes via a scheduled job, but only for projects that actually have a running or paused test using a GA4 goal. Idle projects don't sync, so the GA4 API quota only burns when there's real demand for the data.

### How far back does Otter look for events?

First sync after you select a property pulls the last 7 days. Each subsequent sync overlaps with the previous window by one hour to catch late-arriving events. GA4 sometimes delays events by 24–48 hours, so don't be surprised if conversion counts continue to inch up after a test has technically completed.

### Can I change the GA4 property mid-test?

No — not while you have running or paused tests using GA4 goals on that project. Otter blocks property changes in either state so you can't accidentally invalidate the data mid-experiment. Complete or archive the affected tests first, then change the property — pausing alone doesn't lift the block, because paused tests are still in-flight and can be resumed.

### Can I disconnect GA4 while tests are running?

Not if any of those tests have a GA4 goal. The disconnect button is disabled until the affected tests are completed or archived. Pausing doesn't lift the block, because paused tests can be resumed and a missing GA4 connection would break them on resume. This prevents losing conversion data partway through a test.

### What status does the dashboard show after I connect?

Four possible states: pending_property_selection (OAuth done but no property picked yet), active (property selected, syncs running), expired (Google revoked the token), or revoked (you disconnected). The project settings page surfaces the current state plus the time and event count of the last successful sync.

### Why is my GA4 goal not firing?

Check three things in order: (1) the connection state — it must be active and have a property selected; (2) the event name in your GA4 goal config exactly matches the event in GA4, case-sensitive; (3) the event has fired in GA4 recently — verify in GA4's Realtime or DebugView. If all three check out, give it another 5–10 minutes for the sync to pull the events through.

### What if my GA4 token expires?

Otter auto-refreshes tokens 5 minutes before expiry using the refresh token Google gave us at OAuth time. If the refresh fails — usually because you revoked our access from Google — the connection moves to expired status and syncs stop. Reconnect from the project settings page to resume.

---

Canonical page: https://www.otterab.com/docs/building-tests/ga4-integration
