Display AdMob ads natively inside your web UI, rendered directly within the page layout, not as a native overlay above or below it.
This feature requires a rebuild of your app after enabling Inline Ads in Despia settings. All configuration is handled automatically. No code required.
Despia’s Inline Ads feature lets you monetize your in-app web views with AdMob ads rendered inside your web UI, not overlaid on top of or injected below it.This works by connecting Google’s Mobile Ads SDK directly to the WebView (Android) or WKWebView (iOS) that Despia bundles inside your app. When your web content loads ad tags via AdSense, Google Publisher Tag (GPT), or IMA for HTML5, the SDK intercepts those events and bridges app-level signals, improving ad relevance, fill rates, and revenue.
Improved monetization
App-level signals (device ID, audience data) are passed to ad tags, improving CPMs and fill rates.
Spam protection
Google’s SDK validates ad requests from within the WebView, protecting advertisers and your account standing.
100% NoCode setup
Enable, configure, and rebuild entirely from the Despia dashboard. No native code needed.
Video ad support
Inline video ads play automatically without user interaction, fully compatible with AdSense and IMA for HTML5.
The AdMob SDK registers message handlers on your WebView/WKWebView. When your web page fires ad events through AdSense code, Google Publisher Tag, or IMA for HTML5, the SDK listens and bridges those events with native app signals.
Communication with the Google Mobile Ads SDK only happens in response to ad events triggered by AdSense code, Google Publisher Tag, or IMA for HTML5. No background tracking occurs outside of these events.
In the Despia dashboard, navigate to Your App → Settings → AdMob.
2
Enable AdMob
Toggle Enable AdMob to on.
3
Add your AdMob App ID
Paste your AdMob App ID. You can find this in your AdMob console under App settings.
Your AdMob App ID looks like ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY. Make sure you use your App ID, not an individual Ad Unit ID.
4
Rebuild your app
Click Rebuild App. Despia automatically bundles the Google Mobile Ads SDK, registers the WebView, and configures all required manifest and plist entries for both Android and iOS.
You must rebuild your app after enabling Inline Ads. The WebView API for Ads cannot be activated over-the-air. It requires the SDK to be compiled into your app binary.
These steps must be completed manually in the Despia dashboard. They cannot be automated with an AI agent prompt.
Go to Your App > Settings > AdMob
Toggle Enable AdMob to on
Paste your AdMob App ID (format: ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY)
Click Rebuild App
Despia handles all Android and iOS native configuration automatically during the rebuild. No code changes are required outside your web UI.
When you enable Inline Ads and rebuild, Despia handles all the native setup behind the scenes so you don’t have to touch any code.
Android
iOS
Despia automatically adds the following to your AndroidManifest.xml:
AndroidManifest.xml
<!-- Bypass APPLICATION_ID check for WebView APIs for Ads --><meta-data android:name="com.google.android.gms.ads.INTEGRATION_MANAGER" android:value="webview" />
It also configures your WebView with the correct settings and calls MobileAds.registerWebView() on the main thread during app startup:
Kotlin
Java
MainActivity.kt (auto-generated)
import android.webkit.CookieManagerimport android.webkit.WebViewimport com.google.android.gms.ads.MobileAds// Called in onCreate()CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true)webView.settings.javaScriptEnabled = truewebView.settings.domStorageEnabled = truewebView.settings.mediaPlaybackRequiresUserGesture = falseMobileAds.registerWebView(webView)
MainActivity.java (auto-generated)
import android.webkit.CookieManager;import android.webkit.WebView;import com.google.android.gms.ads.MobileAds;// Called in onCreate()CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true);webView.getSettings().setJavaScriptEnabled(true);webView.getSettings().setDomStorageEnabled(true);webView.getSettings().setMediaPlaybackRequiresUserGesture(false);MobileAds.registerWebView(webView);
Requirements met automatically:
Google Mobile Ads SDK ≥ 20.6.0
Android API level 21+
Third-party cookies enabled
JavaScript and DOM storage enabled
Despia automatically adds the following key to your Info.plist:
Info.plist
<!-- Indicate SDK usage is only for WebView APIs for Ads --><key>GADIntegrationManager</key><string>webview</string>
It also initializes your WKWebView with the correct configuration and calls MobileAds.shared.register(webView) on the main thread in viewDidLoad:
ViewController.swift (auto-generated)
import WebKit// Called in viewDidLoad()let webViewConfiguration = WKWebViewConfiguration()webViewConfiguration.allowsInlineMediaPlayback = truewebViewConfiguration.mediaTypesRequiringUserActionForPlayback = []webView = WKWebView(frame: view.frame, configuration: webViewConfiguration)view.addSubview(webView)MobileAds.shared.register(webView)
Once Inline Ads is enabled and your app is rebuilt, you serve ads from within your web UI using standard Google ad tags. No special Despia SDK calls are needed on the web side.
AdSense
Add your AdSense auto ads snippet or individual ad units to your HTML. The SDK will automatically bridge app signals.
Use the IMA HTML5 SDK for in-stream and outstream video ads. Combined with the Video.js IMA plugin, you can serve pre-roll, mid-roll, and post-roll ads.
<!-- Video.js 8.23.8 --><link href="https://vjs.zencdn.net/8.23.8/video-js.min.css" rel="stylesheet" /><script src="https://vjs.zencdn.net/8.23.8/video.min.js"></script><!-- Google IMA SDK --><script src="https://imasdk.googleapis.com/js/sdkloader/ima3.js"></script><!-- videojs-contrib-ads 7.5.2 (required by videojs-ima) --><link href="https://cdnjs.cloudflare.com/ajax/libs/videojs-contrib-ads/7.5.2/videojs.ads.min.css" rel="stylesheet" /><script src="https://cdnjs.cloudflare.com/ajax/libs/videojs-contrib-ads/7.5.2/videojs.ads.min.js"></script><!-- videojs-ima 2.4.0 --><link href="https://cdnjs.cloudflare.com/ajax/libs/videojs-ima/2.4.0/videojs.ima.css" rel="stylesheet" /><script src="https://cdnjs.cloudflare.com/ajax/libs/videojs-ima/2.4.0/videojs.ima.min.js"></script><video id="my-video" class="video-js vjs-default-skin" controls playsinline> <source src="YOUR_VIDEO_URL.mp4" type="video/mp4" /></video><script> var player = videojs('my-video'); player.ima({ adTagUrl: 'YOUR_AD_TAG_URL' });</script>
Ads play inline. no fullscreen takeover.
Autoplay is supported because Despia configures the WebView to allow it.
Despia’s native runtime automatically injects two CSS variables into every WebView:
Variable
What it measures
var(--safe-area-top)
Status bar, notch, Dynamic Island
var(--safe-area-bottom)
Home indicator, gesture bar
These values update in real time when the device orientation changes. No package installation, no native code, and no configuration is required. The variables are available as soon as your web UI loads inside the Despia runtime.This is directly relevant to Inline Ads because any banner positioned at the top or bottom of the viewport must account for these insets. A bottom banner that ignores var(--safe-area-bottom) will be partially hidden behind the home indicator on modern iPhones. A top banner that ignores var(--safe-area-top) will render under the status bar.
The CSS variables var(--safe-area-top) and var(--safe-area-bottom) are provided by the Despia runtime only. They will be 0 or undefined when running in a standard desktop browser, so layout will appear unaffected during local development. Always verify safe area behaviour on a physical device or simulator running your Despia-built app.
Apply safe area insets to top and bottom ad banners. Despia injects var(--safe-area-top) and var(--safe-area-bottom) into every WebView. Any banner at the top or bottom of the viewport must use these values or it will be obscured by the notch, status bar, or home indicator. Apply the inset to the wrapping container, not to the ad unit itself.
This is where Inline Ads becomes genuinely powerful. Because ads are rendered inside your web UI. as real DOM elements. you can position them anywhere a <div> can go. Layouts that are flat-out impossible with native overlay ads become trivial.
All examples below use React and plain HTML. The same patterns adapt directly to Vue, Svelte, Angular, Astro, or any other framework. just translate the component structure and lifecycle hooks to your framework of choice.
In a standard WebView-based app, the native navigation bar sits outside the WebView. meaning you can’t place an ad between the nav bar and your web content without a native overlay, which causes z-index fights, safe area headaches, and layout jank. With Inline Ads, you render the banner inside the WebView directly beneath your web nav bar, as a real part of your page layout.
<style> body { margin: 0; display: flex; flex-direction: column; height: 100vh; } nav { height: 56px; background: #1a1a2e; color: #fff; display: flex; align-items: center; padding: 0 16px; flex-shrink: 0; } .ad-strip { width: 100%; overflow: hidden; flex-shrink: 0; } main { flex: 1; overflow-y: auto; padding: 16px; }</style><!-- AdSense script — place once in <head> --><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js" data-ad-client="ca-pub-XXXXXXXXXXXXXXXX"></script><nav>My App</nav><!-- Banner lives inside the WebView, flush under the nav --><div class="ad-strip"> <ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-XXXXXXXXXXXXXXXX" data-ad-slot="YYYYYYYYYY" data-ad-format="auto" data-full-width-responsive="true"></ins> <script>(adsbygoogle = window.adsbygoogle || []).push({});</script></div><main> <h1>Welcome</h1> <p>Your page content here...</p></main>
Use data-full-width-responsive="true" so the banner fills the full device width automatically, exactly like a native banner ad unit. If your navigation bar is fixed or the banner sits at the very top of the layout, add a spacer div with height: var(--safe-area-top) above the nav to keep content clear of the notch and status bar. See the Safe areas section above.
Place a banner ad directly below your navigation bar. Normally impossible in a WebView app without a native overlay. With Inline Ads the banner is a real DOM element positioned with flexbox below the nav bar and above the scrollable content. Provide your AdSense publisher ID and slot ID.
2. Sticky banner pinned to the bottom of the screen
A classic mobile ad placement. a persistent leaderboard banner anchored to the bottom of the viewport. In pure WebView apps this normally requires a native view rendered outside the WebView. Here it’s just CSS.
React
HTML
BottomBannerLayout.jsx
export default function BottomBannerLayout({ children }) { return ( <div style={{ display: "flex", flexDirection: "column", height: "100vh" }}> {/* Scrollable content takes all available space */} <main style={{ flex: 1, overflowY: "auto", padding: 16 }}> {children} </main> {/* Sticky bottom banner - part of the web layout, not a native overlay */} <div style={{ position: "sticky", bottom: 0, width: "100%", background: "#fff", borderTop: "1px solid #e0e0e0", flexShrink: 0, }}> <ins className="adsbygoogle" style={{ display: "block" }} data-ad-client="ca-pub-XXXXXXXXXXXXXXXX" data-ad-slot="YYYYYYYYYY" data-ad-format="auto" data-full-width-responsive="true" /> <script> {`(adsbygoogle = window.adsbygoogle || []).push({});`} </script> </div> </div> );}
index.html
<style> body { margin: 0; display: flex; flex-direction: column; height: 100vh; } main { flex: 1; overflow-y: auto; padding: 16px; } .bottom-banner { position: sticky; bottom: 0; width: 100%; background: #fff; border-top: 1px solid #e0e0e0; flex-shrink: 0; }</style><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js" data-ad-client="ca-pub-XXXXXXXXXXXXXXXX"></script><main> <h1>Your content</h1> <p>Lots of scrollable content...</p></main><!-- Pinned to the bottom of the WebView viewport --><div class="bottom-banner"> <ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-XXXXXXXXXXXXXXXX" data-ad-slot="YYYYYYYYYY" data-ad-format="auto" data-full-width-responsive="true"></ins> <script>(adsbygoogle = window.adsbygoogle || []).push({});</script></div>
Pin a persistent banner to the bottom of the viewport. Implemented as a sticky flex child in CSS with no native overlay required. Apply padding-bottom: var(--safe-area-bottom) to the banner container to clear the home indicator on iOS. Provide your AdSense publisher ID and slot ID.
Ads interspersed between list items or content cards. the same pattern used by Instagram, Twitter, and most news apps. Each nth card is replaced with an ad unit that matches the card dimensions.
Use data-ad-format="fluid" with data-ad-layout="in-article" for ads that automatically size themselves to blend naturally between content items.
Insert AdSense ads between every nth card in a content feed. Uses the fluid in-article format so the unit sizes to match surrounding content. Provide your feed array name, AdSense publisher ID, slot ID, and the interval between ads (default: every 3rd item).
A full-screen ad that appears between user actions. e.g., after finishing an article, between levels in a game, or when navigating between pages. This is rendered entirely in your web UI as an overlay <div>, triggered by JavaScript. The user dismisses it and continues.
AdSense and Ad Manager policies require that interstitial-style placements must be clearly dismissible and must not trap users. Always include a visible close mechanism. Review AdSense program policies before deploying.
Show a full-screen ad overlay between user actions. Renders a fixed-position overlay inside the WebView triggered by JavaScript. A countdown runs before the close button appears. Subject to AdSense interstitial policy: the overlay must be dismissible. Provide your publisher ID, slot ID, trigger action, and countdown duration.
An ad unit that sits naturally between paragraphs of an article or long-form content page. the format used by major publishers. It scrolls with the content and loads only when it enters the viewport.
React
HTML
Article.jsx
function InArticleAd() { return ( <div style={{ margin: "24px 0", textAlign: "center" }}> <p style={{ fontSize: 10, color: "#999", marginBottom: 4 }}>Advertisement</p> <ins className="adsbygoogle" style={{ display: "block", textAlign: "center" }} data-ad-client="ca-pub-XXXXXXXXXXXXXXXX" data-ad-slot="YYYYYYYYYY" data-ad-format="fluid" data-ad-layout="in-article" /> <script> {`(adsbygoogle = window.adsbygoogle || []).push({});`} </script> </div> );}export default function Article() { return ( <article style={{ maxWidth: 680, margin: "0 auto", padding: 16 }}> <h1>The Future of Mobile Apps</h1> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p> {/* Ad sits naturally between paragraphs, scrolls with content */} <InArticleAd /> <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p> <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p> </article> );}
article.html
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js" data-ad-client="ca-pub-XXXXXXXXXXXXXXXX"></script><style> article { max-width: 680px; margin: 0 auto; padding: 16px; } .in-article-ad { margin: 24px 0; text-align: center; } .ad-label { font-size: 10px; color: #999; margin-bottom: 4px; }</style><article> <h1>The Future of Mobile Apps</h1> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p> <!-- Mid-article ad - scrolls with content, loads on scroll into view --> <div class="in-article-ad"> <p class="ad-label">Advertisement</p> <ins class="adsbygoogle" style="display:block;text-align:center" data-ad-client="ca-pub-XXXXXXXXXXXXXXXX" data-ad-slot="YYYYYYYYYY" data-ad-format="fluid" data-ad-layout="in-article"></ins> <script>(adsbygoogle = window.adsbygoogle || []).push({});</script> </div> <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p></article>
Inject a fluid banner between paragraphs of an article. Uses data-ad-format set to fluid and data-ad-layout set to in-article so the unit sizes automatically to the reading column and loads on scroll. Specify the paragraph position and provide your AdSense publisher ID and slot ID.
A video ad that plays before your main content video. the same pattern used by YouTube. Using Video.js + the IMA plugin, the ad plays inline within your web UI, the user can skip after 5 seconds (if configured), and then your content video starts automatically.
React
HTML
VideoWithPreroll.jsx
import { useEffect, useRef } from "react";export default function VideoWithPreroll() { const videoRef = useRef(null); useEffect(() => { if (!videoRef.current) return; // videojs and videojs-ima are loaded via <script> tags in your HTML <head> const player = window.videojs(videoRef.current, { controls: true, autoplay: true, muted: true, // Required for autoplay on most browsers playsinline: true, // Required for inline playback in iOS WebView }); // Attach the IMA plugin - pre-roll ad plays before content player.ima({ adTagUrl: "https://pubads.g.doubleclick.net/gampad/ads?YOUR_AD_TAG_PARAMS", }); return () => player.dispose(); }, []); return ( <div style={{ padding: 16 }}> <h2>Watch: Product Overview</h2> {/* Video.js + IMA handles the pre-roll ad automatically */} <div data-vjs-player> <video ref={videoRef} className="video-js vjs-default-skin vjs-big-play-centered" style={{ width: "100%", borderRadius: 8 }} playsinline > <source src="https://example.com/your-content-video.mp4" type="video/mp4" /> </video> </div> </div> );}
video.html
<!-- Load Video.js and IMA plugin in <head> --><link href="https://vjs.zencdn.net/8.23.8/video-js.min.css" rel="stylesheet" /><script src="https://vjs.zencdn.net/8.23.8/video.min.js"></script><script src="https://imasdk.googleapis.com/js/sdkloader/ima3.js"></script><link href="https://cdnjs.cloudflare.com/ajax/libs/videojs-contrib-ads/7.5.2/videojs.ads.min.css" rel="stylesheet" /><script src="https://cdnjs.cloudflare.com/ajax/libs/videojs-contrib-ads/7.5.2/videojs.ads.min.js"></script><link href="https://cdnjs.cloudflare.com/ajax/libs/videojs-ima/2.4.0/videojs.ima.css" rel="stylesheet" /><script src="https://cdnjs.cloudflare.com/ajax/libs/videojs-ima/2.4.0/videojs.ima.min.js"></script><style> .video-wrap { padding: 16px; } video { width: 100%; border-radius: 8px; }</style><div class="video-wrap"> <h2>Watch: Product Overview</h2> <!-- Pre-roll ad plays inline before the content video --> <video id="content-video" class="video-js vjs-default-skin vjs-big-play-centered" controls autoplay muted playsinline > <source src="https://example.com/your-content-video.mp4" type="video/mp4" /> </video></div><script> var player = videojs("content-video"); player.ima({ adTagUrl: "https://pubads.g.doubleclick.net/gampad/ads?YOUR_AD_TAG_PARAMS", });</script>
Despia configures the WebView with mediaPlaybackRequiresUserGesture = false (Android) and mediaTypesRequiringUserActionForPlayback = [] (iOS), so video ads autoplay without requiring a tap. exactly as you’d expect from a native video ad.
Play a pre-roll video ad before a content video. Uses Video.js 8.23.8, the Google IMA SDK, videojs-contrib-ads 7.5.2, and videojs-ima 2.4.0. Despia configures the WebView so the ad autoplays without a user tap on both Android and iOS. Provide your ad tag URL and content video URL.
Show an ad in exchange for unlocking gated content. a premium article, extra lives in a game, a discount code, or a download. The user opts in, watches the ad, and your UI programmatically reveals the content. This entire flow runs in your web UI with no native layer involvement.
React
HTML
RewardedContent.jsx
import { useState } from "react";function AdGate({ onUnlock }) { const [watching, setWatching] = useState(false); const [countdown, setCountdown] = useState(null); function startAd() { setWatching(true); // Push the ad unit (window.adsbygoogle = window.adsbygoogle || []).push({}); // Simulate a 10s ad view then unlock let n = 10; setCountdown(n); const timer = setInterval(() => { n--; setCountdown(n); if (n <= 0) { clearInterval(timer); onUnlock(); } }, 1000); } return ( <div style={{ border: "2px dashed #ccc", borderRadius: 12, padding: 24, textAlign: "center", }}> <p style={{ fontSize: 18, fontWeight: 600 }}>Premium content (locked)</p> <p style={{ color: "#666" }}>Watch a short ad to read this article for free.</p> {!watching ? ( <button onClick={startAd} style={{ background: "#4CAF50", color: "#fff", border: "none", borderRadius: 6, padding: "10px 24px", fontSize: 16, cursor: "pointer", }}> Watch ad to unlock </button> ) : ( <div> <ins className="adsbygoogle" style={{ display: "block", width: 320, height: 250, margin: "16px auto" }} data-ad-client="ca-pub-XXXXXXXXXXXXXXXX" data-ad-slot="YYYYYYYYYY" /> <p style={{ color: "#666" }}> {countdown > 0 ? `Unlocking in ${countdown}s` : "Unlocked"} </p> </div> )} </div> );}export default function PremiumArticle() { const [unlocked, setUnlocked] = useState(false); return ( <article style={{ maxWidth: 680, margin: "0 auto", padding: 16 }}> <h1>The Secret to 10x Growth</h1> <p>Here's the introduction everyone can read...</p> {/* The rest of the article is gated behind the ad */} {unlocked ? ( <p>Here is all the premium content, now fully visible after watching the ad.</p> ) : ( <AdGate onUnlock={() => setUnlocked(true)} /> )} </article> );}
rewarded.html
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js" data-ad-client="ca-pub-XXXXXXXXXXXXXXXX"></script><style> article { max-width: 680px; margin: 0 auto; padding: 16px; } #ad-gate { border: 2px dashed #ccc; border-radius: 12px; padding: 24px; text-align: center; } #premium-content { display: none; } #ad-unit { display: none; margin: 16px auto; width: 320px; height: 250px; } #unlock-btn { background: #4CAF50; color: #fff; border: none; border-radius: 6px; padding: 10px 24px; font-size: 16px; cursor: pointer; }</style><article> <h1>The Secret to 10x Growth</h1> <p>Here's the introduction everyone can read...</p> <!-- Ad gate - entirely inside the WebView --> <div id="ad-gate"> <p style="font-size:18px;font-weight:600">Premium content (locked)</p> <p style="color:#666">Watch a short ad to read this article for free.</p> <button id="unlock-btn" onclick="startAd()">Watch ad to unlock</button> <ins id="ad-unit" class="adsbygoogle" style="display:block" data-ad-client="ca-pub-XXXXXXXXXXXXXXXX" data-ad-slot="YYYYYYYYYY"></ins> <p id="countdown-label" style="color:#666;display:none"></p> </div> <div id="premium-content"> <p>Here is all the premium content, now fully visible after watching the ad.</p> </div></article><script> function startAd() { document.getElementById("unlock-btn").style.display = "none"; const adUnit = document.getElementById("ad-unit"); adUnit.style.display = "block"; (adsbygoogle = window.adsbygoogle || []).push({}); let n = 10; const label = document.getElementById("countdown-label"); label.style.display = "block"; label.textContent = `Unlocking in ${n}s…`; const timer = setInterval(() => { n--; if (n <= 0) { clearInterval(timer); label.textContent = "Unlocked"; setTimeout(unlock, 500); } else { label.textContent = `Unlocking in ${n}s…`; } }, 1000); } function unlock() { document.getElementById("ad-gate").style.display = "none"; document.getElementById("premium-content").style.display = "block"; }</script>
Gate premium content behind a watched ad. The user opts in, an AdSense unit mounts inside the WebView, a countdown runs, and JavaScript reveals the locked content automatically on completion. No native layer involved. Provide your publisher ID, slot ID, what is being unlocked, and the countdown duration.
On tablet-sized screens or landscape layouts, render a persistent ad in a side rail next to your content , a format common on desktop web but rare in mobile apps. With Inline Ads it is just a flexbox column.
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js" data-ad-client="ca-pub-XXXXXXXXXXXXXXXX"></script><style> .layout { display: flex; padding: 16px; max-width: 960px; margin: 0 auto; } main { flex: 1; min-width: 0; } .sidebar-ad { width: 160px; flex-shrink: 0; margin-left: 16px; position: sticky; top: 16px; align-self: flex-start; } .sidebar-ad .label { font-size: 10px; color: #999; margin-bottom: 4px; }</style><div class="layout"> <main> <h1>Main content</h1> <p>Your article or app content here. It scrolls while the ad stays fixed in the rail.</p> </main> <!-- Sticky sidebar ad - inside the WebView, no native layer needed --> <div class="sidebar-ad"> <p class="label">Ad</p> <ins class="adsbygoogle" style="display:block;width:160px;height:600px;" data-ad-client="ca-pub-XXXXXXXXXXXXXXXX" data-ad-slot="YYYYYYYYYY"></ins> <script>(adsbygoogle = window.adsbygoogle || []).push({});</script> </div></div>
Combine this with a CSS media query (@media (min-width: 600px)) to only show the sidebar ad on tablets and landscape phones, and fall back to a banner ad on portrait mobile.
Add a sticky 160x600 sidebar ad for tablet and landscape layouts. A two-column flex layout where the ad sits in a sticky right rail. Hidden below 600px viewport width via a CSS media query to avoid the ad appearing on portrait phones. Provide your AdSense publisher ID and slot ID.
Framework note: All examples above are written in React and plain HTML. The exact same patterns work in Vue (use onMounted to push the ad unit), Svelte (use onMount), Angular (use ngAfterViewInit), Astro, Solid, and any other framework. The key rule in every case is the same: push the ad unit to window.adsbygoogleafter the container element is mounted in the DOM.
The Google WebView Ads test page verifies that your WebView is correctly registered with the Google Mobile Ads SDK. Because the test must run inside your app’s WebView context, you cannot simply open it in a desktop browser.
The recommended approach is to add a hidden link to an existing page in your app, such as a profile page or settings screen, and tap it during development. Despia allows the test URL to load inside the WebView context specifically for this purpose.Add a link like the following to any page in your web UI. Keep it discreet or remove it before releasing to production.
React
HTML
DebugLink.jsx
// Add to your profile page or settings screen during development// Remove or hide behind an admin flag before releasing to productionexport default function DebugAdLink() { return ( <a href="https://google.github.io/webview-ads/test/#api-for-ads-tests" style={{ fontSize: 11, color: "#999" }} > Ad SDK debug </a> );}
<!-- Add to your profile page or settings screen during development --><!-- Remove or hide behind an admin flag before releasing to production --><a href="https://google.github.io/webview-ads/test/#api-for-ads-tests" style="font-size:11px;color:#999;"> Ad SDK debug</a>
Tapping the link loads the test page inside your app’s WebView, which means the Google Mobile Ads SDK is active and the test results reflect your real integration.
A green status bar for each item confirms a successful integration.
Test
What it verifies
WebView connected to GMA SDK
Core SDK registration is successful
JavaScript enabled
JS is enabled in the WebView
DOM storage enabled
localStorage is available
First-party cookies
1P cookies are set and readable
Third-party cookies
3P cookies work (Android)
Google Publisher Tag connected
GPT → SDK bridge is active
Opening the test URL in a desktop browser or a standard browser tab will always show a failed connection. The SDK bridge only exists inside the Despia-built app WebView. Always test on a physical device or simulator running your rebuilt app.
If the WebView connected status shows as failed, confirm you rebuilt your app after enabling Inline Ads in Despia. The SDK registration requires a native rebuild and cannot be applied over-the-air.
Inline Ads does not automatically propagate consent signals collected in the mobile app context (IAB TCF v2.0 or IAB CCPA) to the tags running inside your web view.If you need a unified consent flow across your native app and web content:
Work with your Consent Management Platform (CMP) to gather consent directly in the WebView context.
Alternatively, implement a consent bridge that passes TCF/CCPA strings from your native app into the WebView via JavaScript before ads load.
For users in the EEA (GDPR) or US states with privacy laws, you must ensure your web-based ad tags collect consent independently. Despia’s native UMP consent flow does not automatically extend into WebView contexts.
Do I need a separate AdMob App ID for iOS and Android?
Yes. AdMob requires separate App IDs for each platform. Add both in Despia → Settings → AdMob. Despia will use the correct one for each platform when building.
Can I use Ad Manager (GAM) instead of AdSense?
Yes. Inline Ads supports Google Publisher Tag, which works with both AdSense and Google Ad Manager. Simply use your GAM ad unit paths when defining slots with GPT.
Will ads affect my app's WebView performance?
Minimal impact. The SDK only communicates with AdMob servers in response to ad events. There is no background polling or persistent connection. Standard WebView performance best practices (caching, lazy loading) apply as normal.
Can I show rewarded or interstitial ads from inside the web view?
Inline Ads is designed for display, banner, and video ad formats embedded in web content (via AdSense, GPT, or IMA). For rewarded and interstitial formats, those need to be triggered from the native app layer, not from within the WebView.
Why do I need to rebuild the app?
The AdMob SDK must be compiled into your app binary and registered with the WebView during app startup. This cannot be done over-the-air. Despia handles all the native code. You just trigger the rebuild.