Few things halt a release pipeline faster than a monetization SDK failure. You have your game logic perfected, your build pipeline is green, but at runtime, the logs flood with UnityAdsInitializationError.InternalError or generic "Environment Check Failed" messages.
If you are upgrading to Unity 6 (or Unity 2023.x), this issue has become increasingly common. The transition from the legacy "Built-in Services" window to the Unity Package Manager (UPM) and the decoupled Unity Services Core has introduced complexity in how dependencies are resolved and initialized.
This article details the root cause of these initialization failures and provides a strictly typed, asynchronous solution to implement Unity Ads correctly in modern Unity environments.
The Root Cause: Race Conditions and Dependency Decoupling
In older versions of Unity (2019/2020), the Ads service was often treated as a monolithic "toggle" within the editor. The engine handled the handshake with the backend automatically.
In Unity 2023 and Unity 6, the architecture has changed:
- Dependency on Unity Services Core: The Ads SDK is now a package (
com.unity.ads) that depends oncom.unity.services.core. The Ads SDK cannot function until the Core service has established an authenticated session with the Unity backend. - The Race Condition: The most common cause of "Internal Error" is attempting to call
Advertisement.InitializebeforeUnityServices.InitializeAsynchas completed. - Package Version Mismatches: Migrating projects often leaves artifacts in the
manifest.json. If you have the legacyUnityEngine.Advertisement(built-in) enabled alongside the UPM package, the namespaces collide, or the internal bindings fail silently, resulting in vague error codes.
Prerequisite: Cleaning the Manifest
Before writing code, ensure your environment allows for a clean initialization. We must remove legacy artifacts that confuse the compiler or the build processor.
- Open your project directory (not via Unity Hub) and locate
Packages/manifest.json. - Ensure you are not mixing
com.unity.adswithcom.unity.ads-iosorcom.unity.ads-android(deprecated). - Ensure
com.unity.services.coreis present.
Your manifest dependencies should look similar to this version-agnostic example:
{
"dependencies": {
"com.unity.ads": "4.4.2",
"com.unity.services.core": "1.12.0",
"com.unity.services.mediation": "1.0.0",
...
}
}
Note: If you use Unity LevelPlay or Mediation, the initialization order becomes even more critical.
The Fix: Asynchronous Initialization Wrapper
We will write a MonoBehaviour that guarantees UnityServices.Core is ready before attempting to load the Ads SDK. This script uses the IUnityAdsInitializationListener interface to catch specific failure callbacks.
1. The Setup
Create a script named AdsInitializer.cs. This script handles the asynchronous initialization of the Services Core, followed by the Ads Service.
2. The Implementation
Copy the following code. It utilizes System.Threading.Tasks for cleaner async handling compared to Coroutines, which is the modern standard for Unity 2023+.
using System;
using UnityEngine;
using UnityEngine.Events;
using Unity.Services.Core;
using Unity.Services.Advertisement;
public class AdsInitializer : MonoBehaviour, IUnityAdsInitializationListener
{
[Header("Configuration")]
[SerializeField] private string _androidGameId;
[SerializeField] private string _iOSGameId;
[SerializeField] private bool _testMode = true;
[Header("Events")]
public UnityEvent OnInitializationComplete;
public UnityEvent<string> OnInitializationFailed;
private string _gameId;
async void Awake()
{
InitializeGameId();
try
{
// 1. Initialize the Core Services first.
// This is the critical step often missed in legacy docs.
await UnityServices.InitializeAsync();
Debug.Log("Unity Services Core Initialized.");
// 2. Once Core is ready, initialize Ads.
InitializeAds();
}
catch (Exception e)
{
Debug.LogError($"Unity Services Core failed to initialize: {e.Message}");
OnInitializationFailed?.Invoke(e.Message);
}
}
private void InitializeGameId()
{
#if UNITY_IOS
_gameId = _iOSGameId;
#elif UNITY_ANDROID
_gameId = _androidGameId;
#elif UNITY_EDITOR
_gameId = _androidGameId; // Fallback for Editor testing
#endif
if (string.IsNullOrEmpty(_gameId))
{
Debug.LogError("AdsInitializer: Game ID is missing. Check Inspector settings.");
}
}
private void InitializeAds()
{
if (!Advertisement.isSupported)
{
Debug.LogWarning("AdsInitializer: Advertisement is not supported on this platform.");
return;
}
// Prevents re-initialization if already active
if (Advertisement.isInitialized)
{
Debug.Log("AdsInitializer: Ads already initialized.");
OnInitializationComplete?.Invoke();
return;
}
Debug.Log($"AdsInitializer: Initializing Ads for ID: {_gameId} | Test Mode: {_testMode}");
// Pass 'this' as the listener to handle the callbacks defined below
Advertisement.Initialize(_gameId, _testMode, this);
}
// --- IUnityAdsInitializationListener Implementation ---
public void OnInitializationComplete()
{
Debug.Log("AdsInitializer: Advertisement.Initialize success.");
// Dispatch event to Game Managers or UI Loaders
OnInitializationComplete?.Invoke();
}
public void OnInitializationFailed(UnityAdsInitializationError error, string message)
{
// This captures the specific internal error logic
Debug.LogError($"AdsInitializer: Initialization Failed. Error: {error}, Message: {message}");
OnInitializationFailed?.Invoke($"{error}: {message}");
// Root Cause Handling
if (error == UnityAdsInitializationError.INTERNAL_ERROR)
{
Debug.LogWarning("Troubleshooting Tip: Check internet connectivity and ensure Unity Services Dashboard is enabled for this project ID.");
}
}
}
Deep Dive: Why This Works
The "Internal Error" is frequently a symptom of an unhandled exception within the native bridge. By wrapping the UnityServices.InitializeAsync() call in a try/catch block and await-ing it, we ensure the native environment bindings are established before the Ads package tries to ping the network.
The Listener Pattern
Notice we pass this into Advertisement.Initialize. Without the listener, the SDK fails silently on background threads. The OnInitializationFailed method explicitly exposes the UnityAdsInitializationError enum.
If you receive UnityAdsInitializationError.AD_BLOCKER_DETECTED, the issue is local to the device environment. However, INTERNAL_ERROR usually implies the SDK cannot find the Unity Project ID linked to the build.
Resolving DevOps & CI/CD Issues
If this code works locally but fails in a Jenkins/GitHub Actions build, the issue is likely the Project ID Linkage.
When building via CI, Unity must know which Organization and Project ID the build belongs to.
- Link the Project: In the Unity Editor, go to Project Settings > Services. Ensure the project is linked to a valid Unity Cloud Project ID.
- Commit the Asset: This linkage creates a
ProjectSettings/ProjectSettings.assetfile containing theproductGUID. - Check Cloud Diagnostics: If initialization fails in a production build, verify that you haven't stripped required code. In your
link.xml(if you use code stripping), preserve the Ads namespace:
<linker>
<assembly fullname="Unity.Services.Core" preserve="all" />
<assembly fullname="Unity.Services.Advertisement" preserve="all" />
</linker>
Common Pitfalls
1. The "Test Mode" Trap
If _testMode is true, you will see test ads. If it is false, but your project is not approved for live ads in the Unity Dashboard, you will get initialization errors or "No Fill" errors. Always force _testMode = true in development builds using preprocessor directives:
#if DEVELOPMENT_BUILD
_testMode = true;
#endif
2. Android Manifest Conflicts
On Unity 2023, if you have other SDKs (like Firebase or Facebook), the Gradle merge may fail or strip Unity Ads activities. Inspect your mainTemplate.gradle or launcherTemplate.gradle ensuring the Unity Ads activity is present in the final build artifact.
3. Mediation Clash
If you are using IronSource or Unity LevelPlay, do not initialize the Unity Ads SDK manually as described above. The Mediation adapters handle initialization automatically. Manual initialization will cause a "Singleton" conflict, crashing the ad loader.
Conclusion
The "Internal Error" in Unity Ads is rarely internal to the device; it is almost always a configuration desynchronization between the Core Services and the Ads package. By strictly awaiting the Core initialization and implementing the initialization listener, you transform vague runtime crashes into debuggable, recoverable events.