← All articles

How to set up RevenueCat with Meta ads so your purchases actually track

By default, Meta has almost no idea how much money your subscription app really makes. It sees an install, maybe a trial start if your SDK is wired up, and then nothing. The renewals, the trial conversions, the upgrades, all the revenue that actually decides whether a campaign is profitable, happens later and server side, where Meta never looks.

RevenueCat is where that truth lives. It knows every trial, conversion, renewal, and cancellation across iOS and Android. The whole job is getting that truth into Meta cleanly, so your ads optimise on real purchases instead of a guess. This is the exact step most apps get wrong, so here is how to do it properly.

Why this breaks by default

Three things work against you.

First, subscription revenue is a server side event. A renewal three weeks after install never touches the phone in a way the Meta SDK can catch. If Meta only hears about the first checkout, it is optimising against a tiny slice of the real value.

Second, iOS killed easy attribution. After ATT, most users do not grant tracking, so device level matching is gone and you are leaning on SKAdNetwork, which is delayed and aggregated. That is not a reason to give up, it is a reason to send Meta clean, server side purchase data through the Conversions API so it has something real to learn from.

Third, most setups only send installs and trial starts. Those are the cheap, meaningless events. The events that matter for optimisation are the paid ones: trial conversions, initial purchases, and renewals.

What a good setup looks like

Meta receives your real money events (trial conversion, initial purchase, renewal), tied to the right user, deduplicated between the app and your server, on both iOS and Android. When that is true, you can finally optimise campaigns toward purchases and read cost per purchase against LTV instead of guessing.

Here is how to get there.

Step 1: Wire up the Meta SDK in the app

Install the Meta SDK and connect your app inside Events Manager so installs and basic app events flow in, and so iOS SKAdNetwork is configured. This is the foundation Meta needs before any purchase data is useful. Do not skip it and try to run everything server side, you want both signals.

Step 2: Let RevenueCat collect the identifiers Meta needs

An event is useless to Meta if it cannot be matched to a user. In your RevenueCat setup, enable the collection of device and attribution identifiers and pass Meta’s anonymous ID through to RevenueCat, so every RevenueCat event carries the identifiers Meta uses for matching. Without this, your beautifully accurate purchase events land in Meta and match nobody.

Step 3: Pick one clean pipe for the events

You have two reliable ways to move RevenueCat purchase events into Meta. Pick one, do not run both blindly.

Option A, through your MMP. If you already use AppsFlyer, Adjust, or Singular, RevenueCat integrates with them natively. RevenueCat sends the purchase and renewal events to the MMP, and the MMP forwards them to Meta with attribution already handled. This is the cleanest path if you have an MMP.

Option B, RevenueCat webhooks to the Meta Conversions API. No MMP? Point RevenueCat webhooks at a small server endpoint that forwards the events to Meta’s Conversions API for app events. More control, a bit more engineering, and you own the pipe end to end.

Step 4: Send the events that actually matter

Map the RevenueCat events to Meta purchase events, and be deliberate about which ones you send:

  • Trial start: fine as a soft signal, do not optimise on it.
  • Trial conversion: this is a real purchase, send it.
  • Initial purchase: send it, with revenue and currency.
  • Renewal: send it, this is where subscription LTV is built.

Include the actual revenue and currency on each event. An event with no value teaches Meta nothing about how much a customer is worth.

Step 5: Deduplicate, or you will double count

If the Meta SDK reports a purchase from the app and your server also reports the same purchase through the Conversions API, Meta can count it twice and your numbers inflate. Send a shared event ID on both the client and server events so Meta can dedupe them. This one step saves a lot of confused reporting later.

Step 6: Set realistic expectations on iOS

Post ATT, iOS attribution runs through SKAdNetwork, which is delayed and aggregated. Configure your conversion value mapping so a paid conversion is captured, and accept that iOS reporting will always be coarser and slower than Android. The goal is not perfect device level truth on iOS, it is feeding Meta enough real purchase signal to optimise well.

Step 7: Test before you trust it

Do a real test purchase and confirm the event lands correctly in Events Manager, ideally through the Test Events tool, with the right value and currency. Then compare a few days of Meta reported purchases against RevenueCat. They will not match to the cent, but they should be in the same universe. If Meta shows a fraction of what RevenueCat shows, something in the pipe is broken.

The mistakes I see most often

  • Only sending installs and trial starts, so Meta optimises toward people who will never pay.
  • Purchase events with no revenue attached, so LTV is invisible.
  • No shared event ID, so client and server events double count.
  • Identifiers never passed through RevenueCat, so events match nobody.
  • Renewals ignored entirely, which is most of the actual revenue for a subscription app.

Bottom line

RevenueCat already knows the truth about your revenue. Meta does not, until you connect them properly. Get the identifiers flowing, pick one clean pipe, send the paid events with real values, deduplicate, and verify with a test purchase. Do that and your Meta campaigns finally optimise on money instead of installs.

If your subscription app is running Meta ads but the purchase data looks nothing like RevenueCat, that gap is almost always a tracking setup problem, and it is exactly the kind of thing I fix on a teardown call.

Ads live but the numbers do not make sense? That is what I fix.

Request a teardown call → Apply for done-for-you