Goal

Problem: IP is only available on event properties (e.g. $ip on $pageview), but you need it on the person profile so:

  • You can send it to Meta / Google Ads via PostHog.
  • Free trial / product logic doesn’t break because later events differ.

Solution: Use a Custom Transformation in PostHog’s Data pipeline to copy ‘$ip’ from the event into:

  • $set.$ip → current IP on the person.
  • $set_once.$initial_ip → first seen IP on the person.

Applied only to real pageviews with a client IP (not backend/server events).

1. Check that your events have $ip

You need events that already contain the IP in their properties.

  1. Go to Data → Events in PostHog.
  2. Open a recent $pageview (or your page view event).
  3. Confirm you see something like: { “event”: “$pageview”, “properties”: { “$ip”: “123.45.67.89”, … } }

If $ip isn’t there, fix your tracking / proxy first. The transformation can’t invent the IP.

2. Create a Custom Transformation

  1. In PostHog, go to Data → Data pipeline → Custom transformations.
  2. Click “New transformation”.
  3. Give it a name like:
    “Set IP on person profile from pageviews”.

3. Configure when the transformation runs

Inside the transformation config:

  1. Event matcher
    • Set event matcher to your pageview event, e.g.
      • Event name: $pageview (or your specific event).
  2. Filter to only events where IP exists
    • Add a filter:
      • Property: IP address (this is $ip)
      • Condition: is set / is not empty

This ensures:

  • It only runs on front-end pageviews with a real user IP.
  • It doesn’t touch backend events that contain the server’s IP.

4. Add the transformation code

In the Source code section, choose JavaScript/TypeScript and use this:

What this does:

  • Reads the IP from event.properties.$ip.
  • Ensures $set and $set_once objects exist on the event.
  • Writes to:
    • person.$ip (updatable current IP).
    • person.$initial_ip (first IP, never overwritten because of $set_once).

5. Enable the transformation

  1. Click Save.
  2. Toggle the transformation On (enabled).
  3. Make sure it’s attached to the correct source (where your pageviews come from) and destination (PostHog itself).

6. Test it

  1. Open your website in an incognito/private window.
  2. Trigger a $pageview (just load the page).
  3. In PostHog:
    • Go to Activity / Live events and find your $pageview.
    • Inspect the event payload – you should see: “properties”: { “$ip”: “123.45.67.89”, “$set”: { “$ip”: “123.45.67.89” }, “$set_once”: { “$initial_ip”: “123.45.67.89” }, … }
  4. Open the Person created for that event.
    • You should now see person properties:
      • $ip
      • $initial_ip

If you do, the sync to Meta / Google Ads can now use these person properties.

7. Notes / gotchas

  • Existing users: This only affects events processed after the transformation is enabled. Old events won’t retroactively set IP.
  • Backend events: As long as you kept the filter IP address is set and event matcher to $pageview, you won’t pollute profiles with your server’s IP.
  • Changing IPs: $ip will update over time as users move networks. $initial_ip stays as the first captured IP because of $set_once.

That’s it. With this transformation active, every qualifying pageview automatically propagates the IP onto the person profile, and you avoid breaking your trial / product tracking logic.

Frequently asked qusetions

Person properties are updated after an event is processed.
If the first event didn’t pass an IP or your backend replaced it with the server IP, then the person profile never receives the correct one. Event properties are the source of truth; that’s why you extract the $ip field from the event payload during transformation.

Yes. They serve different purposes:

  • $ip: The most recent IP. Useful for ad platforms and fraud checks.

  • $initial_ip: The user’s first IP. Helpful for attribution and identity resolution.

Using $set_once ensures you don’t overwrite the first IP even if the user changes networks.

Yes. Both Meta CAPI and Google Enhanced Conversions benefit from accurate IP data.
If you send events without real client IPs:

  • Google may drop conversions.

  • Meta will downgrade match quality.

  • Attribution accuracy falls off a cliff.

Once the IP is stored on the person, PostHog’s CAPI integrations can reliably send it for all future conversions.

Two safeguards are essential:

  1. Event matcher: Only run the transformation on $pageview or whichever front-end event you trust.

  2. IP is set filter: Backend events often overwrite $ip with the server’s IP; filtering prevents this.

If you skip this, every user may end up with your backend’s IP, which breaks matching and attribution.

No. Transformations only apply to new incoming events.
If you want to enrich old profiles, you must re-ingest past events or manually backfill person properties.

This doesn’t break anything. The IP is just one matching factor. Meta/Google combine it with:

  • Email

  • Phone

  • Click IDs

  • Browser signals

  • User agent

You don’t risk “merging” users based solely on shared IP.

Yes, but only if the IP is preserved correctly.
For SSR setups, ensure:

  • The proxy forwards X-Forwarded-For.

  • PostHog’s ingestion endpoint uses the forwarded IP instead of the server IP.

If the event arrives without a real client IP, the transformation can’t fix it.

It depends on your basis for processing:

  • If you’re using legitimate interest for analytics & marketing, storing IP may be justified.

  • If you require consent, you must only set IP after the user accepts tracking.

PostHog gives you the mechanism; you are responsible for the compliance posture.

Yes. The same pattern applies to any event property you want to persist:

  • UTM parameters

  • Landing URL

  • Device info

  • Custom user traits

Just add extra $set / $set_once keys inside the transformation code.

Related blog posts

Leave a Reply

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