Meta Pixel setup (Graphene FM)
What this enables: paid Meta ads (Facebook + Instagram) optimizing
against graphene.fm traffic, and an audience cohort built from
graphene.fm visitors only — not mixed with hivejournal.com's
journaling audience.
The codebase already has the Meta Pixel component wired
(apps/frontend/src/components/analytics/FacebookPixel.tsx).
It's gated on two env vars; setup here is the operator workflow
for getting the pixel ID + flipping the gates.
1. Get a pixel ID from Meta
Skip the platform-picker in the ad-creation dialog (Magento / Shopify / etc.) — graphene.fm is custom Next.js, none apply. Use Events Manager directly:
- https://business.facebook.com/events_manager/
- Left sidebar → Data sources
- If a pixel already exists from a prior surface (e.g. the HiveJournal-for-iOS Page from older iterations), you can reuse its 15-digit ID — the hostname gate in step 2 below keeps it Graphene-only at runtime.
- To create a fresh one: top-right + Connect data sources →
Web → name it
Graphene FM+ URLhttps://graphene.fm→ the 15-digit ID lands on the next screen.
When the wizard asks "How do you want to install?", pick
"Manually install pixel code yourself" and IGNORE the snippet
it shows — the install lives in
FacebookPixel.tsx. You only need the ID.
The ID may also appear as "Dataset ID" in newer Meta UIs — same value, just renamed.
2. Set the Vercel env vars
In Vercel → Project → Settings → Environment Variables, add for Production AND Preview:
NEXT_PUBLIC_FB_PIXEL_ID=<the 15-digit ID from step 1>
NEXT_PUBLIC_FB_PIXEL_HOSTNAME=graphene.fm
The HOSTNAME var (added in PR #509)
is what keeps the pixel from firing on hivejournal.com. The same
Vercel deployment serves both hosts; without the hostname gate the
pixel would mix journaling-app traffic into the
audio-fiction-listener cohort and Meta's lookalike-audience
suggestions get worse for both surfaces.
Redeploy. Verify by loading https://graphene.fm and confirming
the Facebook Pixel Helper Chrome extension shows the pixel
firing — then load https://www.hivejournal.com and confirm it
does NOT fire.
3. Pixel-only is enough for the first traffic campaign
For a Traffic-objective ad (optimizing on link clicks), the browser pixel alone is sufficient. Meta will use it for:
- PageView events (auto-fired on every route change)
- Audience-build (graphene.fm visitors → lookalikes)
- Conversion attribution (which clicks led to graphene.fm visits)
Existing event-tracking conventions in
apps/frontend/src/lib/listen-tracking.ts
already wire custom fbq('trackCustom', ...) calls for
listen-progress events; those keep working with the new pixel
once the env vars are set.
4. Conversions API (CAPI) — defer until conversion volume exists
The Meta Ads Manager will offer to also set up the Conversions API (a server-side complement to the browser pixel). It catches ~30-40% of conversions the browser pixel misses (ad-blocker users, iOS 14.5+ ATT opt-outs).
Defer this until week 2-3 of running ads, for two reasons:
- Pixel-only is sufficient for the Traffic objective. CAPI's
value compounds when optimizing for conversion events
(
Purchase,Subscribe) which need a Sales-objective campaign, which needs ~50 conversion events/week before Meta's optimizer converges. At current trajectory (1 paying sub / 28d, per readout #489) the conversion volume to justify CAPI hasn't arrived yet. - CAPI requires backend code to actually fire events. The
Meta UI's CAPI setup wizard only declares intent — the events
it lists (
View Content,Subscribe,Purchase, etc.) need server code that POSTs tohttps://graph.facebook.com/<api_version>/<pixel_id>/eventswith the pixel access token + hashed user data. ~2-3 hours of work; do it once you actually need the iOS-recovered conversion data.
When you DO build CAPI:
- Fire from the existing Stripe webhook (
Subscribe+Purchaseevents) inapps/backend/src/routes/payment.ts - Fire
Complete Registrationfrom the email-confirm route inapps/backend/src/services/graphene-subscribers.ts - Fire
View Contentfrom a backend GET when an unauthenticated visitor lands on/seasons/[id], with the season metadata (uses the existing host-detection already in the page route for OG tag selection) - Mirror the
recordHeartbeatpattern from docs/reference/RECURRING_TASKS.md so a CAPI delivery failure logs toservice_errorsrather than silently dropping events.
Don't build it before there's a conversion-objective ad campaign to optimize against.
Troubleshooting
- Facebook Pixel Helper says "No pixels found" on graphene.fm
→ Check
NEXT_PUBLIC_FB_PIXEL_IDis set in Vercel Production AND Preview, and that you redeployed since adding it. Env vars set after the last deploy don't take effect until the next one. - Pixel fires on hivejournal.com too →
NEXT_PUBLIC_FB_PIXEL_HOSTNAMEisn't set, OR is set to a value that doesn't match (case matters — gate is lowercase-suffix match). Set it tographene.fmexactly. - Events Manager shows "0 events received" after a day →
Walk through the site in an incognito window. The first
PageViewshould appear within ~30s of loading any page.
Related
- Hostname-gated pixel rationale — PR #509 commit message has the full reasoning
— the componentFacebookPixel.tsx— custom event wiringlisten-tracking.ts- Funnel readout #489 — baseline conversion numbers; informs the "defer CAPI" decision