How to Get Full Attribution Coverage Between Adjust and Firebase
A one-time SDK setup for game studios: cross-write Adjust ADID, Firebase user_pseudo_id, and your own user_id so installs join cleanly across both systems. AppsFlyer, Singular, and Branch follow the same pattern.
Sabri Karagonen
Data & Product
How to Get Full Attribution Coverage Between Adjust and Firebase
A game studio I worked with could only match 60% of their Firebase installs to an Adjust record. Forty percent of new users were ghosts. They had no idea where those users came from.
The fix was three lines of SDK code. Coverage went to 98% within a week of the next app release.
If you wire this up once during your Adjust integration, you never have to think about it again. The rest of this post is what to put in those three lines.
Adjust and Firebase run in the same app but generate different IDs:
Firebase stamps every install with a user_pseudo_id. Anonymous, persistent, and the primary key in BigQuery's events_* table.
Adjust stamps every install with an adid. The primary key on the Adjust side.
Your backend has its own user_id once the user logs in.
If you do not explicitly tell each SDK about the others, you cannot join them later.
Most of the 40% gap was iOS users who declined ATT. Without an IDFA, Adjust falls back to probabilistic matching, and a chunk of those users never get attributed. The same thing happens on Android when users reset their GAID. Those installs end up in Firebase with no campaign attached.
// iOS, Swift
Adjust.adid { adid in
if let adid = adid {
Analytics.setUserProperty(adid, forName: "adjust_adid")
}
}
Now every Firebase event for that user carries the Adjust ID. In BigQuery, you can join events_* to the Adjust raw export on user_properties.adjust_adid = adid.
The moment a user logs in or creates an account, push your internal user_id to Adjust as a callback parameter, and set it as a Firebase user property at the same time.
Same idea on AppsFlyer, Singular, and Branch, only the method names change.
AppsFlyer: read appsflyer_id via AppsFlyerLib.getAppsFlyerUID(), write to Firebase. Send your customer_user_id with setCustomerUserId() and the Firebase pseudo ID via setAdditionalData().
Singular: read Singular.getSingularDeviceId(), write to Firebase. Send your user_id with Singular.setCustomUserId(). Pass firebase_pseudo_id as a global property.
Branch: read the install ID from Branch.getInstance().getFirstReferringParams(), write to Firebase. Call setIdentity(userId) on login.
If you only remember one thing: every ID your stack generates should be readable from every other system.
"Isn't pushing IDs around a privacy issue?" Not if your consent flow is set up correctly.
IDFA is gated by ATT. If the user declines, you don't have an IDFA to push anywhere.
Adjust ADID is not a device ID. It's Adjust's own identifier derived from IDFV plus signals. Sharing it with your own Firebase project is fine.
Firebase user_pseudo_id is anonymous, Google treats it as non-PII.
Your own user_id: if it's an email, hash it before sending. If it's a UUID, you're fine.
As long as your privacy policy mentions you use an MMP and analytics provider, and you respect ATT and GDPR, this is exactly what those vendors expect. If you're in the EU and want to be safe, run it past your DPO.