Step 4: React Native (WebView)
There is no native AppLixir module for React Native. You run the HTML5 SDK
inside a WebView (react-native-webview). This works well — but only if the
WebView has a real web origin.
The single most common reason ads do not fill in React Native is loading the SDK
from inline HTML (source={{ html: "…" }}) or a local file. That gives the
page the origin "null", which breaks the two things the SDK depends on:
- Consent. The TCF/GPP consent manager communicates over
postMessage. With anullorigin you getFailed to execute 'postMessage' … Invalid target origin 'null'and no consent string is ever produced. - Demand. The ad server matches the request to your registered domain
via the referrer. A
nullorigin matches nothing, so the request returnsAdError 303: No Ads VAST response— an empty fill.
Fix: host your ad page on the same https:// domain you registered with
AppLixir (the one in your ads.txt) and point the WebView at it with
source={{ uri }}.
The pattern
Host a small page on your domain (for example https://yourgame.com/rewarded)
that loads the SDK and posts a message back to React Native when the user earns
the reward. Then render it in a WebView.
1. The hosted page
Served from https://yourgame.com/rewarded:
<!doctype html>
<html>
<head>
<script src="https://cdn.applixir.com/applixir.app.v6.1.0.js" async></script>
</head>
<body>
<div id="applixir-ad-container"></div>
<button id="watch">Watch ad</button>
<script>
function send(msg) {
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(JSON.stringify(msg));
}
}
document.getElementById("watch").addEventListener("click", () => {
initializeAndOpenPlayer({
apiKey: "YOUR-API-KEY",
injectionElementId: "applixir-ad-container",
adStatusCallbackFn: (status) => {
if (status.type === "complete") send({ type: "reward" });
else if (["skipped", "manuallyEnded", "allAdsCompleted"].includes(status.type)) {
send({ type: "closed", reason: status.type });
}
},
adErrorCallbackFn: (error) => send({ type: "error", data: error.getError().data }),
});
});
</script>
</body>
</html>
2. The React Native screen
import { WebView } from "react-native-webview";
export function RewardedAd({ onReward }) {
return (
<WebView
// ✅ A real https origin — consent and demand both work.
source={{ uri: "https://yourgame.com/rewarded" }}
// ❌ Never: source={{ html: "<html>…</html>" }} → origin "null" → no fill.
javaScriptEnabled
domStorageEnabled
thirdPartyCookiesEnabled // Android: needed for consent + ad serving
sharedCookiesEnabled // iOS: share the app cookie store
allowsInlineMediaPlayback // iOS: play the video inline, not fullscreen-only
mediaPlaybackRequiresUserAction={false}
originWhitelist={["https://*"]}
mixedContentMode="always" // Android
onMessage={(event) => {
const msg = JSON.parse(event.nativeEvent.data);
if (msg.type === "reward") onReward?.();
if (msg.type === "error") console.warn("AppLixir error:", msg.data);
}}
/>
);
}
WebView props that matter
| Prop | Platform | Why |
|---|---|---|
source={{ uri }} | both | Real origin — the whole integration depends on this |
javaScriptEnabled | both | The SDK is JavaScript |
domStorageEnabled | Android | Consent + frequency state use storage |
thirdPartyCookiesEnabled | Android | Consent handshake and ad serving need cookies |
sharedCookiesEnabled | iOS | Reuse the app's cookie store (WKWebView) |
allowsInlineMediaPlayback | iOS | Without it, video is forced fullscreen |
mediaPlaybackRequiresUserAction={false} | both | The user already tapped to open the player |
mixedContentMode="always" | Android | Avoid blocking sub-resources |
Checklist
- WebView loads a registered
https://domain viasource={{ uri }}— not inline HTML, notfile://. - That domain serves your
ads.txt(see Step 6). - JavaScript, DOM storage, and cookies are all enabled (table above).
- The ad is triggered by a tap inside the WebView, not auto-played on load.
- The persistent reward is granted from the server-side web callback (see Step 5); the
rewardmessage is optimistic UI only.
If you still see Invalid target origin 'null' or AdError 303 after this, the
WebView is not actually loading from your https domain — re-check that you are
using source={{ uri }} and not an inline-HTML or bundled file.
Next: Callbacks & rewards.