Overview: In Analytics SDK 6.1 and later (com.unity.services.analytics), when using the EndUserConsent consent flow (Unity 6.2+), analytics begins collecting data during UnityServices.InitializeAsync() if the player previously granted consent. If ExternalUserId isn't set before that call, the SDK records the startup events under the installation ID. When the correct ID is set afterward, the SDK starts a new session and records a second set of startup events under the correct ID. The batch recorded under the installation ID is spurious and can inflate MAU. The fix is to ensure ExternalUserId is set before InitializeAsync() is called.
Symptoms:
- After upgrading to Analytics SDK 6.1 or later, analytics startup events (clientDevice, gameStarted, newPlayer, sdkStart) fire with an unrecognized user ID (the installation ID).
- A new session starts seconds later, and a second set of the same startup events fires with the correct user ID.
- newPlayer fires under the installation ID even for returning players, inflating MAU.
- The issue did not occur before the SDK upgrade.
- The issue appears on sessions where the player previously granted analytics consent.
Cause: SDK 6.1 introduced a behavioral change as part of the Developer Data framework (EndUserConsent API, Unity 6.2+): if consent was previously granted, the SDK begins collecting analytics data immediately during UnityServices.InitializeAsync(), rather than waiting for an explicit StartDataCollection() call as in the legacy flow. Projects still on the legacy consent flow are not affected at InitializeAsync(), since collection there starts only when StartDataCollection() is called.
If your startup sequence loads player data asynchronously and sets UnityServices.ExternalUserId after InitializeAsync() has already been called, the SDK records the initial events without an ExternalUserId and falls back to the installation ID. When ExternalUserId is then set, the SDK treats it as a user change, starts a new session, and records the startup event batch again under the correct ID.
Resolution: Reorder your startup sequence so that ExternalUserId is set before InitializeAsync() is called:
- Fully await your player data load before proceeding.
- Set UnityServices.ExternalUserId from the loaded player data.
- Call UnityServices.InitializeAsync() only after step 2 completes.
This guarantees the correct ID is in place before the SDK records any events, for both new and returning players. No installation-ID batch will appear in the event stream during normal startup.
Note that mid-session account switches are a different case. Once the SDK is initialized, changing ExternalUserId (for example, after downloading a different account from a server) intentionally starts a new session and records startup events for the new ID. That is expected behavior and a legitimate MAU contribution, since a different player signed in. It should not be "fixed" using the reordering above.
More Information:
- Analytics Custom User ID (setting and updating ExternalUserId)
- SDK 6.1 Migration Guide (EndUserConsent flow and the InitializeAsync behavior change)
- Unity Services Initialization (InitializeAsync() and startup sequencing)
- Standard Events Reference (clientDevice, gameStarted, newPlayer, sdkStart)
- Gotchas:
- The SDK falls back to the installation ID only when no ExternalUserId is set at the moment InitializeAsync() fires. Setting it one line later is too late.
- If the duplicate startup batch still appears after reordering, verify your async file load is fully awaited; a missing await before the ExternalUserId assignment is the most common cause.
- This affects all events recorded during SDK initialization (clientDevice, gameStarted, newPlayer, sdkStart), not just a subset.