Bid floors in LevelPlay
Setup in LevelPlay
LevelPlay enables partners to dynamically set bid floor prices using the "Price limitations using waterfall configuration". For every ad opportunity, you first obtain a floor price from Nefta, then request an ad from LevelPlay with that bid floor in adConfiguration. If no ad is returned, you adhere to the provider specified delay and then repeat the process, by first obtaining a new floor price (which is adapted in real time and takes the previous no fill into consideration) and then requesting another ad as before.
Request user insights
Via the SDK, partners will request user specific ad format insight:
Ìnsights.Banner
, Insights.Interstitial
, Insights.Rewarded
:
NeftaPlugin._instance.GetInsights(Insights.Interstitial, callback: { insights in
_requestedFloorPrice = 0
_usedInsight = insights._interstitial
if let interstitialInsight = _usedInsight {
_requestedFloorPrice = interstitialInsight._floorPrice
}
}
NeftaPlugin._instance.GetInsights(Insights.INTERSTITIAL, (Insights insights) -> {
_requestedFloorPrice = 0;
_usedInsight = insights._interstitial;
if (_usedInsight != null) {
_requestedFloorPrice = _usedInsight._floorPrice;
}
}, 5);
Adapter.GetInsights(Insights.Interstitial, (Insights insights) => {
_requestedFloorPrice = 0f;
_usedInsight = insights._interstitial;
if (_usedInsight != null) {
_requestedFloorPrice = _usedInsight._floorPrice;
}
}, 5);
Validate returned valuesIt is crucial a partner checks the values received are valid. Only if a valid response is received, a partner should proceed with using the values.
You are guaranteed to receive the callback in the same thread with all keys that you specified in the request.
Using the _floorPrice
calculated above, request an ad with that bid floor.
Log outcome of ad opportunity
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:
func didLoadAd(with adInfo: LPMAdInfo) {
Adapter.OnExternalMediationRequestLoaded(Adapter.AdType.Interstitial, _usedInsight, _requestedFloorPrice, info);
}
@Override
public void onAdLoaded(@NonNull LevelPlayAdInfo adInfo) {
NeftaCustomAdapter.OnExternalMediationRequestLoaded(NeftaCustomAdapter.AdType.Interstitial, _usedInsight, _requestedFloorPrice, adInfo);
}
private void OnAdLoaded(LevelPlayAdInfo info)
{
Adapter.OnExternalMediationRequestFailed(Adapter.AdType.Rewarded, _bidFloor, _calculatedBidFloor, error);
}
When the ad fails to loads, log the response using the following function:
func didFailToLoadAd(withAdUnitId adUnitId: String, error: any Error) {
ISNeftaCustomAdapter.onExternalMediationRequestFail(.interstitial, usedInsight: _usedInsight, requestedFloorPrice: _requestedFloorPrice, adUnitId: adUnitId, error: error as NSError)
}
@Override
public void onAdLoadFailed(@NonNull LevelPlayAdError error) {
NeftaCustomAdapter.OnExternalMediationRequestFailed(NeftaCustomAdapter.AdType.Interstitial, _usedInsight, _requestedFloorPrice, error);
}
private void OnAdLoadFailed(LevelPlayAdError error)
{
Adapter.OnExternalMediationRequestFailed(Adapter.AdType.Interstitial, _usedInsight, _requestedFloorPrice, error);
}
Initial bid floor price has no fill
If the initially selected ad unit doesn't return an ad:
- Re-request ad insight: the new floor price takes into account the previous no-fill event and is adjusted in real time. You can repeat this process for each ad opportunity. However, latency should be considered if requesting multiple times per ad opportunity. Nefta will help in balancing fill %, revenue uplift and latency.
- Request an ad from LevelPlay without a floor price set.
Example code
Full example:
- Native iOS: https://github.com/Nefta-io/NeftaISAdapter/blob/main/ISIntegration/Rewarded.swift
- Native Android: https://github.com/Nefta-io/NeftaISAdapter-Android/blob/main/ISIntegration/src/main/java/com/nefta/is/RewardedWrapper.java
- Unity: https://github.com/Nefta-io/NeftaISAdapter-Unity/blob/main/Assets/AdDemo/RewardedController.cs
Below is an example showing the integration of dynamic floors in LevelPlay (not production-ready) .
private func GetInsightsAndLoad() {
NeftaPlugin._instance.GetInsights(Insights.Rewarded, callback: Load, timeout: 5)
}
private func Load(insights: Insights) {
_requestedFloorPrice = 0
_usedInsight = insights._rewarded
if let usedInsight = _usedInsight {
_requestedFloorPrice = usedInsight._floorPrice
}
SetInfo("Loading Rewarded with floor: \(_requestedFloorPrice)")
let config = LPMRewardedAdConfigBuilder()
.set(bidFloor: _requestedFloorPrice as NSNumber)
.build()
_rewarded = LPMRewardedAd(adUnitId: "doucurq8qtlnuz7p", config: config)
_rewarded.setDelegate(self)
_rewarded.loadAd()
}
func didFailToLoadAd(withAdUnitId adUnitId: String, error: any Error) {
ISNeftaCustomAdapter.onExternalMediationRequestFail(.rewarded, usedInsight: _usedInsight, requestedFloorPrice: _requestedFloorPrice, adUnitId: adUnitId, error: error as NSError)
SetInfo("didFailToLoadAd \(adUnitId): \(error.localizedDescription)")
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
if self._isLoading {
self.GetInsightsAndLoad()
}
}
}
func didLoadAd(with adInfo: LPMAdInfo) {
ISNeftaCustomAdapter.onExternalMediationRequestLoad(.rewarded, usedInsight: _usedInsight, requestedFloorPrice: _requestedFloorPrice, adInfo: adInfo)
SetInfo("didLoadAd \(adInfo)")
SetLoadingButton(isLoading: false)
_loadButton.isEnabled = false
_showButton.isEnabled = true
}
private void GetInsightsAndLoad() {
NeftaPlugin._instance.GetInsights(Insights.REWARDED, this::Load, 5);
}
private void Load(Insights insights) {
_requestedFloorPrice = 0;
_usedInsight = insights._rewarded;
if (_usedInsight != null) {
_requestedFloorPrice = _usedInsight._floorPrice;
}
Log("Loading Rewarded with floor: "+ _requestedFloorPrice);
LevelPlayRewardedAd.Config config = new LevelPlayRewardedAd.Config.Builder()
.setBidFloor(_requestedFloorPrice).build();
_rewarded = new LevelPlayRewardedAd("kftiv52431x91zuk", config);
_rewarded.setListener(RewardedWrapper.this);
_rewarded.loadAd();
}
@Override
public void onAdLoadFailed(@NonNull LevelPlayAdError error) {
NeftaCustomAdapter.OnExternalMediationRequestFailed(NeftaCustomAdapter.AdType.Rewarded, _usedInsight, _requestedFloorPrice, error);
Log("onAdLoadFailed: "+ error);
_handler.postDelayed(this::GetInsightsAndLoad, 5000);
}
@Override
public void onAdLoaded(@NonNull LevelPlayAdInfo adInfo) {
NeftaCustomAdapter.OnExternalMediationRequestLoaded(NeftaCustomAdapter.AdType.Rewarded, _usedInsight, _requestedFloorPrice, adInfo);
Log("onAdLoaded " + adInfo);
_showButton.setEnabled(true);
}
private void GetInsightsAndLoad()
{
Adapter.GetInsights(Insights.Rewarded, Load, 5);
}
private void Load(Insights insights)
{
_requestedFloorPrice = 0f;
_usedInsight = insights._rewarded;
if (_usedInsight != null) {
_requestedFloorPrice = _usedInsight._floorPrice;
}
SetStatus($"Loading Interstitial with floor: {_requestedFloorPrice}");
var config = new LevelPlayRewardedAd.Config.Builder()
.SetBidFloor(_requestedFloorPrice)
.Build();
_rewarded = new LevelPlayRewardedAd(AdUnitId, config);
_rewarded.OnAdLoaded += OnAdLoaded;
_rewarded.OnAdLoadFailed += OnAdLoadFailed;
_rewarded.OnAdDisplayed += OnAdDisplayed;
_rewarded.OnAdDisplayFailed += OnAdDisplayFailed;
_rewarded.OnAdRewarded += OnAdRewarded;
_rewarded.OnAdClicked += OnAdClicked;
_rewarded.OnAdInfoChanged += OnAdInfoChanged;
_rewarded.OnAdClosed += OnAdClosed;
_rewarded.LoadAd();
}
private void OnAdLoadFailed(LevelPlayAdError error)
{
Adapter.OnExternalMediationRequestFailed(Adapter.AdType.Rewarded, _usedInsight, _requestedFloorPrice, error);
SetStatus($"OnAdLoadFailed {error}");
StartCoroutine(ReTryLoad());
}
private void OnAdLoaded(LevelPlayAdInfo info)
{
Adapter.OnExternalMediationRequestLoaded(Adapter.AdType.Rewarded, _usedInsight, _requestedFloorPrice, info);
SetStatus($"OnAdLoaded {info}");
_show.interactable = true;
}
private IEnumerator ReTryLoad()
{
yield return new WaitForSeconds(5f);
GetInsightsAndLoad();
}
Updated 28 days ago