Zum Inhalt springen

Handover: capture Meta fbc / fbp on the www subdomain

For the server-side Meta Conversions API forwarder ([[2026-06-09-meta-ads-conversions-api]]) to match registrations to ad clicks, it needs Meta’s fbc and fbp identifiers. The Rails app does not capture these — it only reads them back from the persisted user. This doc defines exactly what the www subdomain (marketing site) must capture and how it must hand them to the app.

The contract (what the app expects)

The forwarder reads, at UserRegistered time, from the user’s persisted conversion_details (JSONB), populated from the first-party conversion cookie (today: gad, pipe-delimited key=value|key=value, scoped to .dropscan.de):

user.conversion_details["fbc"]   # Meta click identifier, may be nil
user.conversion_details["fbp"]   # Meta browser identifier, may be nil

So the www subdomain must add two keys, named exactly fbc and fbp, into that same conversion cookie, with values in Meta’s exact string format below. Both are optional — the forwarder omits whichever is absent.

Cookie must be set on the parent domain .dropscan.de so the app subdomain can read it at registration (same as the existing gad cookie). Values contain only fb, digits, ., and URL-safe chars — no | or = — so they embed safely in the pipe/equals format.

What to capture

1. fbclid — the raw click ID (the input)

When a user lands from a Meta ad, the URL carries an fbclid query parameter:

https://www.dropscan.de/?fbclid=IwAR0abc123...

Read fbclid from the landing URL’s query string. This is the only thing that actually comes from Meta; fbc is derived from it.

2. fbc — Facebook click identifier (derived from fbclid)

Format (Meta-defined, all four parts required):

fb.<subdomainIndex>.<creationTimeMs>.<fbclid>

Example: fb.1.1717939200000.IwAR0abc123...

Capture rule: on landing, if fbclid is present, build this string and store it in the gad cookie. If a later visit has a new fbclid, overwrite (most-recent click wins). If there is no fbclid, keep the existing fbc (falling back to the Pixel’s _fbc if one is present). Persistence follows the gad cookie’s rolling 30-day window (see Persistence).

Unlike fbp, fbc needs no pre-seeding dance: it is derived deterministically from the URL’s fbclid, so we build it ourselves for the conversion cookie directly on landing. (The Pixel independently derives its own _fbc from the same fbclid; the two agree by construction.) When fbclid is absent there is no fbc to capture.

3. fbp — Facebook browser identifier

Format:

fb.<subdomainIndex>.<creationTimeMs>.<randomNumber>

Example: fb.1.1717939200000.1098115397

The Meta Pixel base code is now installed (consent-gated, in cookie_consent_controller.js#loadMetaPixel), so fbevents.js normally sets _fbp itself. We still generate fbp ourselves, but the crucial detail is timing: write our value to the _fbp cookie before the Pixel script loads. fbevents.js reuses an existing valid _fbp on init rather than overwriting it, so the Pixel adopts our value. The result is a single fbp shared across client-side Pixel events and server-side CAPI events — consistent matching, no divergence.

Why generate at all instead of just reading the Pixel’s cookie: connect.facebook.net/fbevents.js is heavily ad-blocked, and that blocked population is exactly what server-side CAPI exists to recover. A first-party fbp we set ourselves survives ad blockers, so CAPI still has a stable browser identifier even when the Pixel never loads. Pre-seeding also removes any need to read _fbp after the Pixel’s asynchronous write.

Net behavior: Pixel not blocked → it inherits our fbp (client + server agree); Pixel blocked → our first-party fbp still exists for CAPI. One value, one code path, stored stably per browser (see Persistence).

Flow summary

  1. User lands on www.dropscan.de (optionally with ?fbclid=…).
  2. After marketing-cookie consent (see below), the www JS, before loading the Pixel script:
    • reuses or generates fbp (fb.1.<ms>.<rand>) and writes it to the _fbp cookie so the Pixel inherits it,
    • if fbclid present, derives fbc (fb.1.<ms>.<fbclid>) from the URL,
    • writes fbc and fbp into the .dropscan.de gad cookie (rolling 30-day, see Persistence) alongside the existing keys,
    • then loads the Pixel (loadMetaPixel), which adopts the pre-seeded _fbp.
  3. User registers on the app subdomain → Rails reads the conversion cookie into conversion_details (existing path) → fbc/fbp are now persisted.
  4. MetaAdsForwarder reads them on UserRegistered and sends to Meta CAPI.

Persistence

fbc/fbp live in the shared gad cookie, which updateFirstPartyCookie() rewrites on every visit, merging existing keys forward. So the handoff uses a rolling 30-day window: the value survives as long as the visitor returns to www at least once every 30 days. “Refresh” means renewing the cookie’s expiry while preserving the value — never regenerate fbp’s string, or you mint a new browser ID and break matching. An old creationTimeMs in the value is fine; Meta accepts it as-is.

We deliberately do not use Meta’s 90-day default for the handoff. 90 days would only help the narrow cohort that clicks an ad, never returns to www, and registers 30+ days later — where fbc is already past Meta’s ~7-day click-attribution window anyway, leaving only marginal fbp match quality. Not worth a special-case expiry.

One exception: the _fbp cookie we pre-seed for the Pixel keeps a longer (90-day) expiry, matching what fbevents.js sets itself. It acts as a continuity backstop — if gad lapses but _fbp survives, the next consented visit re-adopts the same browser ID into gad rather than minting a new one.

Read priority for fbp: existing gad fbp key → existing valid _fbp cookie → generate new.

fbc/fbp are marketing identifiers. Under DSGVO/TTDSG they may only be set after the user grants marketing-cookie consent. Capture must be gated on the consent banner; no consent → don’t set them → forwarder simply sends no fbc/fbp (email + IP matching still works). This is the same consent question flagged in the forwarder plan and must be resolved before either side goes live.

Definition of done (www side)