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.

  1. Context
  2. Setup
    1. Pre-requisites
    2. App Configuration

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 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.

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!

// 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>