---
name: build-customer-match-lists
description: >-
  Builds and maintains Google Ads Customer Match lists end to end: pulls contacts out of HubSpot, Salesforce, or a
  spreadsheet, normalizes identifiers, creates the user list, uploads members, verifies match rate and matched size
  after processing, and puts a refresh routine in place. Reach for it when an account has no first-party list yet,
  when a new segment is needed (high-value, lapsed, product-line), when a match rate comes back under roughly 29%,
  or when a list has gone 30+ days without a refresh. It ends at a healthy serving-eligible list, it does not wire
  the list into Performance Max as a signal (set-up-audience-signals), attach it to Video or Demand Gen ad groups
  (set-up-audience-targeting), evaluate ongoing segment results (optimize-audience-performance), or decide whether
  the account is ready to grow reach at all (expand-audience-reach).
---
# Build and Maintain Customer Match Lists

## Purpose
Customer Match is the strongest first-party asset in a Google Ads account: one healthy list simultaneously improves
PMax learning, remarketing precision, exclusion hygiene, and lookalike quality. This skill takes raw CRM contacts and
turns them into uploaded, matched, serving-eligible user lists with a documented refresh loop, so downstream audience
skills always have fresh material to work with.

## When to run
- First-time Customer Match setup on an account that has no uploaded lists.
- A campaign plan calls for a segment that does not exist yet: all customers, top-spender slice, lapsed buyers,
  product-line buyers, or a seed list for lookalikes.
- An existing list reports a match rate below ~29% and needs identifier enrichment.
- A list has not been refreshed in 30+ days, or membership is drifting toward the 540-day expiry.
- Opt-outs or churned records must be purged from an uploaded list.

## When NOT to run
- You need the list used as a PMax asset-group signal → run `set-up-audience-signals` after this.
- You need the list attached to Video/Demand Gen ad groups or built into combined segments → `set-up-audience-targeting`.
- You are reviewing how existing audience segments perform in campaigns → `optimize-audience-performance`.
- You are deciding which audience foundation to fix first before growing reach → `expand-audience-reach`.

## Prerequisites
- Google Ads account eligible for Customer Match: roughly 90+ days old, clean policy record, good payment standing.
  If uploads are rejected for eligibility, stop, no tool call fixes that.
- A reachable customer data source: HubSpot, Salesforce, or a Google Sheet export. Any other CRM requires a manual
  export first (HUMAN STEP).
- At least one identifier per record: email, phone, or postal address. More identifier types per record means a
  materially higher match rate.
- Documented advertising-use consent for any records belonging to EEA users (HUMAN STEP to confirm with the client
  or legal owner, never upload EEA data without it).

## Procedure
1. **Consult the policy guardrail.** Before the first Google Ads write of the session, call `gads_policy_guardrail`
   and honor anything it flags about personalized advertising and user-data policy.
2. **Inventory existing lists.** Call `gads_list_user_lists` and record every Customer Match list with its matched
   size, match rate, and last-refresh signal. This prevents duplicate lists and shows which existing list a new
   segment should extend instead.
3. **Agree on the segment plan with the user.** Typical set: one all-customers foundation list, a top-20%-by-revenue
   list for efficiency plays and lookalike seeding, a lapsed-customer list for win-back, and product/tier lists for
   upsell. Confirm which lists to build this session before touching data.
4. **Pull the source records.**
   - HubSpot: `hubspot_search_contacts` with filters matching the segment definition (lifecycle stage, deal value,
     last-activity date).
   - Salesforce: `salesforce_soql_query` selecting email, phone, first/last name, country, and postal code with a
     WHERE clause encoding the segment.
   - Spreadsheet exports: `sheets_get_values` on the export tab.
   - HUMAN STEP (outside VigilDog): exporting from any CRM without a connector, and de-duplicating records the
     business considers the same customer across systems.
5. **Normalize identifiers before upload.** Lowercase and trim emails; convert phones to E.164 (+country code, digits
   only); lowercase and trim names; use two-letter ISO country codes; keep postal codes as issued. Where the upload
   path requires pre-hashing, apply SHA-256 to the already-normalized value, hashing an un-normalized value silently
   destroys the match. Drop rows with zero valid identifiers and exact duplicates.
6. **Confirm consent status.** HUMAN STEP (outside VigilDog): the user confirms consent coverage for EEA records and
   supplies the consent signals to include. Exclude any records whose consent cannot be shown.
7. **Create the list.** Call `gads_create_customer_match_list` with a name that encodes segment and date (for example
   `all-customers-2026-07`). Preview first (validate_only default); apply only after explicit user approval.
8. **Upload members.** Call `gads_add_customer_match_members` with the normalized records, batching large sets.
   Preview first; apply only after user approval. Never echo raw identifiers back into chat, refer to counts only.
9. **Wait out processing, then verify.** Matching takes roughly 24–48 hours. Re-run `gads_list_user_lists` and read
   the match rate and matched-user count against the bands in Decision rules. A list under 100 matched users will not
   serve at all.
10. **Troubleshoot a weak match rate.** If under ~29%: add phone and address identifiers to the same records and
    re-upload via `gads_add_customer_match_members` (preview → approval → apply); scrub bounced or ancient emails;
    expect structurally lower rates from corporate-email B2B files and compensate with extra identifiers rather than
    chasing a consumer-grade rate.
11. **Set the refresh loop.** Agree cadence with the user (see Decision rules), then on each cycle: re-pull the
    segment (step 4), diff against the previous pull, `gads_add_customer_match_members` for new records and
    `gads_remove_customer_match_members` for opt-outs and records leaving the segment, each write previewed and
    user-approved. Log the refresh date and owner so the 540-day membership ceiling is never hit unnoticed.
12. **Hand off.** Report list name, matched size, match rate, and refresh owner. Route to `set-up-audience-signals`
    (PMax) or `set-up-audience-targeting` (Video/Demand Gen) to actually use the list.

## Decision rules
- **Match-rate bands:** 50%+ excellent, proceed; 29–49% workable, proceed but enrich identifiers on the next
  refresh; below 29%, treat as broken and troubleshoot before relying on the list.
- **Size floors:** below 100 matched users the list cannot serve anywhere; 100–999 serves but carries little
  optimization weight; target 1,000+ matched users before using a list as a PMax signal or lookalike seed.
- **Identifier stacking:** email alone typically matches in the ~29–62% range; adding phone adds roughly 15–20
  points; adding postal address adds roughly another 10–15. Always upload every identifier the source has.
- **Refresh cadence:** all-customers list weekly if automated, monthly at minimum; revenue-ranked and lapsed lists
  monthly; one-off campaign lists on the campaign's own calendar. Membership expires at 540 days, a list that is
  never refreshed silently empties itself.
- **B2B files:** corporate emails match poorly against consumer Google accounts. IF the file is mostly work emails,
  THEN set expectations one band lower and lean harder on phone + address identifiers.
- **Which list first:** IF no lists exist, THEN build the all-customers list before any specialty segment, it is
  the foundation for exclusions, signals, and seeds alike.
- **Upload hygiene:** batch member uploads rather than one giant payload; keep files/payloads deduplicated and free
  of identifier-less rows before the write, not after a rejection.
- **Done means:** list status serving-eligible, match rate at or above the workable band, matched count at or above
  the intended floor, and a refresh owner + cadence written down. Anything less is an open item, not a finished list.

## Common failure modes
- **Hashing before normalizing.** `SHA-256("John@X.com ")` matches nothing. Normalize first, hash second, always.
- **Email-only B2B uploads** producing sub-20% match rates that get blamed on the product. Stack identifiers instead.
- **One heroic upload, then decay.** Without a named refresh owner and cadence, the list quietly ages past relevance
  and eventually past the 540-day cap. Step 11 exists to prevent exactly this.
- **Uploading into the manager account** instead of the client account. Confirm the customer ID with the user before
  the create/upload writes.
- **Skipping consent checks for EEA records.** This is a policy violation with account-level consequences, not a
  formatting nit. When consent is unclear, exclude the records.
- **Building a near-duplicate of an existing list** because nobody ran the step-2 inventory. Always list before
  creating.

## Related skills
- Run before: `expand-audience-reach` (its first-party data gate routes here when lists are missing or stale).
- Run after: `set-up-audience-signals` (use the list as a PMax signal), `set-up-audience-targeting` (attach the list
  to Video/Demand Gen), `optimize-audience-performance` (ongoing health checks on how the list performs in campaigns).
