Meta (Facebook & Instagram)
Feed valued conversions to the Meta Conversions API first-party with @hogsend/plugin-meta-capi — deterministic dedup, reconstructed fbc, CRM-grade action_source.
Meta's Conversions API (CAPI) receives conversions server-side, so you
keep attribution even when the browser Pixel is blocked by ad blockers, iOS,
or cookie restrictions. @hogsend/plugin-meta-capi is Hogsend's native
destination for it: every fired conversion point with
destinations: ["meta-capi"] becomes one CAPI event, delivered by the
engine's durable dispatch with retries and deterministic dedup.
What you need
| Credential | Where to get it |
|---|---|
| Pixel ID (Dataset ID) | Events Manager → your dataset → Settings. Use the same Pixel ID your browser Pixel uses so server + browser events reconcile. |
| Access Token | Events Manager → your dataset → Settings → Conversions API → Generate access token. Self-serve — no App Review or Meta app needed. Copy it immediately (it's shown once). |
Setup
import { createMetaCapiDestination } from "@hogsend/plugin-meta-capi";
const client = createHogsendClient({
conversions,
conversionDestinations: [
createMetaCapiDestination({
pixelId: process.env.META_PIXEL_ID!,
accessToken: process.env.META_CAPI_TOKEN!,
// eventNames: { "deal-sold": "Purchase", "lead-submitted": "Lead" },
// testEventCode: "TEST1234", // while validating — remove for live
}),
],
});Then point conversion points at it: destinations: ["meta-capi"] on each
defineConversion that should reach Meta.
What the plugin sends
One server event per fired conversion:
| CAPI field | Value | Why it matters |
|---|---|---|
event_name | Per-definition eventNames mapping; else Purchase when the conversion has a value, Lead when it doesn't | Meta optimizes best against standard event names |
event_id | The engine's deterministic dedup id (sha256(contact:definition:event-row)) | Stable across retries; a browser Pixel sending the same id dedups against it |
event_time | The conversion's occurredAt, Unix seconds | Honest timing, even for backfilled CRM events |
action_source | system_generated | The home for CRM/offline conversions since Meta retired the Offline Conversions API (May 2025) |
user_data.em / ph / external_id | SHA-256 hashed per Meta's normalization (email lowercased/trimmed; phone digits-only) | The top match-quality signals |
user_data.fbc | Reconstructed as fb.1.<click_ts>.<fbclid> from the stored click evidence | The ad click-id — sent plain per spec, never fabricated |
custom_data.value / currency | The conversion's resolved value | What ROAS and value-based bidding read |
fbc is real or absent. The engine recovers the contact's most recent
campaign.arrived touchpoint before the conversion — the fbclid and the
actual click timestamp captured first-party by @hogsend/js. No Meta click
on record → no fbc, because a fabricated one poisons match quality.
Match quality
The two highest-impact matchers are hashed email and fbc. Both are already
flowing if you've wired the front of the loop:
- Run
@hogsend/json your landing pages so ad clicks firecampaign.arrived(capturesfbclidwith its timestamp). - Use lead intake hidden fields so the browser session stitches to the email-anchored contact.
Conversion Leads (optimizing on funnel stages)
Meta's Conversion Leads performance goal optimizes lead campaigns on
what leads become, not just that they arrived. It wants your funnel stages
as CRM events: define one conversion point per stage
(lead-submitted → contacted → quoted → sold), map each to the stage
name you configure in Events Manager's Leads Funnel via eventNames, and
send them all to meta-capi. Meta's stated requirements: roughly 200+ leads
a month and a lead→stage conversion rate between 1–40%. This works for
website-form leads, not just Instant Forms.
Testing
Set testEventCode in the config, fire a conversion (e.g. re-ingest a
deal.sold stage change in a dev environment), and watch it appear in
Events Manager → Test Events. Delivery state is also on your side:
conversion_dispatches records attempts, the platform's fbtrace_id
receipt, and any error. Remove the test code before going live.
Deduplication vs the browser Pixel
If you also run Meta's browser Pixel for the same conversion, have the Pixel
send the same event_id — Meta counts each conversion once (within ~48h,
matching on event_name + event_id). If CAPI via Hogsend is the only
source for that conversion (typical for CRM-stage events — the browser never
sees a deal close), there's nothing to dedupe.
Alternative: via PostHog
PostHog's Meta Ads Conversions destination can forward PostHog-captured
events to CAPI instead — Hogsend's analytics mirror keeps PostHog populated
(including value/currency), so the plumbing exists. Prefer the native
plugin when the conversion carries money or CRM stage evidence: it sends the
engine's deterministic event_id, the recovered first-party click, and
action_source: system_generated — none of which a mirror can reconstruct
as faithfully. The PostHog route remains a fine choice for
browser-context-rich, value-less events, and is the documented path for
Google, TikTok,
LinkedIn, and
Reddit until those get native destinations.
Conversion timing
How long after a journey enrollment or an event your conversions actually fire — median and 90th-percentile time-to-convert, and the share that convert within 1, 7, 14, and 30 days.
Google Ads
Send server-side conversions to Google Ads via PostHog's Google Ads Conversions destination, matching on gclid or hashed email (enhanced conversions).