Webview Monetization
This page explain how to monetize a webview within a native mobile application. This page does not involve the SDK, only webviews in native application environment.
Context
For the sake of simplicity and consistency in their content management / user experience, some media publishers display a lot of content of their apps (such as news, articles) inside webviews, just like on mobile-web. The native app container mostly serve as a directory / “additional features” provider, but end users will consume mobile-web.
To monetize their mobile-web inventory, some publishers use smart.js and “mobile-web” formats, but the monetization is poor due to cookies restrictions in native apps webviews (not shared, not persistant).
To enhance the monetization, the publisher can requalify the inventory as “In-app” when passing the parameters AppName, BundleId and User IFA to the delivery engine, which will then forward those informations to the DSPs.
Setup
Pre-requisites
This setup makes sense only for publishers who:
- monetize their mobile-web inventory through smart.js (Standard or OneCall).
- display part of this inventory in a native application (iOS / Android).
App Configuration
Android App
The publisher should retrieve AppName, BundleId, IFA and GDPR Consent String and pass them to the WebView
via JavascriptInterface
` before the content of the webview is loaded.
It is up to the publisher to forward these information to the webview again if the consent string has changed.
Important note: this native implementation relies on the Android Webview API that allows native code to be called from javascript code running in the Webview. This is discouraged by Google if you do not fully control the html code that will be executed by the Webview (source), and might lead to Play Store rejection.
First, create a AsyncTask child class to handle the Javascript Interface.
private class AdvertisingIdAsyncTask(
private val context: Context,
private val appName: String,
private val bundleId: String
) : AsyncTask<Void?, Void?, String?>() {
private var advertisingId: String = ""
init {
// self execute task
execute()
}
@Synchronized
override fun doInBackground(vararg p0: Void?): String {
// background code to fetch Advertising Id (must be run outside of main thread per google doc)
var adInfo: AdvertisingIdClient.Info? = null
try {
adInfo = AdvertisingIdClient.getAdvertisingIdInfo(context.applicationContext)
advertisingId = adInfo.id ?: ""
} catch (e: java.lang.Exception) {
// ...
}
return advertisingId
}
@JavascriptInterface
fun getAppName(): String {
return appName
}
@JavascriptInterface
fun getBundleId(): String {
return bundleId
}
@JavascriptInterface
fun getTcfString(): String {
// In-app TCF v2 retrieved from the SharedPreferences (if set by an in-app CMP)
val sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
val tcfString: String = sharedPreferences.getString("IABTCF_TCString", "") ?: ""
return tcfString
}
@JavascriptInterface
@Synchronized
fun getAdvertisingId(): String {
return advertisingId
}
}
Then, before the webview load its content, retrieve all needed data and pass them to this new AsyncTask that will act as Javascript interface.
val packageManager: PackageManager = context.packageManager
val applicationInfo = packageManager.getApplicationInfo(packageName, 0)
// Identification variables
val appName: String = packageManager.getApplicationLabel(applicationInfo) as String
val packageName: String = context.applicationContext.packageName
// AsyncTask that will act as Javascript interface
val asyncTask = AdvertisingIdAsyncTask(context, appName, packageName)
// Enable JavaScript then actually add the javascript interface
val settings = webview.settings
settings.javaScriptEnabled = true
webview.addJavascriptInterface(asyncTask, "SASIdProvider")
// Note that loading the request should happen after the injection of the variable since the
// Smartjs API require the variables in its setup method.
// Adding the user script after the page loading might prevent SmartJS to start and block ads completely.
webview.loadUrl("https://your-content-url.com")
iOS App
The publisher should first configure its webview in order to be able to display ads properly (including video ads), then retrieve the app name, the bundle ID, the advertising ID and the GDPR consent string and pass them to the webview before the content is loaded.
We recommend to use the IDFA with a fallback on the IFV as the advertising ID (because the IDFA requires user consent and the IFV is sufficient to ensure some features like frequency capping are working properly). We also recommend to inject the data using a WKUserScript
before the actual content page is loaded!
It is up to the publisher to forward these information to the webview again if the consent string has changed.
// Webview configuration
webView.configuration.allowsInlineMediaPlayback = true
webView.configuration.websiteDataStore = WKWebsiteDataStore.default()
// Generic app info
let bundle = Bundle.main.bundleIdentifier!
let appName = Bundle.main.object(forInfoDictionaryKey: String(kCFBundleNameKey))!
// Advertising ID (IDFA by default with an IFV fallback to ensure that capping is working properly)
// Note that you must ask for user permission to retrieve the IDFA, check 'App Tracking Transparency'
// section of the iOS 'Getting Started' guide of the official documentation for more information
let advertisingID = ASIdentifierManager.shared().advertisingIdentifier.description != "00000000-0000-0000-0000-000000000000" ?
ASIdentifierManager.shared().advertisingIdentifier.description :
UIDevice.current.identifierForVendor?.description ?? ""
// GDPR consent string if available
let tcfString = UserDefaults.standard.object(forKey: "IABTCF_TCString") ?? ""
// Sending the data to the webview using an user script
let sasJavascript = """
class SASIdProvider {
static getAdvertisingId() { return '\(advertisingID)'; };\
static getAppName() { return '\(appName)'; };\
static getBundleId() { return '\(bundle)'; };\
static getTcfString() { return '\(tcfString)'; };\
};
"""
let sasScript = WKUserScript(source: sasJavascript, injectionTime: .atDocumentStart, forMainFrameOnly: true)
webView.configuration.userContentController.addUserScript(sasScript)
// Note that the user script should be set on the webview BEFORE the page is loaded since the
// SmartJS API require the variables in its setup method.
// Adding the user script after the page loading might prevent SmartJS to start and block ads completely.
webView.load(URLRequest(url: URL(string: "https://domain/content.html")!))
smart.js Configuration
The publisher should setup smart.js this way:
<script type="application/javascript">
sas.setup({ networkid: PUB_NETWORK_ID, domain: "PUB_WEBSITE_DOMAIN", uid: SASIdProvider.getAdvertisingId(), bundleId: SASIdProvider.getBundleId(), appName: SASIdProvider.getAppName() });
sas.setGdprConsentData(SASIdProvider.getTcfString());
</script>
Note that SASIdProvider
is the object passed by the native application to the webview.