MoonetixMoonetixDocs Support Log in

DocsMobile SDKs

View as Markdown

React Native SDK

Show the offerwall in your React Native or Expo app on Android and iOS. Your server creates a short, single-use session, and the package opens the offerwall with it, so no key ever ships inside your app.

How it works

  1. When the user taps your Earn button, your app asks your own server for a session.
  2. Your server calls POST /v1/session with your API key and the user’s ID, and passes the token to the app.
  3. The app calls Moonetix.open(token). The package opens the offerwall in a native screen.
  4. Rewards reach your server through your postback, exactly as with the offerwall link. See Postbacks.

A token works once and for 10 minutes. Create a new one each time the user opens the offerwall. Your API key and your link hash salt stay on your server, so nothing in your app can be used to open another user’s offerwall.

PlatformStatus
AndroidSupported. The package uses the Android SDK inside.
iOSSupported. The package uses the iOS SDK inside. Build with Xcode 16 or newer.
ExpoSupported in development builds, npx expo prebuild and EAS Build. Not in Expo Go, which cannot load native code.
Web and other platformsNot supported. open checks the app ID and the token and then reports UNSUPPORTED.

Before you start

  • An approved publisher account and an app. Every app you add is live right away.
  • Two values from your dashboard, under Apps, your app, Integration. The App ID goes into your app, for Moonetix.init. The API key stays on your server, for POST /v1/session. They are different values: never put the API key in your app or its JavaScript bundle.
  • Your postback URL in the Postback tab, so rewards reach your server. See Postbacks.
  • Your currency in the Currency tab, so users see the right rewards. See Rewards and currency.

Install

React Native 0.77 or newer, or Expo SDK 53 or newer. The package works with the New Architecture and, on React Native 0.77 to 0.81, with the old architecture too. Your Android project needs Kotlin 2.0 or newer, which these versions use by default. It is in the Moonetix npm registry. Add the registry for the @moonetix scope to the .npmrc file next to your package.json:

@moonetix:registry=https://moonetix.com/npm/

Then install the package and rebuild your app:

npm install @moonetix/react-native-offerwall
# or
yarn add @moonetix/react-native-offerwall
# Expo
npx expo install @moonetix/react-native-offerwall

npm, Yarn 1 and pnpm read .npmrc. With Yarn 2 or newer, add the scope to .yarnrc.yml instead:

npmScopes:
  moonetix:
    npmRegistryServer: "https://moonetix.com/npm"

Prefer a file? Download moonetix-react-native-offerwall-2.0.0.tgz and run npm install ./moonetix-react-native-offerwall-2.0.0.tgz.

There is nothing else to set up:

  • Android: the package adds the Moonetix Maven repository (only for the com.moonetix group) and the Android SDK to your Gradle build, the same way React Native adds its own repositories. If your settings.gradle sets repositoriesMode to FAIL_ON_PROJECT_REPOS or PREFER_SETTINGS, add maven { url = uri("https://moonetix.com/maven") } to the repositories there.
  • iOS: the package brings the Moonetix iOS SDK with it, and CocoaPods links and embeds it. Run pod install in your ios folder.

Expo

Add the config plugin to app.json, then run npx expo prebuild or start an EAS Build:

{
  "expo": {
    "plugins": ["@moonetix/react-native-offerwall"]
  }
}

Expo Go cannot load native code, so the offerwall does not open there: Moonetix.isSupported is false and open reports UNSUPPORTED. Use a development build to test.

Create a session on your server

POSThttps://api.moonetix.com/v1/session

Send the fields as a form or as JSON, with your API key in the Authorization header. This is the same request as for the Android SDK.

FieldDescription
user_idrequiredstringThe same user ID you receive in postbacks. 1 to 100 characters: letters, numbers, and . _ @ : + -.
sub1 to sub5optionalstringYour own tracking values. They are returned in the postback as {sub1} to {sub5}.

Your app does not call this itself, because it needs the API key. Add one endpoint to your own server that checks who is signed in, creates the session for that user, and returns only the token. The token endpoint example on the Android page works for React Native too, in PHP, Node.js, and Python.

Errors: 400 invalid_user_id, 401 invalid_api_key, 403 app_not_live or user_blocked (do not show the offerwall), 405 method_not_allowed, 429 rate_limited (retry after the time in the Retry-After header).

Open the offerwall

Call init once with the App ID from the Integration tab. Then open the offerwall with a fresh token from your server.

import { Moonetix } from '@moonetix/react-native-offerwall';

Moonetix.init('YOUR_APP_ID');

async function openOfferwall() {
  // Your own endpoint, which creates the session for the signed-in user
  const res = await fetch('https://your-server.example/earn/session', { method: 'POST' });
  const { token } = await res.json();
  Moonetix.open(token);
}

open never throws for offerwall problems. They arrive as events, see Events.

Full example

A complete Earn button in TypeScript: it asks your server for a token, opens the offerwall, listens to the events, and hides itself where the offerwall cannot open, for example in Expo Go. Call Moonetix.init once when your app starts, as shown above. Replace the URL with your token endpoint, and send your own sign-in credential so your server knows who the user is.

import React, { useEffect, useState } from 'react';
import { Alert, Button } from 'react-native';
import { Moonetix } from '@moonetix/react-native-offerwall';

// Asks YOUR server for a fresh token. Your server calls POST /v1/session with the API key.
async function fetchMoonetixToken(authToken: string): Promise<string> {
  const res = await fetch('https://your-server.example/api/moonetix/session', {
    method: 'POST',
    headers: { Authorization: `Bearer ${authToken}` },
  });
  if (!res.ok) {
    throw new Error(`Token request failed: HTTP ${res.status}`);
  }
  const { token } = (await res.json()) as { token: string };
  return token;
}

// authToken: your own sign-in credential, so your server knows who the user is.
// onClosed: for example, load the user's balance from your server.
export function EarnButton({ authToken, onClosed }: { authToken: string; onClosed: () => void }) {
  const [busy, setBusy] = useState(false);

  useEffect(() => {
    const closed = Moonetix.addListener('closed', onClosed);
    const failed = Moonetix.addListener('error', (error) => {
      console.warn(`Moonetix: ${error.code}: ${error.message}`);
    });
    return () => {
      closed.remove();
      failed.remove();
    };
  }, [onClosed]);

  async function openOfferwall() {
    setBusy(true);
    try {
      Moonetix.open(await fetchMoonetixToken(authToken));
    } catch {
      Alert.alert('The offerwall is not available right now.');
    } finally {
      setBusy(false);
    }
  }

  if (!Moonetix.isSupported) {
    return null;
  }
  return <Button title="Earn coins" onPress={openOfferwall} disabled={busy} />;
}

Branding

The top bar of the offerwall screen shows the Moonetix mark, like the offerwall itself, with your offerwall title and colors from the Design tab of your dashboard. To set the title or color yourself, or to hide the mark, pass options. The color can be any color string React Native understands; transparency is ignored.

Moonetix.open(token, {
  title: 'Earn coins',
  toolbarColor: '#004ABB',
  showLogo: false,
});

Events

Moonetix.addListener returns a subscription. Remove it when your screen goes away.

useEffect(() => {
  const closed = Moonetix.addListener('closed', refreshBalance);
  const failed = Moonetix.addListener('error', (error) => {
    console.warn(`${error.code}: ${error.message}`);
  });
  return () => {
    closed.remove();
    failed.remove();
  };
}, []);
EventWhen
openedThe offerwall finished loading its first page.
closedThe user closed the offerwall. A good moment to load the user’s balance from your server.
errorThe listener receives a MoonetixError with a code: NOT_INITIALIZED, INVALID_APP_ID, INVALID_TOKEN, TOKEN_EXPIRED_OR_USED, NETWORK, UNSUPPORTED, or UNKNOWN, the same names the native SDKs use. Create a new session and try again.

Errors from init and open arrive right after the call returns, so a listener you add in the same tick still receives them. TypeScript types are included.

App Store and Google Play privacy

The iOS SDK inside the package has a privacy manifest that lists what the offerwall collects, as described in our privacy policy. In App Store Connect, under App Privacy, and in Google Play’s Data safety form, add these data types for your app:

Data typeLinked to the userUsed for trackingPurpose
User IDYesNoApp Functionality
Device IDYesNoApp Functionality
Coarse LocationYesNoApp Functionality
Product InteractionYesNoApp Functionality

The user ID is the pseudonymous ID your server sends with the session, the device ID is used for fraud prevention, the coarse location comes from the IP address, and product interaction is the offers a user views, starts, and completes. The SDK does not track users across apps and does not use the advertising identifier. In Google Play’s Data safety form, the same data types are called User IDs, Device or other IDs, Approximate location, and App interactions.

Security

  • Create sessions on your server only. Never put the API key or the link hash salt in your app, because anyone can read an app’s files, including its JavaScript bundle.
  • Credit rewards only from the signed postback on your server, never from events in the app. Events can be faked on a user’s device.
  • Make sure your own endpoint that returns tokens only does so for the signed-in user.

Testing

Before you launch, create a session from your server for a test user ID and open it in a development build on an Android phone, an iPhone, or the iOS Simulator. Check that the offerwall opens, that an offer opens in the store or the browser, and that the back and close buttons work. Then send test postbacks from Integration status on the Integration tab of your app to check your server. Test on a normal home or mobile connection: on a VPN or a test device in the cloud, the offerwall shows a block page such as VPN detected instead of offers. See Testing.

Clicks that come through the package show as sdk in the {integration} postback macro and in the Reporting API, and as Mobile SDK in your dashboard, like the Android and iOS SDKs.

Troubleshooting

What you seeCause and fix
UNSUPPORTED on a phoneThe native part of the package is not in the build. This happens in Expo Go, and when the app was not built again after you installed the package. Fix: Build the app again: npx react-native run-android, pod install and a new iOS build, or a development build with Expo. A Metro reload does not add native code.
UNSUPPORTED on web or desktopThe offerwall opens only on Android and iOS. Fix: Check Moonetix.isSupported and hide your Earn button where it is false.
INVALID_APP_IDThe value in Moonetix.init is not the App ID. It must be the 24-character lowercase hex value from the Integration tab. Passing the API key is a common mistake. Fix: Copy the App ID again from the Integration tab.
NOT_INITIALIZEDopen was called before init. Fix: Call Moonetix.init once when your app starts.
INVALID_TOKEN right after openThe value is not the token, for example the whole JSON answer or an empty string. Fix: Pass only the token field, which starts with sst_.
INVALID_TOKEN after the screen openedWe do not know the token. Usually it was created with the API key of another app than the App ID in init. Fix: Use the App ID and the API key of the same app.
TOKEN_EXPIRED_OR_USED, or a Session ended pageThe token is older than 10 minutes or was used already, for example because the app opened it twice or kept it from an earlier tap. Fix: Create a new token for every open, right when the user taps Earn.
NETWORK, and a No connection page with a Try again buttonThe device could not reach the offerwall. Fix: The user can tap Try again once the connection is back.
Gradle cannot find com.moonetix:offerwallYour settings.gradle does not let projects add repositories. Fix: Add the Moonetix Maven repository there, as described in Install.
Your server gets 403 from /v1/sessionapp_not_live: the app is paused or your account is not active. user_blocked: we blocked this user. Fix: Do not show the offerwall. For app_not_live, check your notifications.
VPN detected, Proxy detected, or Network not supportedThe device is on a VPN, a proxy, or a cloud network. Fix: Test on a normal home or mobile connection. See Test from a normal connection.

For problems on one platform only, the Android and iOS pages list what the native SDKs report.

Changelog

2.0.0: the SDK moves to the Moonetix name: @moonetix/react-native-offerwall, class Moonetix. The wall opens on wall.moonetix.com. The old package stays published.

1.0.0: first release. Moonetix.init and Moonetix.open with the same checks and error codes as the Android and iOS SDKs, opened, closed and error events, top bar options, a TurboModule for the New Architecture that also works with the old one, the Android SDK 1.1.0 added to Gradle automatically, the iOS SDK 1.0.1 bundled, and an Expo config plugin.

License

The package is free to use under the Apache License 2.0. The license file is in the package.