If you’ve been using PostHog, you might have been wondering if there’s any way that you can send the conversion events (such as AddToCard, Purchase, or InitiateCheckout) with all the contextual information you have from the users (email, first & last name, address, order value & currency, etc.) to ad platforms such as Meta, Google, Pinterest, etc.

In this post, we’re going to cover how to integrate your PostHog conversion tracking with Meta using CAPI (Conversions API), achieving the highest event score possible with all the information you have from the customers in PostHog.

PostHog has a feature called Destinations [PostHog destinations doc] that allows you to send your data to external systems in real time. ‘Meta Ads Conversions’ is a PostHog default destination that you should use to send conversion events to Meta Ads.

Requirements

We assume you are already have PostHog setup and tracking the events you intend to send to Meta Ads. If not, go to installation guides in our PostHog articles, OR reach out to us to schedule a free consultation call.

Begin Here

To start, navigate to ‘Data management’ and then to ‘Destinations’. Here, you can find your required destination by selecting ‘Meta Ads Conversions’. It’s worth noting that for each event type, you’re going to need a new Meta destination in PostHog. (This is not the case for Google and other ad platforms in PostHog, where they can handle all event types in a single destination.)

First, you should set the filters and triggers to select the PostHog events that should be sent to Meta. For instance you have an action name Order Completed that should be sent as a Purchase event. This is basically the mapping step.

After filters and triggers, you’ll find event parameter fields. PostHog has set a decent default values there already. Some of the fields you see are common among all event types, but some need to be added/set depending on the event type. We’ll cover both in-depth:

Common fields

Event data

These are the fields you’re going to need to fill in for every event, regardless of what event it is.

Access Token: It’s your authentication token. You can generate it from the Event Source settings in Meta.

Pixel ID: Specifies which pixel you want this event sent to. Found in your Meta Business Manager under your website pixel settings.

Event name: Which is the name of the event. (Case sensitive!)

Event ID: This is perhaps the most important field, as it directly determines how effective your deduplication process will be. Make sure it’s populated with a value that remains consistent every time the event runs. For example, for a Purchase event, use the Order ID; for bookings, use the Booking ID… or any similar variable that is generated only once per transaction.

Event source URL: The URL of the page where the event occurred (if it’s a web event, not for renewal transactions for instance). PostHog already prefills it with {event.properties.$current_url}.

Event time: The time when the event happened. If you have your own timestamp recording process, use it. Otherwise, just leave it as {toInt(toUnixTimestamp(event.timestamp))}.

Action source: Where the conversion occurred. Most of the time, simply “Website – Conversion was made on your website.”

User data

This is perhaps the second most important field, as it directly determines the quality of your Meta CAPI event and the fate of your campaign optimizations.

Fields that require hashing:

All of the following parameters need to be converted to lowercase, trimmed of whitespace, and hashed with SHA-256 before being sent.

em: Email address. If you set this property, you can catch it with this {sha256Hex(lower(person.properties.email))}.

ph: Phone number (digits only, including country code). Same as above, and it will be {sha256Hex(replaceRegexpAll(person.properties.phone, '[^0-9]', ''))}.

fn: First name. Same as above, it will be {sha256Hex(lower(person.properties.first_name))}.

ln: Last name. Same as above, it will be {sha256Hex(lower(person.properties.last_name))}.

ge: Gender. Same as above, it will be {sha256Hex(lower(person.properties.gender))}.

db: Date of birth. {sha256Hex(replaceAll(person.properties.date_of_birth, '-', ''))}.

ct: City. In case you don’t ask it from the user, you have the option to fill it with the city detected from the user’s GeoIP. In that case, it will be {not empty (person.properties.$geoip_city_name) ? sha256Hex(replaceAll(lower(person.properties.$geoip_city_name), ' ', '')) : null}.

st: State. Same as above, it will be {not empty (person.properties.$geoip_subdivision_1_name) ? sha256Hex(replaceAll(lower(person.properties.$geoip_subdivision_1_name), ' ', '')) : null}.

zp: Zip/Postal code. Same as ‘ct’. It will be {not empty (person.properties.$geoip_postal_code) ? sha256Hex(replaceAll(lower(person.properties.$geoip_postal_code), ' ', '')) : null}.

country: Country code. Same as ‘ct’. It will be {sha256(lower(person.properties.$geoip_country_code))}.

Fields that do not require hashing:

client_ip_address: This is usually {event.properties.$ip}, but make sure IP capture is enabled on your end. Also, confirm that this is the client’s IP address, not the server IP from which the backend event is being sent. In case you’re using external integrations like RevenueCat, you might find it in {event.properties.subscriber_attributes.$ip}.

client_user_agent: This may sound like gibberish, but it can hugely impact the event quality. Definitely send it if you have it. It can usually be found at {event.properties.$raw_user_agent}. But again, make sure it’s not the server user agent from which the backend event is being sent.

fbc: It is Meta’s way of preserving ad click context once a user lands on your website. It is derived from the fbclid query parameter that Meta automatically appends to your URL when someone clicks a Meta ad. Its purpose is to help Meta match downstream conversion events (Purchase, Lead, etc.) back to the specific ad, ad set, and campaign that generated the click. This significantly improves event match quality and attribution accuracy, especially for server-side events sent through the Conversions API.
Use the following expression to build it:
{not empty(person.properties.fbclid ?? person.properties.$initial_fbclid) ? f'fb.1.{toUnixTimestampMilli(now())}.{person.properties.fbclid ?? person.properties.$initial_fbclid}' : ''}.
This expression checks whether a valid fbclid value exists on the user (either the current fbclid or the initially captured $initial_fbclid). if one exists, it constructs the required fbc string in Meta’s expected format:

  • Prefix: fb.1. (static identifier required by Meta)
  • Timestamp: current Unix timestamp in milliseconds
  • Click ID: the available fbclid value

If no fbclid is available, the expression returns an empty string, meaning no fbc value will be sent.

If you don’t see any of these fields defined by default, you can add as many as you want by clicking on ‘Add entry’.

Event-specific fields:

Besides those common fields among all events, you may also need some extra fields depending on the event type. We’ll cover the most important ones based on our experience. For a full list of standard events and their parameters, visit Meta for Developers site. You can quickly add any parameters you want after this article.

value:
May come with: AddPaymentInfo, AddToCart, AddToWishlist, CompleteRegistration, InitiateCheckout, Lead, Search, StartTrial, Subscribe, and ViewContent.
It’s the value associated with this event. (e.g. {event.properties.price})

currency:
May come with: AddPaymentInfo, AddToCart, AddToWishlist, CompleteRegistration, InitiateCheckout, Lead, Purchase, Search, Subscribe, and ViewContent.
It shows the currency for the value specified. (e.g. {event.properties.currency})

The currency and value fields always come together, and they are required properties for the Purchase event.

predicted_ltv:
May come with: StartTrial and Subscribe.
It’s the advertiser’s estimate of how much total revenue a subscriber will generate over their entire lifetime..

content_ids:
May come with: AddPaymentInfo, AddToCart, AddToWishlist, InitiateCheckout, Purchase, Search, and ViewContent.
The product IDs associated with the event, such as SKUs or article IDs.

content_type:
May come with: AddToCart, Purchase, Search, and ViewContent.
It’s either ‘product’ or ‘product_group’ based on the ‘content_ids’ or ‘contents’ being passed.
If the IDs being passed are IDs of ‘products’, then the value should be product. If product group IDs are being passed, then the value should be ‘product_group’.

contents:
May come with: AddPaymentInfo, AddToCart, AddToWishlist, InitiateCheckout, Purchase, Search, and ViewContent.
It’s an array of JSON objects that contains the quantity and the International Article Number (EAN), when applicable, or other product or content identifier(s). id and quantity are the required fields.

num_items:
May come with: InitiateCheckout and Purchase.
It’s the number of items when checkout was initiated. (e.g. {length(event.properties.products)})

search_string:
May come with: Search.
It includes the string entered by the user for the search.

Also, you can find the object properties that Meta needs you to send for any event through this link.

Important Note: Use test code

While testing, always use the Test Event Code that Meta provides so you can preview the data you’re sending. Get it from your Event Source settings. When you’ve finalized the setup, just delete it, and Meta will count all incoming events as real ones.

Final Word

I hope you found this article from 99Ways helpful. Everything shared here comes from hands-on experience implementing PostHog across dozens of businesses with similar growth and optimization goals.
If you’re looking for a full-service PostHog setup that goes beyond technical implementation, and is aligned with your actual business outcomes, we’d be happy to connect.
Feel free to fill out the Contact Us form to book a free consultation call with our co-founder & CEO, Iman Nazari.

Frequently asked qusetions

Event match quality is Meta’s measure of how confidently it can match a conversion event to a real user.

Higher match quality leads to:

  • Better attribution

  • More accurate reporting

  • Stronger algorithm optimization

PostHog improves this by sending rich user data (hashed email, phone, location, etc.), which increases the likelihood of a successful match.

Yes. Best practice is to use both Pixel (client-side) and CAPI (server-side) together.

  • Pixel captures real-time browser activity

  • CAPI ensures reliability and data completeness

The key is proper deduplication using Event ID, so Meta does not double-count events.

Deduplication ensures that the same event sent from multiple sources (Pixel + CAPI) is counted only once.

The Event ID is the anchor for this process. If it’s inconsistent or missing:

  • Meta may double count conversions

  • Or fail to match events properly

Using stable identifiers like Order ID or Booking ID is essential.

Focus on high-value, optimization-relevant events, such as:

  • Purchase

  • InitiateCheckout

  • AddToCart

  • Lead

  • CompleteRegistration

Sending too many low-signal events can dilute optimization. Prioritize events that directly correlate with revenue or meaningful intent.

Yes. PostHog allows you to map any custom event to Meta’s standard or custom events.

For example:

  • “Order Completed” → Purchase

  • “Booked Call” → Lead

The key is aligning your internal event structure with Meta’s expected event taxonomy.

The highest-impact fields are:

  • Email (em)

  • Phone (ph)

  • First & last name (fn, ln)

  • Location (city, state, country)

  • IP address and user agent

These directly affect match quality and attribution accuracy, especially when hashed correctly using SHA-256.

Yes, for specific fields like email, phone, and name, Meta requires SHA-256 hashing before sending.

This ensures:

  • Privacy compliance

  • Standardized data formatting

  • Proper matching within Meta’s systems

PostHog expressions allow you to handle this directly in the destination setup.

The fbc (Facebook Click ID) preserves the original ad click context.

It connects a conversion event back to:

  • The exact ad

  • Ad set

  • Campaign

Without fbc, Meta may struggle to attribute conversions accurately, especially for server-side events.

If user data is missing or incomplete:

  • Event match quality drops

  • Attribution becomes weaker

  • Meta’s optimization performance declines

In extreme cases, campaigns may optimize based on partial or misleading signals, leading to inefficient ad spend.

Yes. You can create separate destinations per event and per pixel, allowing:

  • Multi-brand setups

  • Multi-domain tracking

  • Segmented attribution strategies

This is especially useful for agencies or businesses operating multiple offers.

By sending clean, enriched, and deduplicated events, PostHog helps:

  • Meta optimize toward actual revenue, not just tracked conversions

  • Reduce signal loss from browser limitations

  • Improve ROAS over time

The impact is usually seen in better scaling stability and more reliable decision-making.

Setup is only the foundation. Over time, you need to:

  • Audit event mappings

  • Update parameters as funnels evolve

  • Ensure new flows are tracked correctly

Without this, tracking gradually becomes misaligned with the business.

Yes. PostHog can capture events from:

  • Shopify

  • ClickFunnels

  • GoHighLevel

  • Custom-built stacks

As long as events are tracked in PostHog, they can be mapped and sent to Meta via CAPI.

Based on real implementations, the most frequent issues are:

  • Missing or inconsistent Event IDs

  • Incorrect hashing of user data

  • Not sending enough user attributes

  • Wrong event mapping (e.g., Purchase vs Lead)

  • Ignoring fbc and attribution context

These directly reduce match quality and tracking reliability.

Use Meta’s Test Event Code from Events Manager.

This allows you to:

  • Preview incoming events in real time

  • Validate parameters and structure

  • Identify missing fields or formatting issues

Always test before going live.

No. PostHog complements them.

  • GA4 → general analytics

  • GTM → tag management

  • PostHog → product analytics + event pipeline

For CAPI specifically, PostHog acts as a data source feeding enriched events into Meta.

Yes. PostHog tracks user behavior across sessions and events, allowing you to:

  • Capture delayed purchases

  • Track multi-step funnels

  • Attribute conversions beyond a single session

This is critical for high-ticket or longer buying cycles.

PostHog and Meta both provide frameworks for compliance, but implementation matters.

Key requirements:

  • User consent (cookie banners, tracking consent)

  • Proper hashing of personal data

  • Transparent data usage policies

Compliance depends on how you implement, not just the tools.

You should consider hiring an expert if:

  • You’re spending significant budget on ads

  • Your funnels are multi-step or cross-platform

  • You rely on accurate attribution for scaling decisions

A proper setup directly impacts data quality, optimization performance, and revenue decisions, so mistakes can be expensive.

Related blog posts

One Comment

  1. I used this setup to connect Purchase events from PostHog to Google, but noticed Google was flagging them with a “Needs attention” warning:
    “You are only importing events that have both user-provided data (eg, email, phone number) and Google identifiers like GCLID. To get the maximum measurement and performance benefits, Import all of your events that have user-provided data, even those without GCLID.”

    The fix is straightforward. Instead of filtering only for events that include a GCLID, update your SQL filter to:
    “person.properties.gclid IS NOT NULL
    OR person.properties.email IS NOT NULL”

    This allows Google to use either GCLID or user-provided data for matching, improving event coverage and performance.

Leave a Reply

Your email address will not be published. Required fields are marked *