SEO Specialist
Lead Tracking (Monthly)
SOP: Monthly Lead Tracking Audit
Section titled “SOP: Monthly Lead Tracking Audit”Last Updated: 2026-04-21 Version: 1.0 Tier: Monthly Operational Audit
Purpose
Section titled “Purpose”Tracking breaks silently. Ad-blockers get updated, MP secrets get rotated incorrectly, service accounts lose permissions, CF Pages env vars get cleared during a redeploy. The client never complains because they still see calls coming in — they just don’t know that half the calls are uncredited.
This SOP catches broken tracking before the monthly report goes out with wrong numbers.
When to Run: First week of every month, before writing client deliverables Owner: SEO Specialist Timeline: 20 minutes per client site
What You’re Checking
Section titled “What You’re Checking”For each client and for Tekton’s own sites (BCT, Tekton Growth):
- Is gtag still firing on production?
- Are
generate_leadevents still firing on form submits, both client and server-side? - Is GSC still connected to GA4?
- Does the GA4 lead count match the real lead count in GHL (or the client’s CRM) within ±10%?
- Are any sources going to 0 leads when they should have leads?
Any mismatch = investigate before the month’s report goes out.
Section 1: Live Tag Check (per client, 2 min)
Section titled “Section 1: Live Tag Check (per client, 2 min)”- Open the client’s live site in an incognito window
- DevTools → Network → filter
collect - Reload the page
- Confirm at least one POST to
https://www.google-analytics.com/g/collect?...&tid=G-XXXXXXXXreturns 204
If missing:
- Disable ad-blocker first, re-test
- If still missing: the GA4 tag is broken. Run through
/gsc-verifyPhase 4 to diagnose. Most common cause: someone edited a theme/layout file and removed the gtag block.
Log: Pass/Fail into the monthly audit sheet.
Section 2: generate_lead Firing Check (per client, 3 min)
Section titled “Section 2: generate_lead Firing Check (per client, 3 min)”- Pick one form on the site (newsletter, contact, lead magnet)
- Submit a test with a throwaway email (
tracking-test+<client>-<YYYYMM>@tektongrowth.com) - Go to GA4 → Reports → Realtime
- Confirm a
generate_leadevent appears within 60 seconds - Check the event card — should show both
source_channel=clientandsource_channel=serverif the server-side MP path is wired - If only client fires: server-side is broken (MP secret invalid, env vars missing, or API endpoint isn’t calling
sendGa4Event) - If only server fires: client-side is broken (gtag blocked or
bctTrackLeadnot called from handler) - If neither fires: full tracking failure — escalate immediately
Log: Client-side: Pass/Fail, Server-side: Pass/Fail/N/A into the monthly audit sheet.
Note: Remove the test lead from GHL and Resend afterward so it doesn’t skew client’s list.
Section 3: GSC Data Flowing into GA4 (per client, 2 min)
Section titled “Section 3: GSC Data Flowing into GA4 (per client, 2 min)”- GA4 → Reports → Acquisition → Search Console → Queries
- Confirm there is data in the last 7 days
- If “no data” when you expect organic: the GSC↔GA4 product link was broken (happens if the GSC property was re-verified or the GA4 property was moved)
To fix a broken link: GA4 Admin → Product links → Search Console links → unlink the old, re-add. Data re-populates within 24h.
Log: Pass/Fail per client.
Section 4: Lead Count Reconciliation (per client, 8 min)
Section titled “Section 4: Lead Count Reconciliation (per client, 8 min)”This is the most important check. GA4’s conversion count should be within ±10% of the real lead count.
Pull the numbers
Section titled “Pull the numbers”From GA4 (for the previous month):
- Reports → Acquisition → Traffic acquisition
- Secondary dimension:
source_channel(custom dimension we set) - Metric: Key events →
generate_lead - Split out totals by
source_channel:- Client-side total:
generate_leadwheresource_channel = client - Server-side total:
generate_leadwheresource_channel = server - Server-side is the ground-truth lead count — it fires once per successful form submission regardless of browser
- Client-side total:
From GHL (for the same period):
- Contacts → filter by tag containing
bct-newsletter,resource-*, or whatever tag is set for this client’s form sources - OR: count the relevant tagged contacts created during the month
- This is the client’s “real” lead count (assumes GHL is the system of record)
Compare
Section titled “Compare”| Source | Expected match | Acceptable variance |
|---|---|---|
source_channel=server total | = GHL tagged contact count | ±5% |
source_channel=client total | < server total (ad-blockers skip it) | ~85% of server count |
If numbers are wildly off
Section titled “If numbers are wildly off”- Server count > GHL count by >10%: double-counting (form handler firing MP on non-success path, or duplicate events being sent). Check the
subscribe.jsor equivalent for idempotency. - Server count < GHL count by >10%: MP silently failing. Check CF Pages Function logs for
[ga4-mp]errors. Most common: env vars got cleared on redeploy. - Client count > server count: suspicious — should never happen. Probably
source_channelparam missing on one side. Inspect the tracking JS.
Log: GA4 server count, GHL count, variance %, notes into the monthly audit sheet.
Section 5: Source Distribution Sanity (per client, 5 min)
Section titled “Section 5: Source Distribution Sanity (per client, 5 min)”Look at where leads are coming FROM in GA4:
- Reports → Acquisition → Traffic acquisition → filter to
generate_leadevents - Review the session source/medium breakdown
Red flags to investigate:
- 100% of leads from
(direct) / (none)— UTMs are probably not being captured. Check thatbctGetAttributionstill reads localStorage correctly. - 0 leads from
google / organicwhile GSC shows 100+ clicks — GSC↔GA4 link is broken OR theattributionobject isn’t being passed through the subscribe call. - All leads from the same UTM campaign month-over-month with no variance — probably a stale UTM in the capture cookie (90-day TTL might be sticking too long on returning visitors). Not broken, but worth noting in the monthly report.
Log: Notable source distribution changes into the monthly audit sheet.
Section 6: Tekton’s Own Properties
Section titled “Section 6: Tekton’s Own Properties”Run the same 5 checks on:
- Blue Collar Techy (
https://bluecollartechy.com/) - Tekton Growth (
https://tektongrowth.com)
Same SOP, just the sites are ours.
Reporting
Section titled “Reporting”Put results into /Users/nick/Projects/seo-ops-skills/results/<YYYY-MM>/monthly-tracking-audit.md with this shape:
# Monthly Tracking Audit — YYYY-MM
## Per-Client Results| Client | Tag Live | generate_lead | GSC→GA4 | Server MP | Variance | Notes ||---|---|---|---|---|---|---|| Client A | ✓ | ✓/✓ | ✓ | ✓ | +3% | || Client B | ✓ | ✓/✗ | ✓ | ✗ | -18% | MP secret rotated, env var stale — fixed |...
## Action Items (for next month or urgent)- [ ] Client B: redeploy to pick up new MP secret- [ ] Client C: investigate why GSC↔GA4 link droppedSurface any RED items in the Monday team standup and in the client’s monthly report if it affects their numbers.
Troubleshooting Playbook
Section titled “Troubleshooting Playbook””GA4 shows zero generate_lead events this month but GHL has 30 contacts”
Section titled “”GA4 shows zero generate_lead events this month but GHL has 30 contacts””- Client-side broken: check
window.bctTrackLeadon the live site (DevTools console) - Server-side broken: CF Pages → Settings → Environment variables.
GA4_MP_SECRETpresent? - If env vars present: check CF Pages Function logs for
[ga4-mp]400s (usually means stale secret or wrong property) - Redeploy after fixing env vars (not optional — functions pick up env at deploy time)
“generate_lead count is 2x GHL contact count”
Section titled ““generate_lead count is 2x GHL contact count””- Double-firing. Check the client-side form handler — is
bctTrackLeadinside a retry loop? - Check
subscribe.js— issendGa4Eventcalled outside theif (!result.already_subscribed)guard?
”All leads attributed to (direct) / (none)”
Section titled “”All leads attributed to (direct) / (none)””bctGetAttributionnot running. Check the tracking JS file loads on every page- UTM capture broken. Test: visit site with
?utm_source=test&utm_medium=testthen open console, runwindow.bctGetAttribution()— should showutm_source: "test"
Version Control
Section titled “Version Control”- v1.0 (2026-04-21): Initial SOP. Monthly audit for client + Tekton-owned tracking stacks.