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:
- It runs first, before any WebView is opened.
- Native ad SDKs, which usually earn most of the revenue, read native storage.
- On iOS, it can be coordinated with the App Tracking Transparency prompt.
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:
- Inject a
__tcfapiimplementation. Before the page’s scripts run, inject a small script that defines__tcfapi(and__gppif 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. - 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.
- 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
- Fresh install: one consent prompt, not two.
- Reject all in the native CMP, open a WebView, inspect ad requests: the string should reflect the rejection.
- Change consent in native settings, return to an open WebView: the page should see the new decision.
- If web settings exist, change consent there and confirm the native keys update.
- Kill and relaunch the app: decisions persist on both sides.
- Test on both iOS and Android, since WebView storage behaves differently on each.
Key Takeaways
- Native and WebView consent live in separate stores and do not sync by default.
- Choose one source of truth; for most apps it is the native CMP.
- Expose native consent to web content through an injected
__tcfapior__gppand hide the web banner. - Use a JavaScript bridge to send web changes back to native storage.
- Check that ad requests from WebViews carry the same consent string as native ones.