Connecting Google sources
Two paths cover GA4 (traffic) and GTM (tag audit plus optional snippet publish). Google Ads is OAuth only.
- Service account — an agent with Admin on the client’s GA4/GTM can finish the grant with no human click. Otherwise the human pastes one email into GA4/GTM Admin. No consent screen, no FunnelKeeper dashboard.
- OAuth — one Google sign-in covers GA4, GTM, and Google Ads. The refresh
token is stored encrypted. The agent hands
auth_urlto the human; they only ever see Google’s consent page.
GTM asks for tagmanager.readonly plus tagmanager.edit.containers and
tagmanager.publish so Integrations can deploy the event tracking
snippet on an explicit confirm. OAuth grants minted before those write scopes
existed are still read-only — reconnect Google to upgrade. A service-account
grant with Publish on the container has the write scopes from the start.
Login with Google (the dashboard Sign in / Create account button) uses the same
OAuth client and a different callback: /auth/google/callback. That flow asks
only for openid email profile and never stores a refresh token. The client must
list both redirect URIs — login and this connect grant. Provision it with
./scripts/setup-google-oauth.sh.
From the dashboard
Integrations → Connect with Google → consent → pick your property/container → done.
Or Grant access instead on the GA4 / GTM card: copy the service-account email, add it as Viewer (GA4) or a container user (GTM), then Verify. No redirect, no consent screen.
From a terminal or agent — service account (preferred)
POST /connect/google/service-account/start {"product_slug":"demo-product","kinds":["ga4","gtm"]}
→ {"sa_email":"fk-…@….iam.gserviceaccount.com","instructions":{…}}
instructions.ga4.agent and instructions.gtm.agent are the exact Google API
calls. If you have Admin, make them; otherwise give sa_email to the human.
POST /connect/google/service-account/verify {"product_slug":"demo-product"}
→ {"status":"complete","options":{"ga4_properties":[…],"gtm_containers":[…]}}
POST /connect/google/service-account/verify {"product_slug":"demo-product","ga4_property_id":"4210…","gtm_container_path":"accounts/…/containers/…"}
→ {"ok":true,"activated":["ga4","gtm"]}
MCP: connect_service_account then verify_service_account. CLI:
fk connect google-sa --product demo-product.
From a terminal or agent — OAuth fallback
The consent screen can’t be skipped or scripted — Google requires a human. The
flow hands the URL out; give it to the human directly (chat or open). They
never enter the FunnelKeeper dashboard. Then poll:
POST /connect/google/start {"product_slug":"demo-product","kinds":["ga4","gtm"],"client":"cli"}
→ {"state":"…","auth_url":"https://accounts.google.com/o/oauth2/v2/auth?…","expires_at":"…"}
Give auth_url to the human. Then poll (bearer required; the state is bound to your
account and expires after 10 minutes):
GET /connect/google/status?state=…
→ {"status":"pending"} keep polling (2s, then 5s)
→ {"status":"complete","results":[…],"options":{"ga4_properties":[…],"gtm_containers":[…]}}
→ {"status":"error","error":"access_denied"}
When options holds more than one property or container, ask the human which, then:
POST /connect/google/select {"state":"…","ga4_property_id":"4210…","gtm_container_path":"accounts/…/containers/…"}
→ {"ok":true,"activated":["ga4","gtm"]}
Exactly one option? Pass it straight through — no need to ask.
What GTM is for
Nothing is ever written to your container. The Keeper reads the live version weekly and answers one question: is the GA4 tag this product expects present and unpaused? If spend is running while the answer is no, you get an alert card — because every funnel number under-counts until the tag is fixed.
Google Ads
Pick the Ads account the same way you pick a GA4 property. FunnelKeeper reads campaign spend for the last 7 days each night — cost, impressions and clicks, in the account’s own currency — and never writes to your account. If your accounts sit under a manager (MCC), the manager is resolved for you; there’s nothing extra to enter.
Spend sync switches on automatically once FunnelKeeper’s Google Ads developer token is
approved. Until then your account id is stored and the connection waits in pending, and
the CSV bridge covers spend imports. Nothing fails
in the meantime — Test on the Integrations page tells you which state you’re in.