Skip to main content

GSC & GA4 Setup

Goal

Own site analytics data permanently, across all sites, via the official Google APIs. The approach is phased:

  • Phase 1 — UI first: learn what each metric means using the Google web UIs (zero code, zero API). ✅ done
  • Phase 2 — Credential setup: create the service account + grant access (one time). ✅ done — GSC (Full) and GA4 (Viewer) both granted
  • Phase 3 — CSV pipeline: a reusable, parameterized pull script writing CSV on a schedule. ⏳ planned

Current Implementation (id86.net)

GA4 Property

FieldValue
Accountid86.net (accounts/403167335)
Propertyid86.net (properties/548063257, PROPERTY_TYPE_ORDINARY)
Property display nameid86.net
Stream nameid86.net
Stream URLhttps://id86.net
Stream ID15362990766
Measurement IDG-WTHMFL125Z
Service account accessViewer — granted (API reads work)
Data collectionPending — new property, up to 48h to activate; Realtime shows hits within minutes of real visits

GSC Property

FieldValue
Site URLsc-domain:id86.net
PermissionsiteFullUser
Verification methodDNS TXT (google-site-verification=... — value in ~/.ssh/google/.env, GOOGLE_SITE_VERIFICATION_TOKEN)
Service account accessAdded (Full)
Sitemaphttps://id86.net/sitemaps.xml — submitted to GSC, but current external health check returns HTML instead of valid XML; repair before launch

Service Account Credential

FieldValue
Cloud projectvivid-fragment-504210-v5 (display google-analytics-project)
Service account emailid86-api@vivid-fragment-504210-v5.iam.gserviceaccount.com
Key file (local)~/.ssh/google/id86-api.json (mode 600, outside Git)
Enabled APIsSearch Console API, Analytics Admin API, Analytics Data API

API Verification (end-to-end, verified 2026-08-01)

CallResult
GSC sites.listsc-domain:id86.net (siteFullUser)
GSC searchAnalytics/querySuccess — 0 rows (no crawls yet; dev mode behind Cloudflare Access)
GA4 Admin accounts.listaccounts/403167335 id86.net
GA4 Admin accountSummariesproperties/548063257 id86.net
GA4 Data runReportSuccess — 0 rows (no traffic yet; expected for new property)

Both APIs return successful responses. Empty row sets are expected while the site is under development and protected by Cloudflare Zero Trust — they will populate once the site is public and Google/bots can reach it.

GA4 Tracking Implementation

gtag placement

The gtag is added in the child theme functions.php via a wp_head action (priority 1), so it renders as the first script in <head>:

// Google Analytics 4 (gtag.js) — Measurement ID G-WTHMFL125Z
add_action("wp_head", function () {
if (is_admin()) {
return;
}
?>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-WTHMFL125Z"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());

gtag('config', 'G-WTHMFL125Z');
</script>
<?php
}, 1);

Deployment steps used

1. Edit functions.php (add wp_head gtag block)
2. php -l functions.php # No syntax errors
3. git diff --check # clean
4. ./deploy.sh # rsync theme → GC-US-M10
5. wp flush-opcache flush # OPcache
6. curl -ksS --resolve id86.net:443:127.0.0.1 "https://id86.net/?LSCWP_CTRL=purge_all" # LiteSpeed cache (origin-direct)
7. Cloudflare purge_everything # API, zone from ~/.ssh/cloudflare/.env (CF_ZONE_ID)
8. Origin verify: grep 'G-WTHMFL125Z' on https://id86.net/?<ts>

Verification checklist

  • gtag renders as first <script> in <head> (line 8 on origin)
  • Measurement ID matches the stream (G-WTHMFL125Z)
  • No Content-Security-Policy blocking the tag
  • OPcache, LiteSpeed, and Cloudflare caches purged after deploy
  • GSC API access verified end-to-end (siteFullUser, searchAnalytics query OK)
  • GA4 API access verified end-to-end (account + property visible, runReport OK)
  • Sitemap https://id86.net/sitemaps.xml submitted to GSC
  • GA4 Realtime shows 1 active user after a real browser visit
  • GA4 property status flips from "pending" to active (up to 48h)
  • GSC Search Analytics shows data (after launch — needs crawls)

Note on gtag delivery

The tag loads client-side in the visitor's browser from googletagmanager.com, not from the origin server. The origin server timing out on googletagmanager.com is irrelevant to tracking. Because the site sits behind Cloudflare Access, only authenticated human visitors trigger hits — expected during development (site not yet public). GA4 data collection is pending until the first real visits, and will only reflect authenticated visitors until the site launches.

SEOPress vs Independent Google Integration (decision)

SEOPress Pro's in-WP Google integrations are intentionally NOT used for GSC/GA4 data. The site relies on the independent service-account API setup documented here instead.

CapabilityMethodWhy
GA4 trackinggtag injected by child theme functions.php (wp_head)Single source, no double-counting. SEOPress GA4 field stays empty on purpose.
GA4 reportingGoogle Analytics Data API (service account)Reusable across sites, permanent CSV archive, no plugin dependency.
GSC reportingGoogle Search Console API (service account)Same credential works for every site; no per-site SEOPress OAuth.
Instant indexingSEOPress Pro (IndexNow)This one is kept — Bing API key + automate_submission=1 confirmed active.
SitemapSEOPressKept — sitemap generation is SEOPress's job; submitted to GSC once.

Rules to preserve:

  • Do not enable SEOPress's "Google Analytics" (GA4) field or its "Google Search Console" tab — leave both unconnected. Connecting them would duplicate tracking and create a second, less reusable data path.
  • Keep SEOPress sitemap + instant indexing (IndexNow) — they are the indexing/discovery layer, unrelated to data pulling.
  • All GSC/GA4 data operations flow through the service account key ~/.ssh/google/id86-api.json.

Remaining Setup Items

  1. Phase 3 script (planned): reusable GSC + GA4 pull script writing CSV. Deliberately parked until the site is public and traffic flows, so the script can be built around real, understood metrics.

API Credential Reference

Credential locations on .ssh/

ServicePathModePurpose
Google (service account)~/.ssh/google/id86-api.json600GSC + GA4 API key (id86-api@vivid-fragment-504210-v5.iam.gserviceaccount.com)
Google (verification + env)~/.ssh/google/.env600GOOGLE_SITE_VERIFICATION_TOKEN, GCP_SA_KEY_PATH
Cloudflare (full control)~/.ssh/cloudflare/.env600CF_FULL_CONTROL_TOKEN, CF_ZONE_ID, etc.
Reoon (email verification)~/.ssh/reoon/.env600REOON_API_KEY (shared with subscription + contact form)

All live outside any Git repo, owned by rezriz, restricted to owner read/write. Usage:

  • Google: export GOOGLE_APPLICATION_CREDENTIALS=~/.ssh/google/id86-api.json
  • Cloudflare: source ~/.ssh/cloudflare/.env

Backup caution: id86-api.json is the single credential for GSC/GA4 API access — losing it means recreating the service account and re-granting access on every property. Copy it to the encrypted backup (rclone to OneDrive; see Server Infrastructure — Backups).

Secret storage (matches existing discipline)

  • GitHub Actions secret: GCP_SA_KEY
  • Production host env file: ~/.ssh/google/.env (same pattern as ~/.ssh/cloudflare/.env)
  • Never commit the key to Git.

Why the credential is reusable

One service-account JSON key + one parameterized script works for every site you own. Per-site setup is only: add the service account as Viewer in that site's GA4 property + GSC property, and drop in that site's Measurement ID. The credential and the code are created once.

Phase 3 — CSV Pipeline (reusable script)

Not yet built — deliberately parked until the UI has taught us which metrics matter.

Planned design

Location: wp-ops/05-observability/analytics/ (alongside traffic-detect.sh)
Scheduler: cron / GitHub Actions (same pattern as sync-docs.sh --cron)
Output: CSV per site to a reports/ directory
Inputs: --site-url https://id86.net/ --ga4-property 123456789 --output-dir ...
Libraries: google-analytics-data, google-search-console (Python)

Data planned

SourceMetrics
GSC APIqueries, impressions, clicks, positions (aggregate only)
GA4 Data APIsessions, users, engagement, top pages (24–48h latency)

Cons / limitations (recorded here to avoid surprises)

  • No real-time — GSC lags ~24h, GA4 Data API ~24–48h.
  • No in-WP admin widgets — Google UIs + our own scripts, not Site Kit.
  • GA4 free collection ceiling — ~500k events/day; at high traffic, sampling or GA4 360 may be required.
  • Sampling — GA4 report queries can sample at high volume; GSC API is aggregate only (no per-visitor).
  • Retention — GSC API ~16 months; GA4 free retention 2 or 14 months.
  • Key management — service-account JSON must be stored securely.
  • Own maintenance — we own quota-aware batching and error handling.

Quota reality (100k+ visitors/day is fine)

API quotas count report requests, not site traffic. A daily pull makes a handful of calls regardless of visitors/day:

  • Search Console API: thousands of queries/day/property.
  • GA4 Data API: ~150k tokens/day/project.

A daily/weekly pull uses a tiny fraction. Traffic does not get rate-limited by the API.

ConcernChoiceWhy
Indexing / discoverySEOPress (sitemap + IndexNow)Already active, zero setup, automatic on publish
UI learning phaseGA4 + GSC web UIsZero code; learn metrics before automating
Permanent CSV dataGSC/GA4 service-account APIReusable across sites, own history forever
Server impactAPI runs offsiteNo PHP/DB/plugin overhead on WordPress