FloorSync / Guide for Stripe founders
Stripe sales missing in Meta Ads or GA4? A 5-step check
Say Stripe shows 20 new subscriptions this month, and Meta Ads Manager and GA4 each show far fewer. Nothing is broken in Stripe: the sale happens on Stripe’s page or with no browser at all, so the pixel and the GA tag never see it. Here is what to check, in order.
Short answer: send each sale from your Stripe webhook to Meta’s Conversions API and GA4’s Measurement Protocol, with the buyer’s ids you kept at checkout and the Stripe session or invoice id as the event id. Then check it arrived in Meta’s Test Events.
Why the sale goes missing
Three things lose it:
- Checkout is on Stripe’s domain. Your pixel and GA tag are on your site, not on checkout.stripe.com. If the buyer never comes back to your thank-you page (closes the tab, blocks scripts, uses an ad blocker), the browser never reports the sale.
- Renewals have no browser. Stripe charges the card on day 31 from its servers. No page loads, so no tag fires. Propulse puts it plainly: browser tracking won’t catch recurring payments, and GA4’s LTV and cohort reports then count only first purchases.
- The ids that match a sale to an ad click never reach Stripe. Meta matches a server event to a click with the
fbcandfbpcookies (and hashed email); GA4 with the visitor’sclient_id. Tracking Consulting names this as the first reason the join fails.
The 5-step check
01Keep fbp, fbc and the GA client_id at checkout
When your server creates the Stripe Checkout session, read the buyer’s _fbp and _fbc cookies (or the fbclid in the landing URL) and the _ga cookie, and keep them with the session: in the session’s metadata, your own database, or FloorSync’s POST /api/v1/attribution. Without them Meta matches on the hashed email only, and GA4 counts the buyer as a new visitor from nowhere.
02Use the Stripe session or invoice id as the event id
If your thank-you page fires the pixel’s Purchase and your server sends one too, Meta counts two sales unless both carry the same event_id. Use the Checkout session id (cs_…, put {CHECKOUT_SESSION_ID} in your success URL so the page has it) for the first sale, and the invoice id (in_…) for a renewal. In GA4 the same id goes in transaction_id. A webhook Stripe sends twice then counts once.
03Send from the webhook, not the thank-you page
The thank-you page is the least reliable place: it only runs if the buyer comes back. Send from your Stripe webhook handler on checkout.session.completed (paid) and invoice.paid, after it checks Stripe’s signature. Keep the browser pixel too if you like; step 02 stops the double count.
04Decide what to do with renewals
Here experts disagree, and you should choose on purpose. Tracking Consulting advises not sending subscription_cycle renewals to Meta as Purchases: no new click sits behind them, so they credit old campaigns with recurring revenue and make Meta’s numbers look better than the new money they brought.
What FloorSync does, and why: it sends a renewal as a Purchase marked system_generated (not website), matched on the ids kept from the first checkout, with the invoice id as event_id. That way the campaigns that bring buyers who keep paying get credit for it, which matters when you sell a subscription. The ledger keeps them apart (new_paid and renewals), so you or your agent read new money and recurring money separately. If you’d rather Meta only sees first sales, don’t forward invoice.paid events whose billing_reason is subscription_cycle: that’s one if in your handler.
05Check it arrived: Meta Test Events and GA4’s validation server
In Meta Events Manager, open your pixel → Test events, copy the test code and send one event with test_event_code; it shows up in that tab with the match keys Meta read, so you see whether fbp, fbc and the email arrived. GA4 is harder to check: its /mp/collect endpoint answers 204 No Content whether the event was right or not. Send the same body to /debug/mp/collect first, which answers with validationMessages, then look in GA4’s DebugView or Realtime.
Try it on a renewal, right here
Pick an event and switch the buyer’s ids on and off: you see exactly what Meta and GA4 would get, and the notes say what’s missing. It’s worked out in your browser; nothing is sent.
The code: one call in your webhook
If you’d rather not build steps 01–05 yourself, FloorSync does them: your webhook passes each verified Stripe event on, and FloorSync sends it to Meta and GA4 with the right ids, deduplicated, and keeps every answer in a log. Node, from the docs:
import { createHash } from 'node:crypto'
// event: what stripe.webhooks.constructEvent(...) returned
const text = JSON.stringify(event)
await fetch('https://floorsync.towers.app/api/v1/events/stripe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Floor-Key': FLOORSYNC_KEY,
'x-amz-content-sha256': createHash('sha256').update(text).digest('hex'),
},
body: text,
signal: AbortSignal.timeout(10000),
}).catch(() => {}) // never let FloorSync slow down your own webhook
Add ?dry_run=1 and nothing is sent: you get the payloads and GA4’s validation answer back.
Do you need a tool for this at all?
Maybe not yet. Attrifast’s guide argues that under about $1k MRR the only question is whether anything works, and suggests keeping analytics spend to the smaller of $50 a month or 1% of MRR up to $5k MRR. If Meta is your only ad platform and you or your agent can write and maintain the five steps above, Meta’s own Conversions API is free. Vendors’ figures on how many sales go missing (“+30% conversions” and the like) are their claims, not measurements of your business: your own Stripe vs Meta count is the number that matters.
See your own events, free
A free key runs this dry run on your own Meta pixel and GA4. Just your email, no card. $29 a month per business after 7 free days, only when you start the plan.