Conversions, Pixels & Ad Platforms
Forward server-side conversion events from Hogsend to Meta, Google, TikTok, LinkedIn, and Reddit — using PostHog's Destinations pipeline. With a walkthrough.
The short version
You don't wire ad-platform conversion APIs into Hogsend. You let PostHog do it.
Hogsend's job is to decide when a conversion happened and fire a clean, well-named event. PostHog's Destinations pipeline (its CDP) is the part that forwards that event server-side to Meta's Conversions API, Google Ads, TikTok Events API, LinkedIn, Reddit, and more — with the SHA-256 hashing, click-id reconstruction, deduplication, retries, and rate-limit handling all maintained by PostHog.
This is the outbound mirror of PostHog Webhook Setup. That guide forwards PostHog events into Hogsend to trigger journeys. This one forwards events out of PostHog to ad platforms. Same pipeline, opposite direction.
Why PostHog, not a built-in destination
Hogsend ships a defineDestination() authoring layer, and it's tempting to write a destination that POSTs to Meta's CAPI. We deliberately don't ship one, because the hard part of conversion tracking is identity, and PostHog is structurally better positioned to solve it:
- Click-ids live in the browser. Meta's
fbc/fbp, Google'sgclid, TikTok'sttclid, Reddit'srdt_cid, LinkedIn'sli_fat_id— these are set when someone clicks an ad and land on your site. PostHog's browser SDK captures them automatically as person properties ($initial_fbclid,gclid, …). A server-side engine like Hogsend never sees them. - PostHog does the hashing and reconstruction for you. In the destination config, the user-data fields come pre-filled — e.g.
{sha256Hex(lower(person.properties.email))}and anfbcvalue rebuilt fromfbclid/$initial_fbclid. You don't write hashing code. - It's maintained against five moving API targets. Conversion APIs change constantly. That's PostHog's problem to keep current, not yours.
Rebuilding this inside the engine would be a maintenance treadmill that produces worse match quality. So the engine stays focused on orchestration, and PostHog is the conversion data plane. (Outbound destinations are for fanning the lifecycle event stream out to product and data tools — PostHog, Segment, Slack, a warehouse — not for ad-platform CAPI. The line is deliberate.)
These destinations are currently marked Experimental in PostHog. They work well for most cases, but PostHog notes there may be edge cases without official support. Validate with each platform's test/debug tooling before trusting the numbers (see each platform page).
How identity survives a server-side conversion
A fair question: if Hogsend fires a conversion server-side (e.g. subscription_created from a journey), how does it get matched to an ad click that happened in the browser weeks earlier?
Because the ad destinations read their identifiers from person.properties, not from the event. PostHog maintains person properties across every event for that distinct_id — client and server. So as long as the person was seen in the browser at least once (which set $initial_fbclid, gclid, etc.) and was identified with an email, a later server-side conversion event still resolves the hashed email and click-id from the stored person profile.
Ad click (browser) → PostHog person gets $initial_fbclid, gclid, email
… days later …
Conversion detected → capture the conversion event into PostHog for that user
(server or browser) (from your app's PostHog SDK)
→ PostHog Destination reads person.properties → forwards to Meta/Google/…Practical implication: make sure two things are true in PostHog before relying on this:
- Your website runs the PostHog browser snippet so click-ids get captured on landing.
- People are
identify()-ed with theiremailearly (typically on signup), so the hashed-email matcher is populated.
Event-level fields like $current_url and $raw_user_agent won't be present on purely server-side events — that's fine, they're best-effort. The high-value matchers (hashed email + click-id) come from the person profile.
Firing the conversion event into PostHog
The ad-platform matcher needs the conversion event on the user's PostHog timeline (so it can read the person's hashed email + click-ids). Capture it from your app's PostHog SDK — where you actually detect the conversion — not from a journey. Hogsend's journey context has no PostHog-capture call (the ctx.posthog.capture / ctx.identify shims were removed), and the outbound destinations spine fans out the lifecycle catalog (contact.*, email.*, journey.completed, bucket.*) — not arbitrary journey-fired custom events — so it isn't the path for a one-off conversion event either.
// In your app, where the conversion happens (server-side example):
import { PostHog } from "posthog-node";
const posthog = new PostHog(process.env.POSTHOG_API_KEY!, { host: "https://us.i.posthog.com" });
posthog.capture({
distinctId: user.id, // the same distinct_id PostHog knows in the browser
event: "purchase",
properties: { price: 49, currency: "USD" },
});Any ad destination whose event matcher includes purchase then forwards it. Firing conversions directly from your app's PostHog browser SDK carries the richest browser context and is ideal for top-of-funnel events like Lead; capture server-detected conversions (renewals, offline events) from posthog-node as above.
Conversion events are often best captured client-side in your app (where browser context is richest), and lifecycle/back-office conversions (renewals, upgrades detected server-side, offline events) captured server-side from your app via posthog-node. Use whichever source actually knows the conversion happened.
The setup, end to end (Meta as the example)
Every ad destination follows the same flow. Here's adding Meta Ads Conversions:

Step 1: Open Data → Destinations
In PostHog, go to Data in the left sidebar → Destinations. Scroll to Create a new destination.
Step 2: Search for the platform
Type the platform name (e.g. Meta, or ads to see them all). You'll find Meta Ads Conversions, Google Ads Conversions, TikTok Ads Conversions, LinkedIn Ads Conversions, and Reddit Conversions API. Click + Create.
Step 3: Add credentials
Each destination needs platform credentials (an access token / API key and an account or pixel ID). The config form links straight to the platform's docs for obtaining them. See the per-platform pages below for exactly what to get and where.
Step 4: Map the event (mostly pre-filled)
PostHog pre-fills the field mappings. For Meta that's:
| Field | Default mapping | What it does |
|---|---|---|
| Event name | {event.event} | Your PostHog event name → platform event |
| Event ID | {event.uuid} | The dedup key (matches the browser pixel) |
| Event time | {toInt(toUnixTimestamp(event.timestamp))} | When it happened (Unix seconds, GMT) |
| User data → em | {sha256Hex(lower(person.properties.email))} | Hashed email (matching) |
| User data → fbc | rebuilt from fbclid / $initial_fbclid | The click-id (matching) |
| Custom data | price, currency | Conversion value for ROAS |
You usually only change the event matchers (which events to forward) and the value/currency properties. The hashing and click-id logic is done for you.
Step 5: Add event matchers
Under Match events and actions, click + Add event matcher and pick the specific conversion events to forward (e.g. purchase, subscription_created, Lead). Don't forward everything — you only want real conversions reaching ad platforms, not $pageview.
Step 6: Create & enable
Toggle Filter out internal and test users on for production, then click Create & enable. Test with the platform's debug tool (Meta Test Events, TikTok Test Event Code, etc.) before relying on reported conversions.
Deduplication (read this once, applies everywhere)
If you fire the same conversion both client-side (the platform's browser pixel) and server-side (via PostHog), each platform de-dupes them using a shared event id — but only if both sides send the same id:
- Meta matches on
event_id(within ~48h). - TikTok, LinkedIn, Reddit use the same event-id model.
- Google de-dupes on the order/transaction id (or relies on gclid).
PostHog defaults the server-side id to {event.uuid}. To actually de-dupe against your browser pixel, the pixel must send that same id. If you only fire conversions through PostHog (no separate browser pixel), there's nothing to de-dupe and you can ignore this.
Per-platform setup
Events & contacts
Identify and track with hs.contacts.upsert and hs.events.send. The identify/track patterns, and the contactProperties vs eventProperties split that makes them work.
Meta (Facebook & Instagram)
Forward server-side conversions to the Meta Conversions API via PostHog's Meta Ads Conversions destination.