Comprehensive Google Ads audit of an online store
A real audit of a real UPLIFY client — fully anonymized. The niche, brands, domain and all account IDs are hidden; all the analysis, methodology, structure and actual metrics are preserved unchanged.
What narrows the conclusions — read first
- STEP-0 not filled in by the client: no margin, target CPA/ROAS, stock, promo calendar, or pause context → conclusions about budget scaling and paused campaigns are narrowed (client_input). This does NOT block the audit — it only narrows the relevant conclusions.
- Merchant connected — feed read (≈2,969 offers) + account-level statuses; item-level per-offer statuses are limited by access rights (2,968 in unknown) → product conclusions are narrowed to feed level + a roadmap to lift the limitation (see “Audiences & feed”).
- PMax API: no per-asset conversions / search IS / placement cost → waste/scale at the asset level is presented via impressions/CTR, not cost-proof.
- Conversion lag: the new campaign (category B) is <30 days old, with a large share of UNKNOWN-lag → the recent window is under-counted, confidence is reduced.
- GSC unavailable — the organic cross-check is limited (not a blocker; we worked with Ads + GA4 + Keyword Planner).
Store profile + Claim Ledger
An online store of mid-to-high-ticket home goods with a broad assortment (≈1,480 unique SKUs × 2 languages). High-ticket considered demand (a long purchase decision), pronounced seasonality of two product lines, an active phone order channel. The site is bilingual UA/RU (hreflang uk, ru, x-default) — this explains the SKU duplication in the feed as deliberate localization, not an error.
Free shipping above an order threshold; official dealer → manufacturer warranty; installments / pay-in-parts; service maintenance; government support program.
"#1 / best / cheapest / market leader"; guaranteed savings as an unconditional promise; any superlative without proof.
Specific discount %s in copy (they change); "delivery in 1 day"; free service.
Executive summary
The account has 5 campaigns, all Performance Max (2 active, 3 paused). Health is above average. On paper the active campaigns exceed their target ROAS by 3–4× (category A: 15.2 vs. a 3.7 target; category B: 10.9 vs. a 4.7 target).
Measurement verdict — "web purchases can be trusted, the full funnel with a correction." Web-conversion value in Ads (₴1,012,010) nearly matches GA4 google/cpc purchaseRevenue (₴966,281) — a ~4.7% gap, below the 15% threshold → web-purchase is reliable. BUT the phone/lead path is materially under-counted (1,488 hover_phones + 814 form_start in GA4 vs. ~5 in Ads).
1. Compliance / geo
P0The new active campaign (cat. B) has no exclusions of occupied cities that are set on the other 4 (16 cities). Budget waste in the war zone + non-delivery risk → add exclusions.
2. Measurement (GATE)
P1The test conversion purchase_test is still primary; phone/lead conversions are under-counted → Smart Bidding optimizes on only part of the purchases. We lock the signal BEFORE scaling.
3. Brand + growth
P2PMax eats the own brand with no brand-exclusion and there's no separate brand-Search → the brand is managed poorly (F4/F5). Cat. B groups have no search-themes/signals; missing callouts / snippets / price / promotion.
Signal first, then scale
Any automated bidding strategy is only as smart as the conversion signal is clean. In PMax this is doubly critical: bidding is fully automated (tROAS/tCPA), there are no manual modifiers — the algorithm optimizes on exactly what we marked as a conversion.
- Test conversion in primary. purchase_test has primary_for_goal=true and has already recorded a conversion on the new campaign → double-counting and tROAS distortion.
- Phone/leads under-counted. 1,488 hover_phones + 814 form_start (GA4, 30d) vs. ~5 in Ads → some "zero" products/geos actually drive calls. Don't cut blind.
- Signal value / quality. The Ads↔GA4 reconciliation on web-purchase is clean (+4.7%), but low-volume GA4 breakdowns should be read with a measurement caveat (sampling/thresholding) — not as exact zeros.
- 1. Remove the test conversion from primary.
- 2. Wire qualified calls / submit-leads as conversion actions (secondary/observe).
- 3. Wait 2–3 weeks for clean data.
- 4. Only then touch budget/tROAS. Otherwise Smart Bidding will "cement" the current distortions.
Full findings table (19)
Each finding has a severity (P0–P3), a confidence and a decision type — it determines what the finding turns into next (the "Actions" section). A strong senior knows how to fix, to investigate, and to deliberately do nothing.
| # | Area | Finding | Sev. | Conf. | Decision |
|---|---|---|---|---|---|
| F1 | Geo / compliance | The new campaign (cat. B) targets the whole country with no exclusions of the 16 occupied cities that are set on the other 4. Verified GAQL; recommendation on the presumption of an oversight (STEP-0 empty → confirm). | P0 | 95/80% | protect |
| F2 | Measurement | The purchase_test conversion has primary_for_goal=true and recorded a conversion on the new campaign → double-counting risk. | P1 | 90% | fix |
| F3 | Measurement / leads | Phone/leads under-counted: 1,488 hover_phones + 814 form_start (GA4, 30d) vs. ~5 in Ads. Smart Bidding doesn't optimize on calls. | P1 | 75% | fix |
| F4 | PMax / cannibalization (D-9) | PMax with no brand-exclusion absorbs brand search + remarketing → the averaged ROAS (15.2 / 10.9) is inflated by cheap brand/retarget sales. New-vs-returning is not separated → the real CPA on NEW customers is unknown. | P1 | 80% | investigate |
| F5 | Brand coverage (D-10) | No dedicated brand-Search campaign; brand demand (≈5k/mo — “Keywords & copy”) is taken by PMax more expensively and without control. The own brand should be the cheapest and highest-converting channel — right now it's unmanaged. | P2 | 80% | protect |
| F6 | PMax signals | Both asset groups of the active campaign (cat. B) have 0 search-themes and 0 audience signals → PMax learns blind. | P2 | 95% | fix |
| F7 | PMax creative | One group is empty (ad_strength=POOR, no headlines/media); the other is AVERAGE, with a headline using slang/truncation. | P2 | 85% | fix |
| F8 | Extensions | Across all campaigns there are no callouts, structured snippets, price, promotion — despite a strong set of ALLOWED offers. | P2 | 90% | fix |
| F9 | Product / feed | SKU duplicates between the RU/UA feeds split impressions and budget: one language version converts, the duplicate has 0 conv and burns budget (confirmed on 5 SKUs). | P2 | 80% | fix |
| F10 | Product / waste | Top-spend products with no conversions (4 items, ₴1,602 / ₴737 / ₴548 / ₴516, 0 conv) — candidates for isolation into a low-priority listing group. | P2 | 70% | investigate |
| F11 | Geo efficiency | 5 regions with spend and no conversions (≈10.6% spend, 0 conv) — watchlist; don't cut blind (small sample, call path). | P2 | 60% | watchlist |
| F12 | Geo concentration | Kyiv = 29% spend (ROAS 10 / 5.6); top-ROAS regions (40 / 35 / 48 / 31 / 27) are underfunded → room to concentrate. | P2 | 65% | scale |
| F13 | Budget / strategy | Active campaigns exceed their ROAS target by 3–4×, budget isn't fully spent → room to scale / lower tROAS (after margin AND after F4). | P2 | 70% | investigate |
| F14 | GA4 hygiene | GA4 sources contain dev/spam referrers (localhost:8000, 127.0.0.1, a spam domain) with no internal filter. | P3 | 90% | fix |
| F15 | Channels / incrementality | An independent paid channel outside Google Ads drives ₴665,646 in revenue from 1,049 sessions → it affects Ads attribution. | P2 | 80% | investigate |
| F16 | Merchant | 1 product disapproved (legal-takedown, RU+UA); 2,968 offers in unknown (diagnostics rights — see “Audiences & feed”). | P3 | 70% | protect |
| F17 | Devices (CTV) | Minor spend on Connected TV with no conversions (~₴62) — immaterial, monitor. | P3 | 80% | do nothing |
| F18 | Funnel | A sharp drop view_item (24,683) → add_to_cart (123) over 30d — typical for high-consideration, points to a phone/offline path outside Ads. | P2 | 65% | investigate |
| F19 | Structure / naming | Inconsistent campaign and group naming (a typo in the brand, default RU group names) — off-standard. | P3 | 95% | fix |
Action plan — ADD before REMOVE
| Prio. | What | Expected effect |
|---|---|---|
| P0 | Add exclusions of the 16 occupied cities to the new campaign (copy from the other 4) | Remove non-delivery/waste in the war zone; compliance |
| P0 | Remove purchase_test from primary (secondary / archive) | Clean purchase count, no double-counting |
| P1 | Wire call/lead measurement as a conversion in Ads (runbook below) | Smart Bidding "sees" phone sales |
| P1 | Add brand-exclusion to PMax (exclude OWN brand terms) + create a brand-Search campaign paused with real brand keywords | The real non-brand economics become visible; cheaper, managed brand traffic |
| P1 | Add search-themes + audience signals to cat. B groups (category-specific, not "generic") | Better PMax learning and targeting |
| P1 | Add callouts / snippets / price / promotion to the 2 active campaigns | CTR / ad quality, SERP coverage |
| P2 | Fill or disable the empty asset group (POOR) | Remove ballast, raise ad_strength |
| P2 | Customer Match (buyers 540d) — prepare after client consent on data + consent terms | LAL signal / exclusion of existing customers from prospecting |
| P2 | Test: ↓tROAS of active campaigns OR ↑budget +10–25% — only after margin AND the cannibalization check (F4) | More volume while preserving profitability |
| P2 | Sort out the RU/UA SKU duplicates in the feed (supplemental / custom_label) | Less impression/budget cannibalization |
Runbook for the "call measurement" step
To avoid the typical "calls in GA4, 0 in Ads" gap:
- In GA4 mark call_phones + form_start/submit as key events with correct counting (one per session for a call).
- Import these key events into Ads as conversion actions; count = One for a call (not Every).
- Keep on secondary (observe) until quality is confirmed — do NOT push straight to primary.
- After 7–14 days, reconcile the Ads conversion count with GA4; if a CRM is available — reconcile call→sale so that only qualified leads make it into primary.
Success criteria, monitoring and rollback
Every P0/P1 action comes with a "before" snapshot, a control metric, an observation window and an explicit rollback trigger defined BEFORE rollout. No change is applied without a way back.
| Change | Control metric | Window | Rollback trigger |
|---|---|---|---|
| Geo exclusion of 16 cities (F1) | impressions/spend on excluded geos; non-delivery | 3–7 days | cat. B conversions drop >15% with no ROAS change |
| Remove the test conversion (F2) | primary conversion count, tROAS stability | 7 days | CPA jump >20% → return as observe |
| Calls/leads in Ads (F3) | Ads conv vs. GA4 events; quality (CRM) | 7–14 days | spam leads in primary → keep secondary |
| Brand-exclusion + brand-Search (F4/F5) | non-brand ROAS (true), brand CPC/CTR/IS | 14 days | overall CPA rises while brand reach drops → narrow the exclusion |
| Signals + extensions (F6/F8) | ad_strength, CTR, group conv-rate | 14 days | learning degrades → roll back the signal |
"Actions" — every finding → a decision
A report is not a PDF that ends up in a drawer. In UPLIFY OS every finding necessarily gets a destination — the "0 report-only" invariant:
- Action — an executable, reversible card in OS (prepare → manager approve → execute → undo), with a log and a "before" snapshot. E.g. add_geo_exclusion, add_asset_group_signal, create_search_campaign.
- Task — something a human does outside the API (a Customer Match list after client consent; a spec for the tech specialist on call tracking). It becomes a trackable task.
- Watchlist — under observation, the decision deferred until data accumulates (weak geos, CTV).
- N/A — deliberately not applicable (with a reason). It's recorded too, not "forgotten".
The link is two-way: every Action/Task knows which finding it stems from, and coverage is counted as processed / total. In this audit — 19/19 (100%). A routing example:
| Finding | Decision | Destination |
|---|---|---|
| F1 geo exclusion | protect | Action add_geo_exclusion (reversible) |
| F2 test conversion | fix | Task for a manager (change the conv-set in the Ads UI) |
| F4 PMax cannibalization | investigate | Task: separate new-vs-returning + brand-exclusion |
| F5 brand coverage | protect | Action create_search_campaign (brand, paused) |
| F11 weak geos | watchlist | Watchlist (decision after data volume + calls) |
| F17 CTV ₴62 | do nothing | N/A (immaterial impact, deliberate) |
Target structure map (before → after)
| Campaign | Status | Action | Mode |
|---|---|---|---|
| Category A (PMax) | ENABLED · 15.2 | KEEP + brand-exclusion + signals/extensions; test ↑budget / ↓tROAS only after F4 | phased |
| Category B (PMax, new) | ENABLED · 10.9 | PROTECT (geo exclusion) + signals + fill the groups | direct |
| Brand Search (new) | CREATE | CREATE paused — capture brand demand cheaply, remove from PMax (F5) | create |
| Category "everything else" + B | PAUSED · 7.3 | INVESTIGATE — why paused; possible consolidation | client |
| Category C (electrical) | PAUSED · 0.27 | KEEP PAUSED — weak economics | do_nothing |
| Category D (niche) | PAUSED · 31.6 | INVESTIGATE — little data (84 clicks), high return; relaunch for the season | experiment |
Budget + experiment design
| Campaign | Current (90d) | Budget action | lost IS | Rationale | Conf. |
|---|---|---|---|---|---|
| Category A | ₴51,136 | hold → test ↑ after F4 | n/a | ROAS 15.2 on paper, but inflated by cannibalization — investigate first | 70% |
| Category B | ₴8,419 | hold + signals | n/a | new, <30d, lag not matured — too early to touch budget | 65% |
| Top-ROAS geo | ≈₴8,500 | redirect here | — | ROAS 27–48, underfunded (F12) | 65% |
90-day roadmap
Stop the leaks
P0: occupied-cities geo exclusions + remove the test conversion from primary. Set up call measurement, GA4 filters, fix the disapproved product.
Brand + signals
Brand-exclusion in PMax + brand-Search paused; signals for cat. B groups; callouts/snippets/price/promotion; fill the empty group; fix the naming.
Learning
Let PMax learn on clean conversions; separate new-vs-returning (F4); test ↓tROAS (experiment); monitor lag.
Cleanup
SKU duplicates and waste products in the feed; decisions on weak geo regions based on accumulated data (accounting for calls).
Scale
After confirming margin AND a clean signal — scale into top-ROAS regions; consider relaunching the niche campaign for the season.
Keyword bank + copy analysis
Keyword Planner (UA, real volumes/mo — keywords masked): category B — top query 49,500, then 18,100 / 8,100 / 2,400 / 1,900 / 880; category A — top 12,100, then 2,900 / 2,400 / 1,900 / 1,300 + brand queries 1,900 / 1,600 / 720 / 590. The high-frequency brand queries (≈5k/mo) are exactly the demand that PMax now absorbs without control (F4/F5).
A full breakdown of the headlines of the two active groups (copy is masked to the category level; verdict + reason — as in the real report):
| Type | Copy (by category) | Verdict | Reason |
|---|---|---|---|
| H | Buy [category A] | KEEP | EXCELLENT, intent-match |
| H | Economical [category A] for home | KEEP | Benefit, grounded |
| H | Best [A products] for home | REPLACE | Soft superlative "best" → "from the official dealer" (trust) |
| H | Top [subcategory A] | REPLACE | "Top" is weak → "[subcategory] −15%" (benefit, grounded on the LP) |
| H | [Category A] + warranty | KEEP | Confirms service/warranty in ALLOWED |
| H | Best prices on [cat. B slang] | REPLACE | Slang/truncation + superlative → "[category B] in stock" |
| H | [Category B], top series | KEEP | Intent (2,400/mo) |
| H | (no brand hook) | ADD | Brand-query demand 1,900/mo → "[brands]" (social proof) |
| H | (no service hook) | ADD | Service-query demand 880/mo → "Warranty and service" (benefit) |
Prompt cards for new images
Ready to paste into a generator for the weak/empty group. Each is a "draft concept, needs a brand/legal review." The product is masked; the technique (formats, prohibitions) — as in the real report.
1) Product in a modern interior
1.91:1 · PMax/Display2) Product in a home interior
1:1 · PMax · trust3) Seasonal comfort
4:5 · Discovery/Display4) Studio trust-shot
1.91:1 · premiumGA4 audiences + Merchant supplemental
GA4 audiences
Buyers 90d (exclusion from prospecting + LAL); cart-abandoners 30d (add_to_cart without purchase → remarketing); high-intent (view_item ≥2 or hover_phones/form_start without a purchase); engaged ≥N for LAL. The signal must match the campaign's product mix — a category audience goes EXACTLY into its own campaign, not a "generic" one.
Merchant — 5-layer doctrine
Supplemental only (never products.insert). setup/feed: custom_label margin_tier / performance / season. title: formulas by "reason to buy." price/description: a price corridor; the description as a keyword source. image: no watermarks. moderation: fix disapproved (F16), reconcile price site↔feed.
Lifting the item-level limitation
(1) request a Standard/Admin role in Merchant for the service account (or do a one-off Diagnostics→Products export); (2) re-run the statuses with full rights; (3) only then draw item-level conclusions on waste/price/stock. Until then Shopping conclusions are narrowed, not final.
Risks and caveats
- Small conversion volume (8–9/mo per active campaign) → read with a confidence interval, not weekly point estimates; don't pause/scale on 1–2 conversions.
- Conversion lag in the new campaign → the recent window systematically under-counts real value; verdicts on the fresh period come with reduced confidence.
- The phone channel is invisible to Ads until call measurement is wired — some "zero" geos/products actually drive leads.
- Scaling is blocked until margin/target-ROAS is confirmed AND the cannibalization check (F4) is done — bare revenue-ROAS ≠ profit.
- Merchant item-level is narrowed by access rights — product conclusions are feed-level until the limitation is lifted.
- Seasonality of two lines → a correct comparison only within seasonal windows.
Coverage + summary
| Campaign | Status | Spend ₴ | Conv. | Value ₴ | ROAS | Target |
|---|---|---|---|---|---|---|
| Category A | ENABLED | 51,136 | 25.0 | 775,813 | 15.2 | 3.7 |
| Category B | ENABLED | 8,419 | 9.0 | 91,331 | 10.9 | 4.7 |
| Other + B | PAUSED | 13,120 | 8.0 | 95,902 | 7.3 | 14.0 |
| Category C | PAUSED | 11,270 | 1.0 | 3,042 | 0.27 | 14.0 |
| Category D | PAUSED | 1,451 | 2.0 | 45,922 | 31.6 | 14.0 |
| Total | 85,396 | 45.0 | 1,012,010 | 11.9 |
| Geo (top) | Spend ₴ | ROAS | Signal |
|---|---|---|---|
| Kyiv | 16,648 | 10.0 | top-spend, strong |
| Region H | 3,929 | 40.6 | ⭐ underfunded |
| Region G | 4,594 | 35.1 | ⭐ underfunded |
| 5 regions | ≈9,000 | 0 | ❌ watchlist (0 conv) |
Analysis coverage: campaigns 5/5 (100% spend); asset groups 13/13; products top-30 by spend; geo — all 23 oblasts + Kyiv (100%); all 14 conversion actions. Actions coverage: 19/19 findings (100%) routed to Action / Task / Watchlist / N-A — the "0 report-only" invariant.
GAQL: campaign · conversion_action(+segments) · campaign_criterion LOCATION · geographic_view · segments.device/lag · asset_group(+asset/signal) · campaign_asset · shopping_performance_view · customer · brand-exclusion check. GA4: sessionSourceMedium, newVsReturning, eventName. Keyword Planner. Merchant: product_statuses.
Reconciliation: Ads value 1,012,010 ↔ GA4 google/cpc 966,281 → +4.7% (value-level, web-purchase; doesn't cover the phone path). Data — at the real query time (live read-only).
How this audit was made
This report was prepared by Nestor AI — our AI operator inside UPLIFY OS: senior-level account analysis using our own methodology. Every proposed change ("Actions") is approved by a manager — nothing is applied to the account automatically, every change is reversible, logged and has a rollback trigger. Every finding leads to a decision: an action, a task, a watchlist item, or a deliberate "do nothing" — nothing is left as mere text.