Audits one website or web app end to end and scores it: what every tag sends, under which consent state, with which IDs and values, into which platform and CRM field. The output is a scored report, an evidence ledger, a not-checked list and a prioritised fix list. The audit changes nothing, and a finding is a lead until a reproduction or a platform read confirms it.
When to use
Use this skill when:
- Someone asks for a tracking, tag, analytics, pixel or conversion tracking audit, or a one-off tracking health check.
- A redesign, replatform, CMP change, server-side migration or budget increase will rely on conversion data.
- An agency or previous owner hands over tracking and nobody has a baseline.
- Nobody trusts the numbers and no single incident explains why, or a scored baseline is needed to compare after fixes.
Use something else when:
- One metric broke on a known date: tracking-break-map finds the first failing layer.
- Only the GTM container needs reviewing: gtm-container-audit.
- Consent across regions, partial choices and withdrawal: consent-debugging.
- GA4 purchases disagree with orders order by order: purchase-reconciliation.
- Unassigned or (not set) rows: ga4-unassigned-not-set-diagnosis.
- Only one GA4 property's settings and data quality: ga4-audit.
- The plan itself, or event conformance in Mixpanel, Amplitude, PostHog or Segment: tracking-plan-audit.
- No tracking plan exists: tracking-plan-builder first, or audit against a derived plan.
- Platforms differ by design (attribution, dates, view-through): platform-number-reconciliation (planned).
- Recurring monitoring, alerts or an incident write-up: tracking-monitoring-and-incidents (planned); this skill is the one-off scored baseline.
- Native mobile app SDK events: mobile-attribution-fundamentals.
If a named skill is not installed or is still planned, use the fallback for that domain in references/checks.md and mark the deeper check not covered.
Inputs
Read these first if they exist (assets/example-acme-stack.md shows the fields):
martech-context/stack.md: hosts, containers, expected and retired destination IDs, server and native routes, first-party collection endpoints, the offline upload route, CMP, owners.martech-context/tracking-plan/: trigger, must-not-fire cases, dedup key, value rule and consent category per event. Pass rows whose release shipped; har-audit skips proposed rows.martech-context/decisions.md: consent model per region (basic or advanced for Google tags and for Microsoft UET, vendor categories), value rules, first or latest touch policy, approved hashed matching.martech-context/naming-rules.mdandmetrics.md: the UTM registry, conversion definitions, and agreed thresholds that replace this skill's defaults.
Then fill assets/audit-scope-template.md with the user.
- Access levels: L1 public pages only; L2 read access (GA4 and Google Ads MCP servers, GTM export, Meta Ads MCP read tools, CRM and order exports); L3 approved test leads, signups and orders. The level reached caps coverage.
- Exports carry IDs, dates, sources, click IDs, UTMs and amounts only. Ask for name, email and phone columns to be removed.
- Never ask for passwords, tokens, API secrets or service account keys.
Procedure
1. Agree scope, level and approvals
- Keep every audit file in the user's project under
tracking-audit/<YYYY-MM-DD>/(captures/, exports/, outputs/, checklist.csv, report.md, fix-list.md), never in the skill folder. - Fill the scope sheet before any capture. Banner clicks, synthetic click IDs on live landing pages, test leads, test signups, test orders and platform test events each need a named yes. Without one, mark the dependent checks NOT_CHECKED with "no approval".
- Markers:
gclid=TeSter-123exactly; other click IDs andutm_campaignwith theTeSter-123-prefix; test identitiesqa-audit-NN@example.comand+15555550100. Never a real click ID or customer record. - If the consent model per region is not documented, stop and get it from the privacy owner. Consent checks cannot pass or fail without it.
2. Write the expected state
- From stack.md and the plan: expected and retired IDs, the route per event and destination, and each tier-1 conversion's source, trigger, dedup key and value rule.
- No plan: derive one from the current setup, label it "derived, not approved" and cap EVT checks at WARN, because a derived plan turns today's bugs into expectations.
- Absence of a browser tag is not absence of tracking. Ask which events go server-side, through a native integration or by offline import before judging coverage.
3. Capture browser evidence
- Per page type in scope: no-choice, reject and accept captures in a fresh context, cache disabled, log preserved. Consent, SPA and cross-domain cases run three times, one throttled. Journeys start from the landing routes the ads use, built from the final URL and tracking template with markers; never request an ad click URL.
- har-audit reads only HAR files exported from DevTools or Playwright recordHar, never one built with invented or copied start times. Without an export, apply the checks by hand from saved request lists, label them "script not run" and mark time-dependent checks NOT_CHECKED (references/capture-protocol.md).
- The agent's isolated browser (Playwright MCP with
--isolated, or Chrome DevTools MCPnew_pagewith a newisolatedContextper run; never the user's profile) drives scenarios, reads cookie names from request headers and saves complete request lists (Playwright MCP:static: true). - Log every file in the evidence ledger, named after its ID.
4. Run the scripts
One har-audit run per scenario. Options and destination keys are in each script's header:
S="$SKILL_DIR/scripts"; A=tracking-audit/2026-10-28 # SKILL_DIR: this skill's folder
node "$S/har-audit.mjs" "$A/captures/E-01_no-choice_home_run1.har.json" --scenario no-choice --mode advanced --uet-mode basic \
--expect-ids G-XXXXXXXXXX,AW-123456789 --own-domains example.com --cmp-hosts cdn.consent.example.org > "$A/outputs/E-01.txt"
node "$S/har-audit.mjs" "$A/captures/E-06_journey_run1.har.json" --scenario journey --own-domains example.com \
--markers gclid=TeSter-123 --pii qa-audit-01@example.com --server-routes meta:Purchase \
--plan-events "$A/plan-events.csv" --order-value 99.00 --order-currency USD > "$A/outputs/E-06.txt"
node "$S/crm-attribution-coverage.mjs" "$A/exports/leads.csv" --created "Create date" \
--source "Original Traffic Source" --paid-search "Paid search" --gclid gclid > "$A/outputs/E-17.txt"
node "$S/score-audit.mjs" "$A/checklist.csv"
- Never mix scenarios in one run: the scenario decides the consent rules. Exit 1 means at least one FAIL (score-audit: an open S1 or S2 FAIL); 2 means bad input, such as a HAR without real request times.
- Pass
--modeand--uet-modefrom the scope sheet on every consent run (--uet-mode advancedonly where UET is documented to fire denied before a choice). Pass--granted-defaultsonly where the documented model grants signals by default,--form-hostsfor embedded schedulers or forms,--collector-hostsfor the scope sheet's first-party collection endpoints, and--choice-atwhen the capture line's choice time is more than 2 seconds from the recorded click. - Run crm-attribution-coverage once per click ID column (in HubSpot the hidden-field property and HubSpot's own Google ad click ID) and judge the one the offline import reads.
- If Node is unavailable, apply references/checks.md by hand and label the report "script not run".
5. Read platform settings and reports (L2)
- Exact reads for GA4, Google Ads (landing pages and tracking templates included), Meta, BigQuery and exports, plus the completed-day rule: references/platform-reads.md.
- The GA4 MCP server cannot read data retention, enhanced measurement or data redaction (the Admin API can); data filters, unwanted referrals and configured domains need a UI read or an owner's screenshot.
6. Judge each domain
Rules for all 71 checks: references/checks.md. The calls that need judgement:
- Consent in opt-in regions: any non-essential vendor event request or cookie before a choice or after reject is a FAIL (S1); a library loaded with no event request, or an unclassified host receiving data, is a WARN until classified. Google tags follow the documented mode: basic sends nothing before a grant; advanced sends only denied-state requests (gcs G100) and writes no
_ga,_gclor FPID cookies. Advanced UET may send onlyasc=Dbeacons. - A conversion fires on the committed outcome, once. One on a submit click, form start, checkout view, failed payment or the click that opens an embed is a FAIL (S2), and reload, back, double click or revisit must send nothing new unless the destination deduplicates on the same ID.
- GA4 deduplicates web-stream purchases on
transaction_id, and all empty IDs together; a missing or empty ID is a FAIL. One primary Google Ads action per outcome across website tag, GA4 import, offline import and integrations. - Dual routes: Meta pairs browser and server copies on event name plus
eventID/event_idwithin 48 hours; TikTok and OpenAI Ads need the sameevent_idon both; LinkedIn counts only the Insight Tag copy when IDs match. GA4 has no general dedup beyond purchases. - Cross-domain passes only with an unchanged client ID in 3 of 3 fresh-click runs (
_glexpires after two minutes). In SPAs,send_page_view: falsedoes not stop history page views; the observed count must equal the fixture's. - Click IDs are case sensitive and must reach the CRM field exactly. CRM control segment: coverage at least 95 percent PASS, 80 to 95 WARN, under 80 FAIL, over consented records where the decision forbids capture without consent; malformed values or gclids with no upper-case letter above 1 percent FAIL.
- The test email or phone raw, encoded, base64 or lower-cased in any tag, own-host collector or third-party request is a FAIL (S1); a body the script cannot read is a WARN until the vendor's tool shows no test value. Its SHA-256 belongs only in approved matching fields after a grant.
- Numbers that differ by design are not defects until aligned definitions still disagree. Browser protections, blockers and refused consent are limits to report, never defects to route around.
7. Confirm the findings that matter
- Every FAIL and every S1 or S2 WARN gets one reproduction in a fresh context, with its evidence ID. A FAIL that does not reproduce becomes a WARN marked unconfirmed. A platform-read finding is confirmed by a second read or the owner's UI screenshot.
- Negative conversion tests (validation error, declined test card, reload, double click) run only with approval, on staging or a test payment method, with markers. List every record created.
- If real personal data appears in a vendor request, stop, restrict the evidence and tell the user the same day, with a draft note for the privacy owner. Never send it yourself.
8. Score, report and hand off
- Copy assets/audit-checklist.csv and give each check one status (PASS, WARN, FAIL, NOT_CHECKED, NA): the worst result across every capture, run, region, route and read that tested it. INFO lines are evidence, not statuses. Add evidence IDs, owner, impact and effort; NOT_CHECKED and NA need a reason, and a reproduced finding gets "confirmed" in notes.
- Run score-audit: severity weights 8, 4, 2, 1 with WARN at half; coverage under 60 percent withholds the score; any open S1 FAIL makes the verdict "Fail: privacy" with grade F; any open S2 FAIL caps the grade at D. Model: references/scoring.md.
- Write the report from assets/audit-report-template.md and the fix list from assets/fix-list-template.md, in score-audit's fix order.
- Propose updates to stack.md, decisions.md and tracking-plan/. Write nothing until the user approves.
Common failure modes
Mechanisms and the tempting wrong fix for each: references/failure-modes.md.
- Snippet outside the CMP, or a pixel that collects by default. Detect: event requests in the no-choice capture, loaded by page source or carrying no denied state (CON-02).
- Unknown collector. Detect: an unclassified host or own-host candidate receiving data (INV-06, CON-02).
- Session replay recording personal data. Detect: base64 hit or unreadable-body WARN (PII-01).
- Browser pixel without the server's event ID. Detect: Meta Purchase without eid while Conversions API runs (SST-02).
- GET forms leaking personal data. Detect: the test email in page URLs and in every tag's page location (PII-01, PII-02).
- Hidden-field script lowercases click IDs. Detect: case changed in the form POST; the custom CRM property lowercased while HubSpot's own Google ad click ID is not (CID-04 to CID-06).
- Scheduler in a third-party frame. Detect: an embed URL without markers and no conversion from the vendor's success message (CID-04, CNV-01).
- Server route ignoring consent. Detect: server logs show vendor requests for a visitor who rejected (CON-07).
Worked example
Acme Analytics (fictional B2B SaaS, UK opt-in, advanced consent mode): www.example.com, app.example.com (SPA), hosted checkout on pay.example.net, server container on sgtm.example.com; inputs in assets/example-acme-*.
score-audit: 71 checks, 37 PASS, 13 WARN, 16 FAIL, 4 NOT_CHECKED, 1 NA; score 59.5; coverage 94.4 percent; verdict Fail: privacy, grade F.
- P1: a theme-coded LinkedIn tag fires before a choice and after reject (CON-02, CON-03); a GET demo form puts the test email in the URL, so five vendors receive it (PII-01, PII-02).
- P2: two primary purchase actions (CNV-06); Meta Purchase without eventID while the server sends event_id (SST-02); VAT inside the value, 118.8 where the net order is 99.00 (CNV-04).
- P3: the /pricing redirect drops every marker (CID-01); since 2026-09-15 the form script lowercases the gclid property the offline import reads (CID-04 to CID-06, CID-08); the payment return starts a referral session (XDS-03); two SPA page view owners send 8 views where 5 are expected (XDS-05).
Commands, exact output, finding handling, fix items, regression fixtures: references/worked-example.md.
Acceptance checks
- Scope: the scope sheet names level, hosts, journeys, regions with consent model, expected and retired IDs and approvals. Pass: no capture predates it.
- Expected state: written from stack.md and the plan, or labelled derived. Pass: every tier-1 conversion has a source, trigger, dedup key and value rule, or an open question for its owner.
- Captures: every page type in scope has no-choice, reject and accept HAR exports from a fresh isolated context; race-sensitive cases ran three times. Pass: the evidence ledger lists each file with scenario, UTC time and run number.
- Scripts: har-audit ran on every capture, crm-attribution-coverage on each click ID column at L2, score-audit on the checklist, or the report says "script not run". Pass: outputs are cited by evidence ID, and each unclassified host and own-host candidate is classified or open with an owner.
- Statuses: all 71 checks carry one status; every NOT_CHECKED and NA has a reason. Pass: score-audit reports no validation errors.
- Evidence: every FAIL and every S1 or S2 WARN cites evidence and a reproduction or second read, or is labelled unconfirmed. Pass: no confirmed finding without evidence.
- Score: the score-audit output appears unchanged in the report. Pass: verdict, grade, score and coverage match it.
- Fix list: each item has check IDs, owner, draft change, test with approval, rollback and expected report movement. Pass: a colleague could apply it without asking a question.
- Limits and gaps: the report has "Not checked" and "Expected limits" sections. Pass: neither says "none" without a reason.
- Safety: no write call was made and every test record is listed with its cleanup owner. Pass: searching the report, ledger, fix list and any shared excerpt for "@", "%40" and "%2540" outside example domains, non-marker click IDs, "Bearer", "token" and "key=" finds only test values. Raw captures stay with the project; their personal-data result comes from har-audit PII-01 and PII-02.
Boundaries
- Read-only. No GTM publish or workspace edit, no GA4, Google Ads, Meta or CRM setting change, no conversion upload, no Measurement Protocol or Conversions API send, no data filter activation. Every fix leaves as a draft for its owner.
- Live side effects only with the approval in the scope sheet. Meta events sent with a test event code still reach ads measurement, so block ad conversion hosts in the test browser when ads are not under test.
- The audit tests against the privacy owner's documented consent decision. It does not choose the consent model, decide legal basis or certify compliance, and it never deletes data.
- Never use the user's personal browser profile or credentials, or sign in to Tag Assistant or a vendor tool. Never recommend circumventing browser protections, blockers or a refusal (no parameter renaming, fingerprinting or consent bypass).
- Official tools, read-only:
- Google Analytics MCP server: analytics.readonly credentials, started without
--allow-write. - Google Ads MCP server: only search, get_resource_metadata and list_accessible_customers, with
labels: falseandassets: falsein its tools_config.yaml (its mutate tools are on by default) and a Read-only user. - Meta Ads MCP server: read tools only (ads_get_*, ads_pixel_event_read, ads_pixel_parameter_read), never the create, update or delete tools.
- Tag Manager has no Google MCP server: use an export or the tagmanager.readonly API, or Stape's GTM MCP server with a Read-permission identity and read actions only.
- BigQuery MCP server: execute_sql_readonly, with execute_sql and cancel_job denied by policy; Cloud Logging under a log reader role for server routes.
- Google Analytics MCP server: analytics.readonly credentials, started without
- Other hand-offs: utm-convention-and-registry. Planned: cross-domain-and-spa-qa, server-side-tracking-debug, offline-conversion-import.
Live docs
Platform settings, limits and menu paths change. Before stating any parameter name, limit, quota or UI path:
- Fetch the current page from the vendor docs MCP server or llms.txt listed in
sources.yaml, or open the canonical URL. - If the live page disagrees with this skill, follow the live page and tell the user what changed.
- Cite the URL you used.
Most used here: GA4 limits, transaction ID, cross-domain, SPA and Admin API pages; Google's consent guide; the Tag Manager release notes; Google Ads offline import help (2998031); Meta, TikTok and OpenAI Ads dedup; Microsoft's UET consent page; HubSpot default properties; WebKit tracking prevention. Checked 2026-11-01 to 2026-11-06.