focosys.io
← Writing
Essay

HIPAA-Compliant Marketing Analytics That Actually Works

A practical HIPAA-compliant marketing analytics guide covering PHI leaks, BAAs, server-side filtering, and audit-safe conversion tracking.

HIPAA-Compliant Marketing Analytics That Actually Works
Key takeaways
  • A compliant stack needs a server-side layer that strips PHI before data reaches any ad platform. Browser-only tracking cannot be made compliant no matter how the consent banner is configured.
  • No major ad platform (Google Ads, Meta, LinkedIn, Microsoft) will sign a BAA, so any conversion tracking sending identifiable data to them on a healthcare property is a violation regardless of consent status.
  • URL paths, form field names, and chat transcripts are the three most common PHI leaks, and none of them show up in a CMP dashboard, only in the actual network request payload.
  • Rebuilding a healthcare tracking stack correctly takes 4-8 weeks and centers on three moves: BAA-eligible infrastructure, server-side filtering, and first-party measurement that doesn't depend on the platforms you can't get a BAA from.

A regional health system hired an agency to run Google Ads for their orthopedics and behavioral health service lines. Six months in, cost per lead looked great. Conversion tracking was clean. The agency's monthly report showed strong ROAS on both service lines.

Then the system's compliance officer asked a simple question in a board meeting: "Where does the appointment request data go after someone submits the form?"

Nobody in the room could answer. Not marketing, not IT, not the agency. It took three weeks and an outside audit to find out: full URL paths, including /schedule/behavioral-health/intake?provider=dr-chen, were riding along in every Google Ads conversion hit. That path, combined with the click ID Google already stores, is PHI. No BAA existed with Google. It had been happening for six months across two condition-specific service lines.

This is not a rare finding. It's the default state of most healthcare marketing tracking, because the tools were built for e-commerce and nobody rebuilt them for a regulated industry. This post is the long version of how to fix that: where PHI actually leaks, why standard "compliant" setups aren't, and what a stack that survives an audit looks like. If you want the narrower version first, start with what HIPAA-safe alternatives to Google Analytics actually require, then come back to the full rebuild here.

Most teams treat HIPAA compliance for marketing like GDPR compliance: add a consent management platform, get a cookie banner, done. That approach fails for two reasons specific to healthcare.

First, consent doesn't cure the underlying problem. Under GDPR, if a user consents to tracking, you can send their data to Google. Under HIPAA, consent to cookies is not the same thing as authorization to disclose protected health information, and even with authorization, you still need a signed Business Associate Agreement with any vendor that could touch that data. A user clicking "accept all" on a cookie banner does not create a BAA with Google. The two frameworks solve different problems, and a CMP only solves the GDPR one.

Second, the "PHI or not" question isn't about the field name. It's about combination. An email address alone is not PHI. A condition name alone is not PHI. An email address tied to a visit to a page about that condition is PHI, because now you have an identifier linked to health information. This is why hashing (Enhanced Conversions, Meta's advanced matching) doesn't solve anything on its own. A SHA-256 hash of patient@email.com sent alongside a page_location of /conditions/hiv-treatment is still PHI. The hash protects the string from being read in transit. It does not remove the association HHS cares about.

Consent Mode and BAAs solve different problems

Consent Mode v2 tells Google whether a user opted into tracking. It says nothing about whether Google is contractually permitted to process health information. You need both a working consent implementation AND a BAA-eligible vendor stack. Most healthcare teams have neither, and believe they have the first.

Where PHI Actually Leaks

Where PHI actually leaks

I've audited enough healthcare tracking setups to know the leaks cluster into a small number of patterns. If you fix these five, you've closed the leaks that usually create the biggest exposure.

1. URL Paths and Query Parameters

This is the single biggest source. Any tag that reads document.location or window.location.href (which is most of them, by default) captures the full URL, including the path. If your site structure looks like /services/oncology/, /providers/dr-martinez-endocrinology/, or /patient-portal/appointments?type=psychiatric-eval, every one of those is a PHI leak the moment it's paired with a click ID, session ID, or hashed email.

GA4's default configuration sends page_location on every event. Google Ads' conversion linker tag reads the full URL to build attribution. Meta Pixel's PageView event does the same. None of these tools ask "does this URL contain health information" before sending it.

Check this yourself: open Chrome DevTools, go to Network, filter for google-analytics, googleads, or facebook. Visit a condition-specific page on your site. Look at the outbound request. If the URL path shows up in the request payload (it will, in most default configs), that's the leak.

2. Form Field Names and Values

Appointment request forms, intake forms, and contact forms often pass field data into the dataLayer for marketing purposes, sometimes intentionally (to pre-fill a CRM), sometimes by accident (a developer pushed the whole form object to dataLayer.push() for debugging and never removed it).

I've seen dataLayer.push({event: 'form_submit', formData: {...}}) where formData included the "reason for visit" free-text field. That field gets picked up by a GTM variable, mapped to a custom parameter, and shipped to Google Ads as part of the conversion event. Nobody wrote code to intentionally send psychiatric history to Google. It happened because a variable was scoped too broadly.

3. Chat Widgets and Session Recording Tools

Third-party scripts added directly to HTML, outside of GTM, are common on healthcare sites because they're usually installed by a UX or support team, not the marketing/analytics team managing GTM. Hotjar, Intercom, Drift, and similar tools frequently get added via a <script> tag in the header, which means:

  • They load before any consent check runs.
  • They are invisible to a GTM container audit, because they're not in GTM.
  • Session recording tools capture everything on screen, including what a patient types into an intake form, even if that field is never sent to GA4 or Meta.

A chat widget that logs conversation transcripts to a vendor's server, where the patient describes symptoms, is transmitting PHI to that vendor. If there's no BAA with the chat vendor, that's a violation independent of whether marketing ever sees the data.

4. Enhanced Conversions and Advanced Matching

Google's Enhanced Conversions and Meta's Advanced Matching both hash user identifiers (email, phone) client-side and send the hash to the platform to improve match rates. The hashing is presented as a privacy feature. In a healthcare context, it's a compliance trap, because teams assume "hashed" means "de-identified" means "not PHI." It doesn't.

If the hashed email is sent alongside event data tied to a health-specific page or action, HHS guidance treats that as PHI regardless of the hash. The identifier plus the health context is what matters, not the encoding.

5. Retargeting Pixels on Condition Pages

Standard retargeting setup: fire the Meta Pixel or Google Ads remarketing tag on every page, build an audience of everyone who visited. On a healthcare site, this means building an audience called (functionally, even if not by name) "people who visited the oncology page" or "people who visited the addiction treatment page," and handing that audience definition to Meta or Google. That's a disclosure of health information to a third party with no BAA, used to build advertising audiences. It's one of the more severe versions of this problem because the intent (audience targeting) makes the health association explicit rather than incidental.

The FTC angle, not just HHS

The FTC has also gone after health-adjacent companies (BetterHelp, GoodRx, Premom) under the FTC Act and the Health Breach Notification Rule for sharing health data with Meta and Google for ad targeting, even outside traditional HIPAA-covered entities. If your organization sits in a gray zone on HIPAA applicability, the FTC's health data enforcement doesn't care. Sharing condition-specific browsing behavior with ad platforms is the exposure, under either framework.

The BAA problem, specifically

A Business Associate Agreement is a contract that makes a vendor legally responsible for protecting PHI it might handle on your behalf. Under HIPAA, if a vendor could touch PHI while providing a service to you, you need a signed BAA with them before you use the service, not after you discover PHI went through it.

Here's the list of vendors relevant to marketing analytics and their actual BAA status, as of this writing:

VendorWill sign a BAA?Notes
Google Analytics 4NoGoogle will not sign a BAA for GA4 under any tier, including GA4 360.
Google AdsNoNo BAA available for the ad platform or conversion tracking.
Google Tag ManagerNoSame as above; GTM is not a covered service.
Google Cloud PlatformYesBigQuery, Cloud Storage, and Cloud Functions are BAA-eligible. This matters for building a compliant server-side layer.
Meta Pixel / CAPINoMeta does not offer a BAA for advertising products.
LinkedIn Insight TagNoSame category as Meta and Google Ads.
Microsoft Advertising (UET)NoSame.
Hotjar / FullStory / session recording toolsRarelyA handful of enterprise-tier vendors will sign a BAA; most self-serve plans will not. Check per-vendor, don't assume.

The practical consequence: you cannot make GA4, Google Ads conversion tracking, or Meta Pixel/CAPI "HIPAA compliant" through configuration. There is no setting that fixes the lack of a BAA. The only way to use these platforms in a healthcare marketing context is to ensure PHI never reaches them in the first place, and treat them as compliant only for aggregate, de-identified, non-health-specific traffic.

This is the point where most teams get confused. They think "if we strip the PHI before it goes to Google, we're fine, and we don't need to worry about the BAA." That's backwards. You still can't disclose PHI to Google under any circumstance, with or without a BAA (a BAA doesn't grant you permission to send PHI freely; it just makes the vendor contractually accountable if PHI does end up there). The right framing: no BAA means PHI must never reach that vendor, period. The absence of a BAA is why the filtering has to be airtight, not a reason to skip filtering. I broke this down separately in what a BAA actually covers for marketing tools, because this misunderstanding shows up in almost every healthcare analytics audit.

What a compliant stack actually looks like

The fix is architectural, not a checkbox. Here's the pattern that holds up under audit.

Server-Side Tagging as the Filtering Layer

Route all tracking events through a server-side GTM container (or an equivalent middleware layer) instead of sending events directly from the browser to Google, Meta, and everyone else. The browser sends events to your own endpoint. Your server container is the only thing that talks to the ad platforms.

This matters because the server container is where you can enforce filtering rules that a browser-side tag cannot reliably enforce. A malicious or misconfigured browser tag can still fire regardless of what your GTM container intends, especially with hardcoded scripts. A server container you control is a single, auditable chokepoint.

// Server container: strip PHI before forwarding to any ad platform tag
function sanitizeEventData(eventData) {
  const phiPatterns = [
    /\/conditions\//i,
    /\/providers\//i,
    /\/patient-portal\//i,
    /diagnosis|treatment|prescription/i
  ];

  const url = eventData.page_location || '';
  const containsPHI = phiPatterns.some(pattern => pattern.test(url));

  if (containsPHI) {
    // Strip the path, keep only the domain and a generic category
    eventData.page_location = eventData.page_location.split('?')[0]
      .replace(/\/(conditions|providers|patient-portal)\/[^/]+/i, '/$1/[redacted]');
  }

  // Never forward form field values, only the fact that a form was submitted
  delete eventData.formData;
  delete eventData.form_field_values;

  return eventData;
}

This is illustrative, not a drop-in solution. Your actual filtering rules depend on your URL structure and form architecture. The point is that the filtering happens once, server-side, before any downstream tag runs, instead of relying on 12 different browser-side tags to each individually avoid grabbing PHI. For the migration mechanics, use the server-side tracking guide as the implementation companion to this compliance model.

GA4 Replacement or Containment

Two viable paths here, depending on how much of your traffic touches PHI-adjacent content.

Path one: replace GA4 with a first-party analytics stack on BAA-eligible infrastructure. Send events from your server container into BigQuery. Google Cloud Platform is BAA-eligible; GA4 itself is not. This means you can build your own dashboards and reports on top of BigQuery data and stay covered, as long as PHI that lands in BigQuery is handled under the terms of your GCP BAA and access to it is restricted appropriately.

Path two: keep GA4 for non-PHI-adjacent pages only, and exclude anything condition-specific, provider-specific, or portal-related from ever reaching it. This is riskier because it depends on your filtering being perfect, permanently, across every future page your marketing team publishes. Path one is more work up front and more resilient long-term.

Most health systems with more than a handful of service lines end up on path one, because path two requires every new landing page to be manually reviewed before launch, forever.

Conversion Tracking Without PHI

For Google Ads and Meta, the goal is a conversion signal that confirms "a lead happened" without any content that identifies what kind of lead, from what page, for what condition.

// Server container: generic conversion event, no condition/provider data
function buildConversionPayload(rawEvent) {
  return {
    event_name: 'lead_submitted',
    event_time: rawEvent.timestamp,
    conversion_value: rawEvent.estimated_value || null,
    // Explicitly no page_location, no form_data, no service_line
    user_data: {
      // Hashed identifiers only if the page itself is not health-specific
      // On condition pages, omit user_data entirely
    }
  };
}

On a general contact page, you might still send a hashed email for Enhanced Conversions matching, because that page carries no health context on its own. On a condition-specific scheduling page, you send the conversion event with no user identifier at all, sacrificing match rate for compliance. That tradeoff is the actual cost of doing this correctly, and it's non-negotiable, not something to get to later.

Separate your conversion actions by risk tier

Don't use one "Lead" conversion action for your entire site. Split conversions into tiers: general inquiries (low risk, can carry identifiers) versus service-line-specific appointment requests (high risk, generic signal only, no identifiers, no page context). This lets you keep decent match rates where it's safe and stay clean where it isn't.

Your CMP still matters, but it can't be the only gate. Pass consent state into your server container as part of every event, and have the server container itself refuse to forward events to non-BAA vendors when consent is absent or when the event touches PHI-adjacent content, regardless of consent.

This distinction matters: consent controls whether you send non-PHI marketing data to Google and Meta. It does not, and cannot, authorize sending PHI to a vendor with no BAA. Build your server logic around that separation explicitly, rather than treating "consent granted" as a green light for everything.

The 6-step rebuild

If you're starting from an audit that found problems (and if you haven't audited yet, you likely have them), here's the order of operations that works.

  1. Inventory every tag and script currently live on the site. Export your GTM container JSON. Grep it for every tag type. Separately, view-source your top 20 pages and note every hardcoded <script> tag not managed by GTM. This step alone usually surfaces 3-5 tools nobody remembered installing.

  2. Classify your URL structure by PHI risk. Every page path gets tagged: general (no health context), condition-adjacent (mentions a service line but not patient-specific), and patient-specific (portal, scheduling, intake). This classification drives every filtering rule downstream.

  3. Stand up the server-side container and route all traffic through it. Nothing should send directly from the browser to Google or Meta once this is live. This is the single biggest lift in the rebuild, typically 2-3 weeks depending on your current tag count.

  4. Build and test the filtering layer against your PHI-risk classification. Test by visiting every page tier and checking the Network tab for outbound requests. Confirm patient-specific pages send zero identifying information downstream, and condition-adjacent pages send only generic conversion events.

  5. Migrate GA4 reporting to BigQuery-based first-party analytics, or lock down GA4 to general-tier pages only with hard filtering in front of it.

  6. Get BAAs signed with every vendor that remains in the stack, including GCP, your CMP if it processes any user data, and your chat/session recording tools if you're keeping them. If a vendor won't sign, it comes out of the stack, no exceptions for "but the reporting is so good."

A mid-size health system with 10-15 service lines takes 4-8 weeks for this end-to-end, most of it in steps 3 and 4. It is slower than any agency's standard tracking setup, because standard tracking setups were never built for this constraint.

What to check this week

You don't need the full rebuild to find out how exposed you are right now. Open DevTools, go to Network, filter for google, facebook, doubleclick. Visit three pages: your homepage, a condition-specific service page, and your appointment scheduling flow. Look at what's in the outbound request payloads on the second and third page.

If you see the URL path, a form value, or a hashed identifier riding alongside a health-specific page, you have exactly the problem this post describes. Everything after that is architecture. The audit itself takes fifteen minutes.

Christopher Landaverde — Marketing Systems Engineer More writing →