Mobile · PMs · marketers · engineers
Install is not session is not in-app purchase
The SDK will send a lot if you enable everything. The ads manager will optimize toward whatever you marked as the conversion. Pick one primary, then add funnel events with their real names.
Install and first open
An install is a new install, or a reinstall, depending on the MMP and the device. First open is the first time your code runs after that. They are close in time and not the same row. Attribution usually keys off the install or the first open the MMP defines, not off a custom event you named Install because the web pixel was named Lead.
Reinstalls and ATT-denied users make this noisier than web PageView. Decide whether a reinstall is a new conversion with UA. Do not S2S an install event on every session to keep the campaign warm. Extra install events look like fraud or like a broken SDK, and they will not match SKAN postbacks one-to-one.
Sessions
A session is an app coming to the foreground under the MMP's timeout. Useful for engagement, toxic as a paid conversion. Networks will find users who open once. If the goal is purchase, do not let a session token leak into the purchase slot in the MMP dashboard because the names looked similar.
Background fetch, push opens, and instant-run debug loops mint sessions. Gate what you send S2S if your backend echoes every lifecycle callback. The SDK already sent the session. A duplicate S2S session is how counts diverge between MMP UI and your warehouse.
In-app names per MMP
Name events like the web funnel: tutorial_complete, subscribe, purchase. Keep value and currency on the money events. Same ISO 4217 lecture as web. Adjust event tokens are opaque ids, not English. AppsFlyer eventName is a string you choose. Typos become a second event. Branch distinguishes standard and custom endpoints.
Fire in-app events when the action is real: tutorial complete, subscription start, purchase after receipt. Do not fire the whole funnel on first open. Do not reuse the install token for purchase because you ran out of Adjust events in the free tier. Purchse is a custom event whether you meant it, here as on Meta. Volume is not a conversion.
POST https://api3.appsflyer.com/inappevent/id123456789
{
"appsflyer_id": "1234567890123-1234567",
"eventName": "af_purchase",
"eventValue": "",
"eventTime": "2026-08-21 18:04:00.000",
"eventCurrency": "USD"
}
S2S it once
If the SDK already sent the event, a backend echo without a dedup id double-counts. If the SDK never saw the event (server-side subscription, refund), S2S is the right pipe. Pick one writer per event name. Retry the same body. A new timestamp on retry is a second conversion.
pixellint validate json will tell you if AppsFlyer is missing eventName or appsflyer_id. It will not tell you that you fired af_purchase on first open. That is a product catalog problem. Put the event name in the measurement spec next to the ads manager conversion slot.
https://s2s.adjust.com/event?s2s=1&event_token=f0ob4r&app_token=4w565xzmb54d&gps_adid=660e1d86-6796-463a-be86-897993136018
SKAN will not match 1:1
SKAN postbacks are coarse and delayed. They will not line up with MMP install rows one-to-one. Do not S2S extra installs to force the totals to agree. Do not drop MMP install because SKAN exists. Report two views.
A custom event named Install that fires on every session will not map to a SKAN conversion value you designed for purchase. Map SKAN bits to the same funnel you named in the MMP, then stop trying to make the two graphs identical.
Check the artifact
Paste the pixel URL or JSON body into the
playground. Same engine as
pixellint validate. Nothing leaves the tab.