Integration Guide
Complete guide to integrating the BidMachine Plus Unity plugin.
BidMachine Plus Unity Plugin
BidMachine Plus exposes two demand paths through one API:
| Mode | Description |
|---|---|
| AdNetwork | BidMachine runs as a header-bidding demand source — on its own, or plugged into a third-party mediation |
| Mediation | BidMachine acts as the mediation platform itself, running the auction across your demand |
The same ad unit classes — BannerAd, InterstitialAd, RewardedAd — work in both modes. The active mode is selected at initialization.
This page covers plugin installation, initialization, and ad loading. Two installation paths are available: automated via AI coding agents, or manual setup via Unity Package Manager.
Automated Integration with AI Coding Agents
The BidMachine Plus SDK Agents bundle ships a portable integration skill for Claude Code, Codex, and Gemini. Install it for your runtime:
- Claude Code
- Gemini CLI
- npx (any agent)
/plugin marketplace add bidmachine/bidmachine-sdk-agents
/plugin install bidmachine-sdk-agents@bidmachine
gemini extensions install https://github.com/bidmachine/bidmachine-sdk-agents
npx skills add bidmachine/bidmachine-sdk-agents
Then ask your agent to add BidMachine Plus as a First Look in front of your existing mediation — for example:
Add BidMachine Plus as a First Look in front of my existing ad mediation in this Unity game.
The bundled skill drives the integration: it installs the plugin, initializes BidMachine in Mediation mode, and wires BidMachine Plus as the primary source for interstitial and rewarded — requested first on a timeout — with fallback to your existing mediation (MAX, AdMob, LevelPlay, or another) on a no-fill, preserving your current setup.
First Look covers interstitial and rewarded only. For banners, use the manual setup below — the plugin supports them, but they aren't part of the First Look flow.
Manual Installation
Requires Unity 2021.3+, Android SDK 23+, iOS 13.0+, and Xcode 16.4+.
Unity Package Manager
BidMachine Plus is distributed through the BidMachine scoped registry. Register it, then install com.bidmachine.plus. The External Dependency Manager is pulled in automatically as a package dependency — no separate import is needed.
- Editor UI
- manifest.json
- Open Edit → Project Settings → Package Manager.
- Under Scoped Registries click + and fill in:
- Name:
BidMachine Registry - URL:
https://npm.bidmachine.com - Scope(s):
com.bidmachine
- Name:
- Click Apply.
- Open Window → Package Manager, switch the source to My Registries, select BidMachine Plus, and click Install.
Add the scoped registry and the dependency to Packages/manifest.json at your project root:
{
"scopedRegistries": [
{
"name": "BidMachine Registry",
"url": "https://npm.bidmachine.com",
"scopes": ["com.bidmachine"]
}
],
"dependencies": {
"com.bidmachine.plus": "0.1.0"
}
}
Android Configuration
In Player Settings → Publishing Settings, enable Custom Main Gradle Template and Custom Gradle Settings Template.
In Assets → External Dependency Manager → Android Resolver → Settings, enable Patch mainTemplate.gradle, Copy and patch settingsTemplate.gradle from 2022.2, and Use Jetifier. Then run Android Resolver → Resolve.
Required permissions in AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
Optional permissions:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
iOS Configuration
In Assets → External Dependency Manager → iOS Resolver → Settings, disable Link frameworks statically.
Add to the exported Xcode project's Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>SKAdNetworkItems</key>
<array>
<!-- full SKAdNetwork list is published with the SDK -->
</array>
<key>NSUserTrackingUsageDescription</key>
<string>$(APP_NAME) needs your advertising identifier to deliver personalized ads.</string>
Initialization
Call BidMachine.Initialize once at app startup. Pass the desired mode to select the demand path.
BidMachine.GetInstance returns the SDK instance — keep a reference; you'll pass it to every ad unit constructor.
BidMachine Plus initializes in one of two modes, selected by the IntegrationMode you pass to the builder:
AdNetwork Mode
BidMachine runs as a header-bidding ad network. When it plugs into a third-party mediation, name that platform with WithMediator so it can compete in the waterfall.
var sdk = BidMachine.GetInstance("YOUR_APP_KEY");
var config = new InitializationConfigBuilder(IntegrationMode.AdNetwork)
.WithMediator(MediationProvider.AdMob) // the mediation BidMachine plugs into — or MediationProvider.LevelPlay, MediationProvider.Max, a custom string
.WithLoggingEnabled(true) // remove before release
.WithTestModeEnabled(true) // remove before release
.Build();
sdk.Initialized += (sender, args) =>
{
if (args.Error != null) Debug.LogError($"init failed: {args.Error.Message}");
};
sdk.Initialize(config);
Mediation Mode
BidMachine acts as the mediation platform itself, running the auction across your demand. Pass IntegrationMode.Mediation — there's no third-party mediator to declare:
var config = new InitializationConfigBuilder(IntegrationMode.Mediation)
.WithLoggingEnabled(true) // remove before release
.WithTestModeEnabled(true) // remove before release
.Build();
var sdk = BidMachine.GetInstance("YOUR_APP_KEY");
sdk.Initialized += (sender, args) => { /* ready */ };
sdk.Initialize(config);
In both modes the ad unit classes expose NotifyWin() and NotifyLoss(winnerEcpm, networkName) for reporting the outcome of a waterfall round — see Mediation.
Running the auction on your own server instead? See S2S Bidding for the bid-token flow and loading with a
bidPayload.
Ad Integration
All ad lifecycle events deliver an AdInfo describing the winning ad (via EventArgs.AdInfo):
| Property | Type | Description |
|---|---|---|
PlacementId | string | Placement ID from the BidMachine dashboard |
Price | double | eCPM ÷ 1000 (e.g. 0.005 = $5 CPM) |
Precision | RevenuePrecision | One of Estimated, Exact, Unknown |
RawData | IReadOnlyDictionary<string, string> | Network metadata. Known keys: networkName, dsp, ecpm |
Read the winning network via e.AdInfo.RawData["networkName"], and the per-impression revenue via e.AdInfo.Price.
e.AdInfo.Precision describes how reliable that price is:
| Value | Meaning |
|---|---|
Exact | Real-time auction price — trust for reporting |
Estimated | Historical-data estimate — treat as approximate |
Unknown | Confidence cannot be determined |
All ad events fire on the Unity main thread, so you can update UI and call MonoBehaviour APIs from them directly.
Interstitial Ads
Full-screen ads. Create once, load, then call Show when ready. Reload from Closed to keep an ad ready for the next show.
public class AdsController : MonoBehaviour
{
private InterstitialAd interstitialAd;
private void CreateInterstitialAd()
{
interstitialAd = new InterstitialAd(sdk, placementId: "YOUR_PLACEMENT_ID");
interstitialAd.Loaded += (s, e) => Debug.Log($"Interstitial loaded from {e.AdInfo.RawData["networkName"]}");
interstitialAd.LoadFailed += (s, e) => Debug.LogError($"Interstitial load failed: {e.Error.Message}");
interstitialAd.Shown += (s, e) => Debug.Log("Interstitial displayed");
interstitialAd.ShowFailed += (s, e) => Debug.LogError($"Interstitial show failed: {e.Error.Message}");
interstitialAd.Clicked += (s, e) => Debug.Log("Interstitial clicked");
interstitialAd.Closed += (s, e) => interstitialAd.Load(); // reload for next show
interstitialAd.Expired += (s, e) => Debug.LogWarning("Interstitial expired before show");
interstitialAd.RevenuePaid += (s, e) => Debug.Log($"Interstitial revenue: {e.AdInfo.Price} from {e.AdInfo.RawData["networkName"]}");
interstitialAd.Load();
}
private void ShowInterstitialAd()
{
if (interstitialAd.IsLoaded)
{
interstitialAd.Show();
}
else
{
Debug.LogWarning("Interstitial ad not ready yet");
}
}
private void OnDestroy()
{
interstitialAd?.Dispose();
}
}
| Callback | Description |
|---|---|
Loaded | Ad loaded and ready to show |
LoadFailed | Ad failed to load |
Shown | Ad is displayed full-screen |
ShowFailed | Ad failed to display |
Clicked | User tapped the ad |
Closed | User dismissed the ad |
Expired | Ad expired before being shown |
RevenuePaid | Billable impression recorded |
Rewarded Ads
Same lifecycle as interstitial, plus a UserQualifiedForReward callback when the user completes the ad and earns a reward.
public class AdsController : MonoBehaviour
{
private RewardedAd rewardedAd;
private void CreateRewardedAd()
{
rewardedAd = new RewardedAd(sdk, placementId: "YOUR_PLACEMENT_ID");
rewardedAd.Loaded += (s, e) => Debug.Log($"Rewarded loaded from {e.AdInfo.RawData["networkName"]}");
rewardedAd.UserQualifiedForReward += (s, e) =>
{
Debug.Log($"Reward earned: {e.Reward?.Amount} {e.Reward?.Label}");
// grant reward to the user
};
rewardedAd.LoadFailed += (s, e) => Debug.LogError($"Rewarded load failed: {e.Error.Message}");
rewardedAd.Shown += (s, e) => { };
rewardedAd.ShowFailed += (s, e) => { };
rewardedAd.Clicked += (s, e) => { };
rewardedAd.Closed += (s, e) => rewardedAd.Load(); // reload for next show
rewardedAd.Expired += (s, e) => { };
rewardedAd.RevenuePaid += (s, e) => Debug.Log($"Rewarded revenue: {e.AdInfo.Price} from {e.AdInfo.RawData["networkName"]}");
rewardedAd.Load();
}
private void ShowRewardedAd()
{
if (rewardedAd.IsLoaded)
{
rewardedAd.Show();
}
else
{
Debug.LogWarning("Rewarded ad not ready yet");
}
}
private void OnDestroy()
{
rewardedAd?.Dispose();
}
}
| Callback | Description |
|---|---|
Loaded | Ad loaded and ready to show |
UserQualifiedForReward | User completed the ad, grant reward |
LoadFailed | Ad failed to load |
Shown | Ad is displayed full-screen |
ShowFailed | Ad failed to display |
Clicked | User tapped the ad |
Closed | User dismissed the ad |
Expired | Ad expired before being shown |
RevenuePaid | Billable impression recorded |
The UserQualifiedForReward args expose Reward (may be null if reward metadata is not supplied), which carries Label (string) and Amount (int).
Banner Ads
BannerAd displays as an overlay anchored to a screen edge. Load it, then call Show(position) from the Loaded handler; call Hide() to remove it. It is single-shot — it does not auto-refresh.
| Size | Dimensions | Use case |
|---|---|---|
BannerAdSize.Banner | 320 × 50 | Standard banner |
BannerAdSize.Leaderboard | 728 × 90 | Tablets |
BannerAdSize.MRec | 300 × 250 | Medium rectangle |
BannerAdSize.Adaptive(width, maxHeight) | custom | Fluid layout |
Use BannerAdSize.GetMaxAdaptiveHeight(width) to calculate maxHeight for adaptive banners.
Call Show(position) on a loaded BannerAd to anchor it to one of the positions below. Call Hide() to remove it from the screen.
| Position | Constant |
|---|---|
| Top | BannerPosition.HorizontalTop |
| Bottom | BannerPosition.HorizontalBottom |
| Left | BannerPosition.VerticalLeft |
| Right | BannerPosition.VerticalRight |
var banner = new BannerAd(sdk, placementId: "YOUR_PLACEMENT_ID", size: BannerAdSize.Banner);
banner.Loaded += (s, e) => banner.Show(BannerPosition.HorizontalBottom); // default position
banner.LoadFailed += (s, e) => Debug.LogError($"Banner load failed: {e.Error.Message}");
banner.Shown += (s, e) => { };
banner.ShowFailed += (s, e) => { };
banner.Clicked += (s, e) => { };
banner.Expired += (s, e) => { };
banner.RevenuePaid += (s, e) => { };
banner.Load();
// Later:
banner.Hide(); // remove the overlay; Show(position) can re-display it
banner.Dispose();
| Event | Description |
|---|---|
Loaded | Ad loaded and ready to show |
LoadFailed | Ad failed to load |
Shown | Banner visible on screen, impression tracked |
ShowFailed | Ad failed to display |
Clicked | User tapped the ad |
Expired | Ad expired before being shown |
RevenuePaid | Billable impression recorded |
Publisher Extras
Attach arbitrary key → value pairs forwarded with the bid request as publisher extras. The API exists at two levels with the same shape — choose the one that matches the scope you need.
| Scope | Where to call | Applies to |
|---|---|---|
| SDK-wide | BidMachine.AddExtra | Every auction from this instance |
| Per ad unit | <AdUnit>.AddExtra | Only that placement's auctions |
Passing null as the value removes the key. Per-ad-unit extras override SDK-wide values for the same key on that placement.
SDK-wide
var sdk = BidMachine.GetInstance("YOUR_APP_KEY");
sdk.AddExtra("user_segment", "whale");
sdk.AddExtra("ab_bucket", "control");
IReadOnlyDictionary<string, string> all = sdk.GetExtras();
sdk.AddExtra("ab_bucket", null); // remove
Per ad unit
Available on BannerAd, InterstitialAd, and RewardedAd:
interstitialAd.AddExtra("placement_context", "level_complete");
bannerAd.AddExtra("screen", "main_menu");
IReadOnlyDictionary<string, string> extras = interstitialAd.GetExtras();