How to Install PostHog on ClickFunnels (2.0 and Classic)

Check every funnel step with one snippet per page. A checkout on another domain you run needs a visitor-ID handoff, and payments need server confirmation.

Three funnel step cards stand on domain plates tagged offer, checkout and thanks; the first, an opt-in page with the ClickFunnels logo, is solid with a blue head strip and a blue line up to the PostHog logo. The checkout and thank-you cards are dashed grey outlines with empty head strips.

Quick answer: Copy the web snippet from your PostHog project, paste it into one ClickFunnels code location, then load each public funnel step with analytics consent granted and confirm a $pageview in PostHog Activity. In 2.0, use site-level Header Code for the whole site or funnel-level Head Code for selected funnels.

In this guide

What You Need Before You Start

If capture must wait for consent, you also need a consent tool whose acceptance and withdrawal callbacks you can edit.

Step 1: Get the PostHog Web Snippet

Log in to PostHog and open the project you want to use for the ClickFunnels funnel.

Open Project settings → Web snippet and copy the full JavaScript snippet. It includes your project token and API host; keep those values together.

PostHog project settings showing the Web snippet section, with the project name and token redacted
PostHog project settings in an earlier interface: the Web snippet section, with the project name and token redacted. Copy the snippet from your own project.

If you use separate PostHog projects for staging, client accounts, brands, or regions, pause here and confirm the project is the correct one. Sending production funnel data into the wrong project is easy to miss and annoying to clean up later.

Step 2: Review the Person Profile Setting

Keep person_profiles: 'identified_only', the web SDK’s default. (+) It still captures anonymous visits, including ad traffic from people who have not yet submitted a form or bought anything.

Anonymous events cost less to process than identified events. (+) After a confirmed signup or purchase gives you a stable contact or customer ID, call posthog.identify() to create a profile and link the earlier anonymous activity. (+) This call needs to be part of your implementation; submitting a form does not replace it. For the billing trade-off, see how to reduce identified-event costs.

Here’s what our snippet looks like with that profile setting. Replace the token and host with your project’s values; this example uses the US host. If your funnel needs consent before analytics, keep both opt-out settings: capture stays off and no analytics identifiers are stored until the visitor accepts, and Step 5 connects your consent tool. (+) If it doesn’t, remove only opt_out_capturing_by_default: true. PostHog then captures from the first page load, and the remaining setting keeps analytics identifiers out of storage for any visitor who opts out.

<script>
    !function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],Object.defineProperty(u,"toString",{configurable:!0,enumerable:!0,writable:!0,value:function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e}}),Object.defineProperty(u.people,"toString",{configurable:!0,enumerable:!0,writable:!0,value:function(){return u.toString(1)+".people (stub)"}}),o="init capture register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagResult isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey getNextSurveyStep identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty createPersonProfile opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing debug".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
    posthog.init('YOUR_POSTHOG_PROJECT_TOKEN', {
        api_host: 'https://us.i.posthog.com',
        defaults: '2026-05-30',
        person_profiles: 'identified_only',
        opt_out_capturing_by_default: true,
        opt_out_persistence_by_default: true,
        cookieWinsOnConflict: true,
    })
</script>
View source code

Step 3: Add the Snippet to ClickFunnels

In ClickFunnels 2.0, choose the code location that covers the pages you want to track: (+)

LocationWhere to paste itWhat it covers
SiteSite & Blog → Site Settings → Header CodeThe whole site, including courses, blogs, store pages and every funnel page. Use this for one installation across everything.
FunnelOpen the funnel’s settings → Head CodeEvery page in that funnel. Use this when only selected funnels should be tracked.
PagePage editor → Settings → Show Code → Header Code or Footer Code. On funnel pages, Settings is under the Customize gear icon.Only that page. Use the header for a page-only installation, or the footer for a conversion event after the shared header has loaded PostHog.

Install the base snippet once per page: choose the site-level field or the funnel-level field, rather than both. Before adding it, check page fields and any tag manager for an existing installation. Conversion-event code can remain on an individual page; it should call posthog.capture(), without initializing PostHog again.

Paste the full snippet, including its <script> tags, then save your changes.

ClickFunnels Classic (legacy)

In our Classic setup, we opened the funnel’s Settings, scrolled to Head Tracking Code, pasted the snippet there and saved the settings.

ClickFunnels Classic funnel settings showing the Head Tracking Code field
Pasting the PostHog Snippet into ClickFunnels Head Tracking Code Section — ClickFunnels Classic, from the setup guide first published in 2025.

Step 4: Repeat the Setup for Other Funnels or Domains

A funnel can span several pages and hostnames, so check the full path a visitor takes.

Confirm PostHog is present on every step you want to analyze, including checkout and thank-you pages. Add the snippet to any separately managed funnel or site that the chosen setting does not cover.

For example, follow a visitor through:

offer.example.com → checkout.example.com → thanks.example.com

If PostHog runs only on the landing page, you will see arrivals but not what happens on the later steps. You cannot tell where visitors left or whether they completed the funnel.

With the same project and default cookie-based persistence, these subdomains share the visitor cookie. (+) The cross_subdomain_cookie setting defaults to true, so the cookie is set on the parent domain, example.com. (+) A tab opened earlier on one subdomain can keep an older identity in its own localStorage; the snippet’s cookieWinsOnConflict: true makes the shared cookie win and keeps open tabs in sync. (+)

A step on a different domain that you also run, such as checkout.example.net, starts with a separate visitor ID unless you connect the visits. That takes two code changes: on the first site, add the visitor’s posthog.get_distinct_id() and posthog.get_session_id() to the link that leads to the second site; on the second site, read them and pass them as bootstrap: { distinctID, sessionID } in its PostHog initialization, before its first event. For anonymous visitors, call identify() with a known user ID, or createPersonProfile(), before leaving the first site so the initial person properties survive the handoff.

If you cannot add code to an external payment provider’s pages, you cannot observe that part of the journey with the browser snippet. (+) Track the departure on your own site and confirm the payment from a server-side source, as described in Step 8.

Step 5: Connect Consent and Test the Live Funnel

If capture waits for consent, connect your consent tool to PostHog’s opt-in and opt-out controls. (+) The example snippet above loads the SDK with capture opted out and analytics storage off. Pasting it into a ClickFunnels tracking field does not connect it to a banner.

Call this function when your consent tool reports a change to analytics permission. Apply the saved choice on each page too. You need to connect the function to your tool; ClickFunnels does not call it automatically.

window.analyticsConsent = new Promise(resolve => {
  window.allowAnalytics = resolve;
});

function onAnalyticsConsentChange(hasConsented) {
  if (hasConsented) {
    posthog.opt_in_capturing({ captureEventName: false });
    window.allowAnalytics();
  } else {
    posthog.opt_out_capturing();
  }
}
View source code

Place it in the same head tracking code, after the snippet. captureEventName: false stops each call from sending a $opt_in event, which would otherwise repeat on every page; to count acceptances, capture your own event from the banner’s accept button. (+) window.analyticsConsent stays pending until capture is allowed; our ClickFunnels A/B testing guide uses it to hold a page experiment until then. If your consent tool blocks scripts until consent, keep the first three lines in a head script it does not block, and call window.allowAnalytics() right after posthog.init in the blocked code.

Sharing a visitor cookie does not automatically share consent. The SDK stores its consent choice in localStorage by default, separately on each origin. If one choice should cover your sibling subdomains, add opt_out_capturing_persistence_type: 'cookie' to the same initialization on each subdomain. (+) Keep the same project token and cross-subdomain cookies enabled. Your consent tool’s own saved choice still needs to work across those hosts.

Test the actual public URLs, not just the editor preview.

  1. Open the landing page in a clean browser profile and accept analytics consent. A $pageview should arrive right away.
  2. Move through the funnel like a visitor. Submit a test form and visit the thank-you or next-step page. Reload a page to check that the saved choice holds.
  3. Repeat in a fresh consent state with analytics rejected: no capture events or recordings are sent, and no analytics identifiers are stored. Only the consent choice remains, under __ph_opt_in_out_ followed by your project token. Then accept and withdraw: capture stops, and PostHog’s cookies and localStorage data are removed. The consent choice and a per-tab window ID in sessionStorage remain until the tab closes.

Step 6: Verify Events in PostHog

Open Activity in the same PostHog project and find your recent test visit. Open its events and check:

  • A $pageview appears when you load the funnel step.
  • The event URL matches the ClickFunnels page you visited.
  • Moving to the next funnel step creates another pageview.
  • Each page load produces one pageview, without duplicates.

On both sides of each hostname change, use the browser console to compare the visitor and session IDs:

({
  distinctId: posthog.get_distinct_id(),
  sessionId: posthog.get_session_id()
})
View source code

For one continuous visit, the IDs should stay the same across the hostname change. If your code calls identify() with a known customer ID, check that the earlier and later events belong to the same person. Repeat in a browser that has visited the funnel before, as well as a fresh one.

Step 7: Create a Simple Funnel in PostHog

Open Product analytics, create a new insight and choose Funnel. (+) Start with the simplest path that ends in a completed action.

For a lead generation funnel, start with:

Pageview: landing page
Pageview: thank-you page

For each $pageview step, filter $current_url to that step’s page with a “contains” or regex match on its path, so campaign and preview parameters in the URL still match. (+) Two unfiltered pageview steps could count any two pages.

A thank-you pageview can be a starting point if visitors reach it after a successful submission. It counts arrivals at that page, so direct visits and reloads can inflate the total. It does not confirm a payment.

For a sales funnel, start with:

Pageview: sales page
Pageview: checkout
purchase_completed

Add purchase_completed only after the paid-conversion setup below is working. Keep optional clicks out of the main funnel unless they are required to complete it.

Step 8: Add Custom Events for Important Actions

Use autocapture to explore interactions, then capture named events for the actions your reports need. (+) Keep their names the same in your code and tracking plan: lead_form_submitted for a confirmed lead and purchase_completed for a confirmed payment.

A confirmed lead

For a thank-you step reached after a successful form submission, put this starter event in that page’s Settings → Show Code → Footer Code. Keep the shared installation snippet in the header.

<script>
  if (window.posthog) {
    posthog.capture('lead_form_submitted', {
      funnel_name: 'main_offer_funnel',
      funnel_step: 'thank_you'
    });
  }
</script>
View source code

Send this event only after the submission has succeeded. A thank-you page is often a better place than the submit button: a click can still lead to a validation error or failed submission.

This still fires on a page load: refreshing or revisiting the thank-you page repeats it. For exact lead counts, capture from a verified form-success handler or server confirmation and deduplicate by the lead’s submission ID.

A confirmed payment

Use the order-confirmation page’s Footer Code only if your integration exposes verified order data there. Otherwise, send purchase_completed with the actual value and currency from your own server endpoint, after it verifies the webhook’s signature. (+)

If your ClickFunnels plan includes webhooks, open Workspace Settings → Webhooks → Add New Endpoint, choose V2 and subscribe to orders/invoice.paid. (+) It fires when a one-time or subscription invoice becomes paid, including renewals. Separate initial purchases from renewals if they are different outcomes in your tracking plan.

Carry the browser’s posthog.get_distinct_id() into your integration’s contact or order record before checkout, then send it as the server event’s distinct_id. (+) That mapping is part of the integration: receiving a paid webhook alone does not connect it to the browser visit. Carry the analytics-consent decision with that mapping and apply it before sending server events; the browser’s opt-out applies to the current device. (+)

Include a numeric value, an ISO currency code such as USD, and the invoice ID. If you use Revenue analytics, map the revenue and currency properties and match its amount-unit setting to the units you send. Deduplicate webhook retries by invoice ID in your integration, and choose one authoritative capture source for each paid conversion. After a test purchase, check Activity for exactly one purchase_completed with its invoice ID. Then resend that request, body and signature headers unchanged, within the ten minutes before its signature expires. (+) Your endpoint should answer with a success status and recognize the invoice as already processed, and Activity should still show one.

Troubleshooting: PostHog Is Not Showing Events

If no events appear in PostHog, check these items in order.

  1. Confirm the code was saved in the site, funnel or page field that covers this URL.
  2. Compare the project token and API host with the intended PostHog project.
  3. Check the browser console for JavaScript errors. In the network tab, filter for your API host and inspect failed requests; a Content Security Policy can block the SDK or its requests. (+)
  4. Disable ad blockers for the test or use a clean browser profile. (+)
  5. If posthog.has_opted_out_capturing() returns true, check the consent callback. (+) No events are expected while capture is opted out.

If events arrive but your funnel counts still look wrong, use our PostHog audit to check whether the events belong to the right people, record successful actions and appear in the right reports.

Frequently asked questions

Will PostHog track button clicks automatically?

Yes, when interaction autocapture is enabled. Clicks, form submissions and changes are captured automatically, but form field values are not captured by default. (+) A captured click or submit interaction does not establish that the action succeeded.

Will PostHog slow down my funnel?

It adds JavaScript and network requests. The loader runs asynchronously; the SDK still executes in the browser. (+) Compare the published funnel’s load and interaction performance before and after installation, particularly on mobile.

What to Do After Installation

Once you can follow a visitor from the landing page to a completed action, use our ClickFunnels A/B testing guide to measure changes to that path. If tracking waits for consent, start the experiment after consent and check that its exposure event arrives.

To send confirmed funnel conversions to your ad platforms, follow the Meta Ads destination guide or the Google Ads destination guide.

Before using the reports, filter your own test visits out of the analysis.

About the author

Amin Heshmati

Amin Heshmati

Amin Heshmati is an author at 99Ways focused on PostHog, conversion optimization, experimentation, attribution, and analytics implementation. He writes about building reliable measurement systems and using data to improve digital performance.

More articles

Where is growth getting stuck?

Bring us the funnel, the evidence, and the uncertainty. We’ll help you find the leverage worth testing.

Start the conversation