Bid floors in MAX

Setup in MAX

Max enables partners to programmatically set bid floor per ad request in realtime. Before requesting an ad from MAX, get insights from Nefta and set the bid floor accordingly.

Integrate the AdSDK

You can integrate the MAX adSDK via Unity, Native iOS or Native Android.

Disable Back-to-back Ad Loading

In order for each ad request to have realtime adjusted bid floor price back to back ad loading has to be disabled.

📘

It's important that you disable back to back ad loading in MAX when using multi ad unit set up before initializing MAX SDK:

private readonly string[] _adUnits = new string[] {
#if UNITY_IOS
  // interstitials // Replace with your ad unit ids
  "6d318f954e2630a8", "37146915dc4c7740", "e5dc3548d4a0913f",
  // rewarded // Replace with your ad unit ids
  "918acf84edf9c034", "37163b1a07c4aaa0", "e0b0d20088d60ec5"
#else // UNITY_ANDROID
  // interstitials // Replace with your ad unit ids
  "7267e7f4187b95b2", "00b665eda2658439", "87f1b4837da231e5",
  // rewarded // Replace with your ad unit ids
  "72458470d47ee781", "5305c7824f0b5e0a", "a4b93fe91b278c75"
#endif
};

MaxSdk.SetExtraParameter("disable_b2b_ad_unit_ids", string.Join(",", _adUnits));
MaxSdk.InitializeSdk();
let adUnits = [
  // interstitials
  "6d318f954e2630a8", "37146915dc4c7740", "e5dc3548d4a0913f",
  // rewarded
  "918acf84edf9c034", "37163b1a07c4aaa0", "e0b0d20088d60ec5"
]

let max = ALSdk.shared()
max.settings.setExtraParameterForKey("disable_b2b_ad_unit_ids", value: adUnits.joined(separator: ","))
max.settings.isVerboseLoggingEnabled = true
let initConfig = ALSdkInitializationConfiguration(sdkKey: "key") { builder in
   builder.mediationProvider = ALMediationProviderMAX
}
max.initialize(with: initConfig) { sdkConfig in
}
ALSdkInitializationConfiguration *initConfig = [ALSdkInitializationConfiguration configurationWithSdkKey: @"key" builderBlock:^(ALSdkInitializationConfigurationBuilder *builder) {
   builder.mediationProvider = ALMediationProviderMAX;
}];
    
ALSdkSettings *settings = [ALSdk shared].settings;
NSArray *adUnits = @[
  // interstitials
  @"6d318f954e2630a8", @"37146915dc4c7740", @"e5dc3548d4a0913f",
  // rewarded
  @"918acf84edf9c034", @"37163b1a07c4aaa0", @"e0b0d20088d60ec5"
];
[settings setExtraParameterForKey: @"disable_b2b_ad_unit_ids" value: [adUnits componentsJoinedByString:@","]];

[[ALSdk shared] initializeWithConfiguration: initConfig completionHandler:^(ALSdkConfiguration *sdkConfig) {
}];
String[] adUnits = new String[]{
  // interstitials
  "7267e7f4187b95b2", "00b665eda2658439", "87f1b4837da231e5",
  // rewarded
  "72458470d47ee781", "5305c7824f0b5e0a", "a4b93fe91b278c75"
};

AppLovinSdk sdk = AppLovinSdk.getInstance(this);
sdk.getSettings().setExtraParameter("disable_b2b_ad_unit_ids", String.join(",", adUnits));
AppLovinSdkInitializationConfiguration initConfig = AppLovinSdkInitializationConfiguration.builder( "key" )
  .setMediationProvider( AppLovinMediationProvider.MAX )
  .build();
sdk.initialize( initConfig, new AppLovinSdk.SdkInitializationListener() {
  @Override
  public void onSdkInitialized(final AppLovinSdkConfiguration sdkConfig) {
    AppLovinSdk.getInstance(MainActivity.this).getSettings().setVerboseLogging(true);
  }
});
List<String> adUnits = Platform.isAndroid ?
  ["0822634ec9c39d78", "084edff9524b52ec", "3d7ef05a78cf8615", "c0c516310b8c7c04" ] :
  ["5e11b1838778c517", "fbd50dc3d655933c", "ad9b024164e61c00", "c068edf12c4282a6" ];

AppLovinMAX.setExtraParameter("disable_b2b_ad_unit_ids", adUnits.join(","));

Requesting floor price insights

Via the SDK, you can insights:

  • Churn: ._churn._d1_probability, ...
  • User value bid floor price per ad format: ._banner._floorPrice, ._interstitial._floorPrice and ._rewarded._floorPrice.
  • Recommended AdUnit Id: ._banner._adUnit, ._interstitial._adUnit and._rewarded._adUnit
NeftaAdapterEvents.GetInsights(Insights.Churn | Insights.Rewarded, (Insights insiights) => {
   _usedInsight = insights._rewarded;
}, TimeoutInSeconds);
[NeftaPlugin._instance GetInsights: Insights.Churn | Insights.Interstitial callback: ^(Insights * insights) {
   _usedInsight = insights._interstitial;
} timeout: 5];
NeftaPlugin._instance.GetInsights(Insights.Churn | Insights.Rewarded, callback: { insights in
   _usedInsight = insights._rewarded
}, timeout: TimeoutInSeconds)
NeftaPlugin._instance.GetInsights(Insights.CHURN | Insights.REWARDED, 
  (Insights insights) -> {
      _usedInsight = insights._rewarded;
   }, TimeoutInSeconds);
);
  
Nefta.getInsights(Insights.INTERSTITIAL, (Insights insights) => {
  _interstitialInsight = insights.interstitial
}, 5);

If you provide timeout parameter you are guaranteed to receive callback in specified time. Before using any insight, check that it is not null.

Log outcome of ad opportunity

After a partner has received insight, requested an ad in MAX using the insights.[_banner|_interstitial|_rewarded|._adUnit field, the partner should log the outcome of the ad opportunity in order to continuously maximise ad revenue uplift.

When the ad successfully loads, log the response using the following function:

private void OnAdLoadedEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
{
    NeftaAdapterEvents.OnExternalMediationRequestLoaded(NeftaAdapterEvents.AdType.Rewarded, _usedInsight, adInfo);
}
- (void)didLoadAd:(MAAd *)ad {
    [ALNeftaMediationAdapter OnExternalMediationRequestLoad: AdTypeInterstitial recommendedAdUnitId: _recommendedAdUnitId calculatedFloorPrice: _calculatedBidFloor ad: ad];
}
func didLoad(_ ad: MAAd) {
    ALNeftaMediationAdapter.onExternalMediationRequestLoad(.rewarded, ad: ad, usedInsight: _usedInsight)
}
@Override
public void onAdLoaded(@NonNull MaxAd ad) {
    NeftaMediationAdapter.OnExternalMediationRequestLoaded(NeftaMediationAdapter.AdType.Rewarded, ad, _usedInsight);
}
AppLovinMAX.setInterstitialListener(InterstitialListener(
  onAdLoadedCallback: (ad) {
    Nefta.onExternalMediationRequestLoaded(AdType.Interstitial, _interstitialInsight, ad);
  });

When the ad fails to load, log the response using the following function:

private void OnAdFailedEvent(string adUnitId, MaxSdkBase.ErrorInfo errorInfo)
{
    NeftaAdapterEvents.OnExternalMediationRequestFailed(NeftaAdapterEvents.AdType.Rewarded, _usedInsight, adUnitId, errorInfo);
}
- (void)didFailToLoadAdForAdUnitIdentifier:(NSString *)adUnitIdentifier withError:(MAError *)error {
    [ALNeftaMediationAdapter OnExternalMediationRequestFail: AdTypeRewarded adUnitIdentifier: adUnitIdentifier usedInsight: _usedInsight error: error];
}
func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError) {
    ALNeftaMediationAdapter.onExternalMediationRequestFail(.rewarded, adUnitIdentifier: adUnitIdentifier, usedInsight: _usedInsight, error: error)
}
@Override
    public void onAdLoadFailed(@NonNull String adUnitId, @NonNull MaxError maxError) {
          NeftaMediationAdapter.OnExternalMediationRequestFailed(NeftaMediationAdapter.AdType.Rewarded, adUnitId, _usedInsight, maxError);
}
AppLovinMAX.setInterstitialListener(InterstitialListener(
  onAdLoadFailedCallback: (adUnitId, error) {
    Nefta.onExternalMediationRequestFailed(AdType.Interstitial, _interstitialInsight, adUnitId, error);
  });
❗️

For best results

When making ad request to MAX with adUnitId / floorPrice form Insight object; make sure to send that insight object back in onExternalMediationRequestLoaded / onExternalMediaitonRequestFailed. So that the next insight can take this feedback into consideration.

When an ad successfully shows log the impression with this:

// By default this impression is collected automatically in Unity.
// But in case you disable this (with sendAdEvents=false in plugin Init) and send impression manually:
MaxSdkCallbacks.Rewarded.OnAdRevenuePaidEvent += (adUnitId, info) =>
{
    NeftaAdapterEvents.OnExternalMediationImpression(adUnitId, info);
};
- (void)didPayRevenueForAd:(nonnull MAAd *)ad {
    [ALNeftaMediationAdapter OnExternalMediationImpression: ad];
}
func didPayRevenue(for ad: MAAd) {
    ALNeftaMediationAdapter.onExternalMediationImpression(ad)
}
@Override
public void onAdRevenuePaid(final MaxAd ad) {
    NeftaMediationAdapter.OnExternalMediationImpression(ad);
}
AppLovinMAX.setInterstitialListener(InterstitialListener(
  onAdRevenuePaidCallback: (ad) {
    Nefta.onExternalMediationImpression(ad);
  });

Initial Ad Unit has no fill

If the initially recommended ad unit doesn't return an ad:

  • Re-request the ad insight: the new floor price takes into account the previous no-fill event and is adjusted in real time. Then request the ad again. You can repeat this process until you get fill. Please note max requires an exponential backoff delay between these retries, specified as 2, 4, 8, 16, 32, 64, 64, 64... seconds. As mentioned in the introduction, Nefta insures your ads are served within time windows you specify and balances fill %, revenue uplift and latency.