Key takeaways

  • On Android and iOS, the Moonetix Unity plugin opens the wall in a native screen with a single-use token your server creates, so no key ships inside the build.
  • On desktop, WebGL, or without the plugin, your backend signs the offerwall link and the game opens it with Application.OpenURL or a WebView plugin.
  • Only your server credits currency, from verified postbacks, and the game reads the balance back when the player returns.
Integration pageUnity offerwall SDKSetup and code

How do you add an offerwall to a Unity game?

To add an offerwall to a Unity game on Android or iOS, install the Moonetix Unity plugin (com.moonetix.offerwall), have your server create a single-use session token for the signed-in player, and call Moonetix.Open(token) from your Earn button. On desktop or WebGL, or if you prefer no plugin, your backend signs an offerwall link and the game opens it with Application.OpenURL. Either way, your server credits the player’s currency only when a signed postback arrives, and the game reloads the balance when the player comes back.

The business side, such as where to place the wall and how to price your currency, is covered in Monetize a mobile game with an offerwall. This guide is the Unity implementation. Every Moonetix code sample below comes from the Unity SDK and Offerwall link docs.

Who does what

PartDoesNever does
Unity clientAsks your server for a token or link, opens the wall, reloads the balanceHolds the API key or link salt, adds currency by itself
Your backendCreates sessions or signs links, receives postbacks, owns the walletTrusts the client about rewards
MoonetixShows offers, tracks results, sends signed postbacksPays your players directly

Plugin or link: which one fits your build

Build targetUseWhy
AndroidUnity pluginNative screen, the plugin adds the Android SDK to the Gradle project for you
iOS 13 or newerUnity pluginNative screen, the iOS SDK is bundled and embedded; build with Xcode 16 or newer
Windows, macOS, LinuxOfferwall linkThe plugin reports Unsupported on desktop
WebGLOfferwall link, or an iframe on your registered https siteThe plugin reports Unsupported in WebGL
Unity EditorPlugin flow for testing onlyMoonetix.IsSupported is false, so you can test your button logic

The iframe embed only loads on https pages of the website you registered for the app and its subdomains, so it fits only a WebGL build hosted there. A game on a phone uses the plugin or the link, and a desktop game uses the link.

Path A: the Moonetix Unity plugin (Android and iOS)

Step 1: install the package

The plugin needs Unity 2022.3 LTS or Unity 6 (6000.0) or newer and lives in the Moonetix package registry. Add the registry and the package to Packages/manifest.json:

{
  "scopedRegistries": [
    {
      "name": "Moonetix",
      "url": "https://moonetix.com/upm",
      "scopes": ["com.moonetix"]
    }
  ],
  "dependencies": {
    "com.moonetix.offerwall": "2.0.0"
  }
}

Unity’s manual explains scoped registries, which tell the Package Manager where a custom registry is. You can also add the registry in Project Settings, Package Manager, and install Moonetix Offerwall from My Registries. There is nothing else to set up: on Android the plugin adds the Moonetix Maven repository, the Android SDK and its R8 rule when Unity generates the Gradle project, and on iOS it adds the iOS SDK to the Xcode project. Set Target minimum iOS Version to 13.0 or higher in Player Settings.

Step 2: create a session on your server

Your game never calls Moonetix for a session itself, because that needs the API key. Add one endpoint to your own server that checks which player is signed in, calls POST https://api.moonetix.com/v1/session for that player, and returns only the token. The request in Node.js, from the docs:

const res = await fetch('https://api.moonetix.com/v1/session', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MOONETIX_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ user_id: playerId }),
});
const { token } = await res.json();

Take playerId from your own sign-in, never from the request, so nobody can open the wall as another player. Use the account ID your wallet uses, not a device ID, which changes on reinstall. A token works once and for 10 minutes, so create a new one each time the player taps Earn. If your server gets 403 with app_not_live or user_blocked, do not show the wall. The App ID and the API key are both under Apps, your app, Integration, and they are different values: never put the API key in your game.

Step 3: open the offerwall from C#

Call Init once with the App ID, then open the wall with a fresh token from your server:

using MoonetixOfferwall;
using UnityEngine;

public class EarnButton : MonoBehaviour
{
    void Awake()
    {
        Moonetix.Init("YOUR_APP_ID");
    }

    // Call this with a fresh token from your server.
    public void ShowOfferwall(string token)
    {
        Moonetix.Open(token);
    }
}

Open never throws for offerwall problems. They arrive as events on the Unity main thread, so you can update your UI directly:

void OnEnable()
{
    Moonetix.OnClosed += RefreshBalance;
    Moonetix.OnError += HandleError;
}

void OnDisable()
{
    Moonetix.OnClosed -= RefreshBalance;
    Moonetix.OnError -= HandleError;
}

void HandleError(MoonetixError error)
{
    Debug.LogWarning(error.CodeName + ": " + error.Message);
}

OnClosed is the moment to load the player’s balance from your server. OnError reports NotInitialized, InvalidAppId, InvalidToken, TokenExpiredOrUsed, Network, Unsupported, or Unknown; for most of them, create a new session and try again. The package has an Offerwall demo sample with a complete Earn button that fetches the token with UnityWebRequest: Window, Package Manager, Moonetix Offerwall, Samples.

Step 1: an endpoint that returns the signed link

The offerwall link is https://wall.moonetix.com/{APP_ID}?user_id={USER_ID}&hash={HASH}. The hash is an HMAC-SHA256 of the user ID with your app’s link hash salt, as lowercase hex. Anything inside a game build can be extracted, so the salt lives on your backend only. The docs build it in Node.js like this; put it behind your normal player authentication and return url as JSON:

const crypto = require('crypto');
const hash = crypto.createHmac('sha256', linkHashSalt).update(String(userId)).digest('hex');
const url = `https://wall.moonetix.com/${appId}?user_id=${encodeURIComponent(userId)}&hash=${hash}`;

Here userId is your own account ID for the signed-in player, the same one the plugin path uses, and linkHashSalt and appId come from your server configuration. PHP and Python versions are on the same docs page.

Step 2: open the link from Unity

The client calls your endpoint with the player’s session token, reads the URL, and opens it:

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

public class OfferwallButton : MonoBehaviour
{
    [Serializable]
    class LinkResponse { public string url; }

    public void OnEarnClicked()
    {
        StartCoroutine(OpenOfferwall());
    }

    IEnumerator OpenOfferwall()
    {
        using (UnityWebRequest req = UnityWebRequest.Get("https://api.yourgame.com/offerwall-link"))
        {
            req.SetRequestHeader("Authorization", "Bearer " + PlayerSession.Token);
            yield return req.SendWebRequest();
            if (req.result != UnityWebRequest.Result.Success)
            {
                ErrorToast.Show("Offers are not available right now.");
                yield break;
            }
            string url = JsonUtility.FromJson<LinkResponse>(req.downloadHandler.text).url;
            if (url != null && url.StartsWith("https://wall.moonetix.com/"))
            {
                Application.OpenURL(url);
            }
        }
    }
}

PlayerSession and ErrorToast stand for your own session and UI classes. Unity’s Application.OpenURL reference says web URLs open in the default browser, and warns not to pass strings from untrusted sources, which is why the code checks the prefix. Fetch a fresh link each time rather than storing one, so it always belongs to the player who is logged in now.

System browser or WebView plugin?

Application.OpenURLWebView plugin
EffortOne lineA plugin, platform settings, and testing
Player stays in your UINo, the browser opensYes, with your own close button
App store links in offersHandled by the systemYou may need to pass them to the system
Cookies and sign-insThe player’s own browserA separate web storage

On Android and iOS, the Moonetix plugin is the simplest way to keep players inside the game. If you still use a third-party WebView plugin with the link, test three things on real devices: JavaScript is on, store and app links are handed to Application.OpenURL, and there is a visible close button. The trade-offs of in-app browser tabs are in Offerwall integration: iframe, link or API?

Step 3: handle the return to the game

Rewards do not travel through the page back into Unity. They go from Moonetix to your server. So when the player comes back, ask your server:

void OnApplicationPause(bool paused)
{
    if (!paused)
    {
        StartCoroutine(Wallet.Refresh());
    }
}

Unity’s OnApplicationPause reference says Unity calls it with false when the game regains focus, for example after the player switches back from the browser. With the plugin, use OnClosed instead.

Credit through your backend, never the client

Anything the client says can be faked, and plugin events can be faked on a player’s device too. Your backend credits the player only when a verified postback arrives:

StatusMeaningYour backend
1CreditedAdd the reward, once per transaction ID
2ReversedTake it back, only if you credited that transaction

Verify the hash first: an HMAC-SHA256 of transaction ID, user ID, reward, and status joined with colons, signed with your postback secret key, which is a different value from the link salt and the API key. Store the last status per transaction ID and skip repeats after a retry. Answer with a 2xx status within 6 seconds, with no redirect. A C# handler is in Offerwall postback in C# and ASP.NET Core, and the full checklist is in Postback security: verify every reward call.

Set expectations in your UI: some offers credit within minutes, others after a review or a later step. A reward history filled from status 1 postbacks, and a short note such as “Your 450 gems have arrived”, bring players back to spend them. Decide early what happens when a player already spent currency that is later reversed, and write it into your terms. For your in-game wallet design, see Unity currency system: build coins, a wallet and saves.

Store policies and privacy forms, in general terms

Apple and Google both have rules about incentives and update them, so read the current versions before you submit (checked on November 4, 2026):

  • Never reward ratings or reviews. Google Play’s User Ratings, Reviews, and Installs policy says not to offer rewards or incentives for ratings or reviews.
  • Keep the offerwall optional. Apple’s App Review Guidelines (3.2.2) say apps must not force users to rate, review, or download other apps to access functionality.
  • Children’s apps are out. End users of a Moonetix offerwall must be at least 16, with parental permission under 18.
  • Be careful with crypto rewards, which have their own rules on the App Store.

In App Store Connect and Google Play’s Data safety form, the Unity SDK docs list four data types to add for the offerwall: User ID, Device ID, Coarse Location, and Product Interaction, all linked to the user, not used for tracking, for App Functionality. The SDK does not use the advertising identifier.

Test with a test player

  1. Create a test account in your game and note its user ID.
  2. Plugin: create a session from your server for that ID and open it in a development build on an Android phone, an iPhone, or the iOS Simulator. Link: use Check a link, then Test a link in Integration status on the Integration tab, and compare the result with your endpoint. They must match exactly.
  3. Check that an offer opens in the store or the browser, and that back and close work.
  4. In Integration status, use Send a test postback for the test player’s ID with status 1. The balance should go up once after OnClosed or a resume. A status 2 test has its own new test_ transaction ID, which you never credited, so it must change nothing.
  5. Test on a normal home or mobile connection. On a VPN or a test device in the cloud, the wall shows a block page such as VPN detected.

The full routine is in How to test your app before it goes live, and every postback attempt is listed under Reports, Postbacks.

A Moonetix offerwall for your Unity game

Moonetix is a pay-per-result affiliate network: your players earn your own currency by completing offers, and you earn per completed offer. Each app sets its currency name, icon, rate, and wall design, and every credit and reversal reaches your server as a signed postback. See how to get approved as a publisher or see how the wall works.

Frequently asked questions

Is there a Moonetix SDK for Unity?

Yes. The Unity plugin com.moonetix.offerwall opens the wall in a native screen on Android and iOS 13 or newer. On desktop and WebGL it reports Unsupported, so use the signed offerwall link there.

Does the Unity plugin work in the Editor?

No. Moonetix.IsSupported is false in the Editor, and Open checks the app ID and the token and then reports Unsupported, so you can test your button flow before building for a device.

Should I use Application.OpenURL or a WebView in Unity?

On Android and iOS, use the Moonetix plugin if you want players to stay in the game. Otherwise start with Application.OpenURL, which opens the system browser and handles store links for you.

Why do I get TokenExpiredOrUsed?

A token works once and for 10 minutes. Create a new token on your server each time the player taps Earn, and never reuse one from an earlier tap.

Can the Unity client credit the reward when the player finishes an offer?

No. The client and its events can be modified, so only your server credits currency, and only from verified postbacks.

Can I build the hash or the session inside the game?

No. That would ship the link salt or the API key in your build, and anyone who extracts it could open the offerwall as any player. Always do both on your backend.