Consent in WebViews: Keeping Native and Web Consent in Sync

The Double-Banner Problem

Many apps are partly native and partly web. A game shows its store, news or help pages in a WebView; a utility app renders articles from its website; a hybrid app wraps most of its interface in web content. Each side has its own consent mechanism: the native app uses an in-app CMP or SDK, and the web pages load a web CMP. Unless they are connected, the user is asked twice, choices made in one place are ignored in the other, and ad requests from inside the WebView go out with the wrong signals.

This guide covers how to make native and web consent agree, so users answer once and every ad request carries the same decision.

Where Each Side Stores Consent

Native

The IAB TCF specifies how an in-app CMP stores consent: in NSUserDefaults on iOS and SharedPreferences on Android, using standard keys such as IABTCF_TCString, IABTCF_gdprApplies and IABTCF_PurposeConsents. Ad SDKs read these keys directly. The IAB GPP defines similar keys with an IABGPP_ prefix, such as IABGPP_HDR_GppString.

Web

A web CMP typically stores its decision in first-party cookies or local storage for the site’s domain, and exposes it through the __tcfapi and __gpp JavaScript functions. Ad tags in the page call those functions, not native storage.

The gap

WebViews have their own cookie and storage space, separate from the device browser and separate from the app’s native preferences. On iOS, a WKWebView using a non-persistent data store forgets everything when it closes. Nothing flows between the two sides unless you build it.

Pick One Source of Truth

The first decision is which side owns consent. For most apps the answer is the native CMP, because:

Web-first can make sense for hybrid apps whose entire interface is web content, but then you must write the decision back to native storage for native SDKs.

Passing Native Consent Into the WebView

With native as the source of truth, the WebView needs to see the same decision. Common patterns:

  1. Inject a __tcfapi implementation. Before the page’s scripts run, inject a small script that defines __tcfapi (and __gpp if needed) and answers calls with the TC string and values read from native storage. Ad tags in the page then get the native decision through the standard API.
  2. Suppress the web banner. Configure the web CMP to stay hidden when the page is loaded inside your app, for example based on a query parameter or a custom user agent token you control. Otherwise the user sees a second banner.
  3. Pass the string on load. Some web CMPs can accept an existing consent string at initialisation and adopt it. Check your web CMP’s documentation for supported methods.

Whichever pattern you use, make sure the injected values update when the user changes their choice in the native settings screen, not only on the next app launch.

Passing Web Consent Back to Native

If users can change consent inside the WebView, for instance through a privacy settings page, the app must learn about it. Use a JavaScript bridge: a message handler on iOS or a JavaScript interface on Android. The web side listens with __tcfapi('addEventListener', ...) and sends the new string to native code, which writes the standard keys. Validate input from the WebView before writing it; only accept messages from your own content.

Ads Inside WebViews

Ads rendered in a WebView are web ads. They read consent from the page, not from native storage, unless the ad SDK provides a way to link the two. Google’s Mobile Ads SDK, for example, offers an API to register WebViews so that ads inside them can use signals from the app; check the current documentation for your SDK. In any case, confirm with a test that ad requests from the WebView carry the same TC or GPP string as native ad requests.

Testing Checklist

Key Takeaways

← Blog Read All →