How to Add an Offerwall to a Flutter App

Key takeaways

  • Install sharklio_offerwall from the hosted repository at sharklio.com/pub, call Sharklio.init once with your App ID, and open the wall with Sharklio.open(token).
  • Your server, not the app, calls POST /v1/session with your API key and gets a token that works once and for 10 minutes, so no key or salt ships in your Flutter build.
  • Only your server credits currency, from verified postbacks; the app just reloads the balance when the SharklioClosed event fires.

How do you add an offerwall to a Flutter app?

To add an offerwall to a Flutter app, install an offerwall plugin, have your own server create a short-lived session for the signed-in user, open the wall from Dart with that session, and credit the user on your server when the offerwall sends a postback. With Sharklio, the plugin is sharklio_offerwall 1.1.0, the session comes from POST https://api.sharklio.com/v1/session, and the app calls Sharklio.open(token). The app never holds a secret and never decides that a reward was earned.

This guide is the Flutter implementation. Where to place the wall and how to price your currency are covered in Monetize a mobile game with an offerwall and How to set your virtual currency exchange rate. Every code sample below matches the official Flutter SDK docs.

Who does what

A Flutter offerwall has three parts. Keeping each job in the right place is what makes the integration safe:

PartDoesNever does
Flutter appAsks your server for a token, calls Sharklio.open(token), reloads the balance on closeHolds the API key or link hash salt, adds currency by itself
Your serverCalls POST /v1/session with the API key, receives postbacks, owns the walletReturns tokens for anyone but the signed-in user, trusts the app about rewards
SharklioShows offers in a native screen, tracks results, sends signed postbacksPays your users directly

The flow, step by step:

  1. The user taps your Earn button, and the 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 back to the app.
  3. The app calls Sharklio.open(token), and the plugin opens the offerwall in a native screen.
  4. When the user completes an offer, Sharklio sends a signed postback to your server, and your server credits the user.

A token works once and for 10 minutes, so you create a new one every time the user opens the wall. If you have not set up postbacks before, What is a postback URL? explains the idea.

Plugin, WebView or Offers API?

The plugin is not the only way to show offers in Flutter. Every Sharklio app has the offerwall link, the Offers API, and the mobile SDK, with the same App ID and the same postbacks:

sharklio_offerwall pluginSigned link in a WebView or browserOffers API
What the app gets from your serverA single-use session tokenA signed link (HMAC of the user ID)Offers as JSON, via your server
UINative screen with a close button, title and colors from your Design tabWhatever your WebView or browser package showsYour own Flutter widgets
Store links and back buttonHandled by the pluginYours to handle in a WebViewYours to handle
EffortLowLow to mediumHigh
Shows in reports assdkofferwallapi

For most apps the plugin is the shortest path to a polished screen. The link and the API are compared in Offerwall integration: iframe, link or API?, and a custom wall is covered in Build your own offerwall with an Offers API.

Step 1: install sharklio_offerwall

The plugin needs Flutter 3.27 or newer and runs on Android 5.0 (API 21) or newer and iOS 13 or newer. It is published in the Sharklio package repository, a hosted pub repository, so the dependency uses the hosted key. Add it to pubspec.yaml and run flutter pub get:

dependencies:
  sharklio_offerwall:
    hosted: https://sharklio.com/pub
    version: ^1.1.0

This is the standard syntax for a package from your own or a third-party server, described in the Dart team’s guide to custom package repositories and the pub dependencies reference.

Because the plugin brings native Android and iOS code, stop the app and run it again after you add it. Flutter’s Using packages page explains that hot reload and hot restart only update Dart code, and that skipping a full restart can cause a MissingPluginException.

Android

There is nothing else to add. The plugin brings the Sharklio Maven repository, the Android SDK and its R8 rules with it. One exception: if your android/settings.gradle blocks project repositories with repositoriesMode, add maven { url = uri("https://sharklio.com/maven") } to its dependencyResolutionManagement repositories.

iOS

There is nothing to add here either. The plugin brings the Sharklio iOS SDK with it, for CocoaPods and for Swift Package Manager. Build with Xcode 16 or newer.

Step 2: create the session on your server

Your server calls POST https://api.sharklio.com/v1/session with your API key in the Authorization header. You find the key in your dashboard under Apps, your app, Integration. Send the fields as a form or as JSON:

  • user_id (required): the same user ID you will receive in postbacks. 1 to 100 characters: letters, numbers, and . _ @ : + -.
  • sub1 to sub5 (optional): your own tracking values, returned in the postback as {sub1} to {sub5}.

Here is a Node.js endpoint behind your normal sign-in, built on the request from the docs. Your Flutter app calls it, and it returns only the token:

app.post('/offerwall-session', requireAuth, async (req, res) => {
  const r = await fetch('https://api.sharklio.com/v1/session', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.SHARKLIO_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ user_id: String(req.user.id), sub1: 'shop_screen' }),
  });
  const data = await r.json();
  if (!r.ok) {
    return res.status(r.status === 403 ? 403 : 503).json({ error: data.error });
  }
  res.json({ token: data.token });
});

requireAuth stands for your own authentication middleware. The user ID must come from the session on your server, never from a value the app sends, or one user could open the wall as another. Use your account ID, the one your wallet uses, not a device ID that changes on reinstall. If the same person also uses your website, use the same ID there. PHP and curl versions of the session request are in the Flutter SDK docs.

Errors your endpoint should expect:

StatusErrorWhat to do
400invalid_user_idFix the user ID format
401invalid_api_keyCheck the key from the Integration tab
403app_not_live or user_blockedDo not show the offerwall
405method_not_allowedUse POST
429rate_limitedRetry after the time in the Retry-After header (the limit is 600 requests per minute for each app)

Never call /v1/session from Dart

Anything inside an app bundle can be extracted, including strings in compiled Dart code. If the API key or the link hash salt ships in your app, anyone can open the offerwall as any of your users. Keep both on your server, and make sure your token endpoint only returns tokens for the user who is signed in.

Step 3: open the offerwall from Dart

Call init once with the App ID from the Integration tab, before runApp:

import 'package:sharklio_offerwall/sharklio_offerwall.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  Sharklio.init('YOUR_APP_ID');
  runApp(const MyApp());
}

When the user taps Earn, fetch a fresh token from your endpoint and open the wall. This example uses the http package and your own session class (Session) for the sign-in header:

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:sharklio_offerwall/sharklio_offerwall.dart';

Future<void> openOfferwall(BuildContext context) async {
  final res = await http.post(
    Uri.parse('https://api.yourapp.com/offerwall-session'),
    headers: {'Authorization': 'Bearer ${Session.token}'},
  );
  if (res.statusCode != 200) {
    if (!context.mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('Offers are not available right now.')),
    );
    return;
  }
  final token = jsonDecode(res.body)['token'] as String;
  await Sharklio.open(token);
}

Never cache a token. Fetch a new one on every tap, so it always belongs to the user who is signed in now and is never past its 10 minutes. open never throws for offerwall problems: they arrive as events (Step 4).

Title, color and logo

The top bar of the offerwall screen shows the Sharklio mark with your offerwall title and colors from the Design tab of your dashboard. To set the title or color in code, or to hide the mark, pass SharklioOptions:

await Sharklio.open(
  token,
  options: const SharklioOptions(
    title: 'Earn coins',
    toolbarColor: Color(0xFF004ABB),
    showLogo: false,
  ),
);

Step 4: listen for events and refresh the balance

The plugin reports what happens through a stream:

Sharklio.events.listen((SharklioEvent event) {
  switch (event) {
    case SharklioOpened():
      break;
    case SharklioClosed():
      refreshBalance();
    case SharklioErrorEvent(:final SharklioError error):
      debugPrint('${error.code.name}: ${error.message}');
  }
});

If you prefer callbacks, Sharklio.setListener(SharklioListener(onOpened: ..., onClosed: ..., onError: ...)) does the same. refreshBalance is your own function that loads the balance from your server.

EventWhenYour app
SharklioOpenedThe offerwall finished loadingOptional: log it for your own analytics
SharklioClosedThe user closed the offerwallLoad the balance from your server
SharklioErrorEventError codes notInitialized, invalidAppId, invalidToken, tokenExpiredOrUsed, network, unsupported, unknownCreate a new session and try again; for notInitialized and invalidAppId, check your Sharklio.init call

Rewards do not travel through the plugin into Dart. Some offers credit within minutes, others after a review or a later step, so a reward is not always there when the user closes the wall. A reward history in your own UI, filled from credited postbacks, shows users what arrived and when.

Step 5: credit users on your server, from postbacks

Events in the app can be faked on a user’s device, so SharklioClosed is a signal to refresh, never a reason to add currency. Your server credits the user only when a verified postback arrives. Add your postback URL under Apps, your app, Postback, with the macros you need, for example:

https://yoursite.com/postback?user={user_id}&tx={transaction_id}&status={status}&reward={reward}&hash={hash}
StatusMeaningYour server
1CreditedAdd {reward}, once per transaction ID
2ReversedTake it back, only if you credited that transaction

Before anything else, verify {hash}: an HMAC-SHA256, in lowercase hex, of the transaction ID, user ID, reward and status joined with colons, signed with your postback secret key (a different value from the link hash salt). Store the last status per transaction ID and skip repeats, because the same event can arrive twice after a retry. Answer with a 2xx status within 6 seconds, with no redirect. Full handlers in PHP, Node.js and Python are in the Postbacks docs, and the reasoning behind each check is in Postback security: verify every reward call.

If you passed sub1 in the session, it comes back as {sub1}, so you can see which screen in your app earns the most. Clicks through the plugin show as sdk in the {integration} macro and as Mobile SDK in your dashboard.

Android and iOS: what the plugin handles

The plugin uses the Sharklio Android SDK and iOS SDK inside, so the native behavior comes with it:

  • Store links: offers that lead to Google Play open in the Play Store app, and App Store offers open in the App Store app. Other sites open in the browser (Safari on iOS), so installs are tracked correctly.
  • Navigation: on Android, the back button goes back inside the offerwall, and the close button returns to your app. On iOS, swiping from the left edge goes back inside the offerwall.
  • Restarts: screen rotation and low-memory restarts on Android, and the offerwall being paused in the background on iOS, keep the user where they were without using the token again.
  • Locked-down web view: no JavaScript bridge into your app on either platform.

Privacy labels and Data safety

Apple’s App privacy details page says your App Store Connect answers must include the practices of third-party partners whose code you add to your app, and Google’s Data safety help page says the same for libraries and SDKs (both checked on October 21, 2026). Sharklio’s iOS SDK docs list the data types to declare:

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

The docs say the SDK does not track users across apps and does not use the advertising identifier. Fill in Google Play’s Data safety form with the same data types.

Store policies, in general terms

Apple and Google both have rules about incentives, and both update them, so read the current versions before you submit a build with an offerwall. Apple’s App Review Guidelines (3.2.2) say apps must not force users to rate the app, review the app, download other apps, or take similar store actions to access the app, while apps may otherwise reward specific actions (checked on October 21, 2026). In practice:

  • Keep the wall an optional extra, never a gate in front of your app’s core features.
  • Never reward ratings or reviews, of your app or any other app.
  • Keep it out of apps for children. End users of a Sharklio offerwall must be at least 16, with parental permission under 18. See Offerwall COPPA rules.
  • Be careful with crypto rewards, which have their own App Store rules.

The details are in Can you put an offerwall in an iOS app? and Incentivized installs: Google Play policy.

Step 6: test before you launch

  1. Create a test account in your app and note its user ID.
  2. Run a debug build on an Android phone, an iPhone, or the iOS Simulator. Tap Earn and check that the offerwall opens.
  3. Open an offer and check that it goes to the store or the browser, and that the back and close buttons return you to the app.
  4. Check that SharklioClosed triggers your balance refresh.
  5. In Integration status on the Integration tab, use Send a test postback for the test user ID with status 1. The balance should go up once. A test transaction ID always starts with test_ and has offer_id 0. Then send a status 2 test: it is for a transaction you never credited, so it must change nothing.
  6. Open Reports, Postbacks to see every attempt and the status code your server returned.

Test on a normal connection

The offerwall blocks VPNs, proxies, Tor and data center networks. On a VPN or a test device in the cloud, you see a block page such as VPN detected instead of offers. Turn the VPN off, or test on a phone with mobile data.

The Testing and troubleshooting docs list common errors and fixes, and How to test your app before it goes live walks through the full checklist.

A Sharklio offerwall in your Flutter app

Sharklio is a pay-per-result affiliate network: your users complete offers for your own currency, and you earn per completed offer. Each app sets its own currency name and icon, conversion rate, user split, rounding, an optional time-limited bonus, and the wall design with a live preview, and the sharklio_offerwall plugin shows that wall in a native screen on Android and iOS. Offers depend on each user’s country and device, every credit and reversal reaches your server as a signed postback, and your reports break down clicks, conversions and reversals by country, offer and sub ID. Every publisher application and every app is reviewed by hand before the wall goes live. See how the wall works, or get in touch with questions.

Building for other stacks too? See the guides for Unity, React Native, Android with Kotlin and iOS with Swift.

Frequently asked questions

Is there an offerwall SDK for Flutter?

Yes. Sharklio’s sharklio_offerwall plugin, version 1.1.0, opens the offerwall in a native screen on Android 5.0 or newer and iOS 13 or newer, and needs Flutter 3.27 or newer.

How do I install a Flutter package from a hosted repository?

Add the package under dependencies in pubspec.yaml with a hosted key for the repository URL and a version, then run flutter pub get. For this plugin the URL is https://sharklio.com/pub.

Can I credit coins when SharklioClosed fires?

No. App events can be faked on a user’s device, so use SharklioClosed only to reload the balance. Credit currency on your server from verified status 1 postbacks.

How long does a session token last?

A token works once and for 10 minutes. Create a new one on your server each time the user opens the offerwall.

Do I need to change my Podfile or Gradle files?

Usually not. The plugin brings the iOS SDK for CocoaPods and Swift Package Manager, and the Android SDK, Maven repository and R8 rules. Only if your android/settings.gradle blocks project repositories do you add the Sharklio Maven repository there.

Why do I see VPN detected instead of offers?

The offerwall blocks VPN, proxy, Tor and data center connections, including test devices in the cloud. Test on a normal home connection or on mobile data.

Does the plugin use the advertising identifier?

No. According to Sharklio’s iOS SDK docs, the SDK does not use the advertising identifier and does not track users across apps. You still declare User ID, Device ID, Coarse Location and Product Interaction in your privacy answers.