How to Add an Offerwall to an iOS App (Swift)

Key takeaways

  • Your server creates a single-use session token with your API key, and the app only calls Sharklio.open(token:), so no key or salt ever ships in your app.
  • The SDK supports iOS 13 or newer, installs with Swift Package Manager, CocoaPods or a manual xcframework, and opens the wall from UIKit, SwiftUI or Objective-C.
  • App events such as onClosed are for your UI only: credit coins on your server from the signed postback, and refresh the balance when the wall closes.

How do you add an offerwall to an iOS app in Swift?

To add an offerwall to an iOS app, install the SharklioOfferwall package with Swift Package Manager, call Sharklio.configure(appId:) once at launch, and when the user taps your Earn button, get a fresh session token from your own server and call Sharklio.open(token:). Your server creates that token with your API key, so no secret ships inside the app. Rewards never travel through the app: they reach your server as a signed postback, and your app reloads the balance when the wall closes.

This guide follows the Sharklio iOS SDK reference, version 1.0.1 (checked on October 21, 2026). Placement, pricing, and the business side are covered in Monetize a mobile game with an offerwall. If you are new to the term, what an SDK is explains what you are adding to your build.

Who does what

Three parts work together, and each one has a job it must never hand to another:

PartDoesNever does
Your iOS appAsks your server for a token, opens the wall, reloads the balance on closeHolds the API key or link hash salt, adds coins by itself
Your serverSigns in the user, calls POST /v1/session, receives postbacks, owns the walletTrusts the app about rewards
SharklioShows offers for the user’s country and device, tracks results, sends signed postbacksPays your users directly

A token works once and for 10 minutes. Create a new one every time the user opens the wall, and never cache one between visits.

Step 1: install SharklioOfferwall

The SDK needs iOS 13 or newer, runs on iPhone and iPad, and builds with Xcode 16 or newer. It uses only UIKit and WebKit, has no third-party dependencies, and works from Swift, SwiftUI, and Objective-C. Pick one of three ways to add it:

MethodWhat you addGood fit when
Swift Package Managerhttps://sharklio.com/ios/sharklio-ios.git, library SharklioOfferwallMost new and current Xcode projects
CocoaPodsA pod line pointing at the 1.0.1 podspecYour project already manages dependencies with a Podfile
ManualSharklioOfferwall.xcframework, set to Embed & SignYou vendor binaries by hand or cannot use a package manager

Swift Package Manager

In Xcode, choose File, Add Package Dependencies, enter https://sharklio.com/ios/sharklio-ios.git, and add the SharklioOfferwall library to your app target. Apple’s guide to adding package dependencies to your app explains the version rules Xcode offers; accepting updates up to the next major version keeps you on 1.x while still picking up newer 1.x releases. In a Package.swift:

dependencies: [
    .package(url: "https://sharklio.com/ios/sharklio-ios.git", from: "1.0.1"),
],
targets: [
    .target(name: "MyApp", dependencies: [
        .product(name: "SharklioOfferwall", package: "sharklio-ios"),
    ]),
]

CocoaPods

pod 'SharklioOfferwall', :podspec => 'https://sharklio.com/ios/1.0.1/SharklioOfferwall.podspec'

Manual xcframework

Download SharklioOfferwall-1.0.1.xcframework.zip from the install section of the docs, unzip it, drag SharklioOfferwall.xcframework into your project, and set it to Embed & Sign.

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 and the user’s ID, and returns the token to the app. The API key is in your dashboard under Apps, your app, Integration. Here is a Node.js endpoint behind your normal user authentication, using the request from the docs:

app.post('/sharklio-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) }),
  });
  if (r.status === 403) return res.status(403).json({ error: 'offerwall_unavailable' });
  if (!r.ok) return res.status(502).json({ error: 'try_again' });
  const { token } = await r.json();
  res.json({ token });
});

requireAuth stands for your own sign-in middleware. The endpoint must only ever return a token for the user who is signed in, never for a user ID the app sends. PHP and curl versions of the same request are in the session docs.

Which user ID to send

Send the ID your wallet uses, the same one you will receive in postbacks: 1 to 100 characters of letters, numbers, and . _ @ : + -. Avoid device IDs, which change on reinstall, and email addresses. If the same person also uses your website or Android app, use the same ID everywhere. You can also send sub1 to sub5, your own tracking values, which come back in the postback.

Session errors and what to do

ResponseMeaningWhat your server should do
400 invalid_user_idEmpty ID or characters that are not acceptedFix the ID format
401 invalid_api_keyWrong or old keyCheck the key on the Integration tab
403 app_not_live or user_blockedThe app is not live yet, or this user is blockedDo not show the wall; hide or disable the Earn button
429 rate_limitedMore than 600 requests per minute for the appRetry after the time in the Retry-After header

Step 3: configure and open the wall

Call configure once with the App ID from the Integration tab, for example in application(_:didFinishLaunchingWithOptions:):

import SharklioOfferwall

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    Sharklio.configure(appId: "YOUR_APP_ID")
    return true
}

The app then needs one function that asks your server for a token. This one uses URLSession with async/await, which needs iOS 15; on iOS 13 and 14, use a completion handler instead:

struct SessionResponse: Decodable { let token: String }

func fetchSharklioToken() async throws -> String {
    var request = URLRequest(url: URL(string: "https://api.yourapp.com/sharklio-session")!)
    request.httpMethod = "POST"
    request.setValue("Bearer \(UserSession.accessToken)", forHTTPHeaderField: "Authorization")
    let (data, response) = try await URLSession.shared.data(for: request)
    guard (response as? HTTPURLResponse)?.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }
    return try JSONDecoder().decode(SessionResponse.self, from: data).token
}

UserSession stands for however your app stores its own sign-in.

From UIKit

Pass the view controller to present from, or leave it out and the SDK presents from the top screen of your app:

@IBAction func earnTapped(_ sender: UIButton) {
    sender.isEnabled = false
    Task {
        defer { sender.isEnabled = true }
        do {
            let token = try await fetchSharklioToken()
            Sharklio.open(token: token, from: self)
        } catch {
            showMessage("Offers are not available right now.")
        }
    }
}

From SwiftUI

Because Sharklio.open(token:) without a view controller presents from the top screen, a SwiftUI button needs nothing extra. In a SwiftUI app without an app delegate, the App initializer is a convenient place for configure:

@main
struct MyGameApp: App {
    init() {
        Sharklio.configure(appId: "YOUR_APP_ID")
    }
    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

struct EarnButton: View {
    @State private var loading = false

    var body: some View {
        Button("Earn coins") {
            loading = true
            Task {
                defer { loading = false }
                if let token = try? await fetchSharklioToken() {
                    Sharklio.open(token: token)
                }
            }
        }
        .disabled(loading)
    }
}

Disabling the button while the request runs stops a double tap from creating two sessions. Objective-C projects use [Sharklio configureWithAppId:] and [Sharklio openWithToken:from:options:], shown in the docs.

Title, colors, and sheet presentation

The top bar shows the Sharklio mark with the offerwall title and colors from the Design tab of your dashboard, and the SDK picks up changes there within a few minutes. To set them in code, pass options, which win over the Design tab. The wall opens full screen by default; presentation: .sheet opens it as a sheet the user can swipe down to close:

Sharklio.open(token: token, options: SharklioOptions(
    title: "Earn coins",
    toolbarColor: UIColor(red: 0, green: 0.29, blue: 0.73, alpha: 1),
    showLogo: false,
    presentation: .sheet
))

What the SDK already handles

  • Offers that lead to the App Store open in the App Store app, and other sites open in Safari, so installs are tracked correctly.
  • The close button returns to your app, and a swipe from the left edge goes back inside the wall.
  • If iOS pauses the wall in the background, it comes back where the user was without using the token again.
  • The web view is locked down: only offerwall pages load in it, there is no JavaScript bridge into your app, and camera and microphone requests are denied.
  • Light and dark mode, larger text sizes, and VoiceOver.

That list is the main reason to use the SDK instead of a plain WKWebView: store links, navigation, and restore after backgrounding are the parts that usually take the most testing. Offerwall integration: iframe, link or API? compares the other ways to show a wall.

Step 4: listen for events and refresh the balance

open never throws for offerwall problems; they arrive as events, always on the main thread:

Sharklio.onClosed = { [weak self] in
    self?.refreshBalance()
}
Sharklio.onError = { error in
    print(error.code.name, error.message)
}

There is also onOpened, fired when the wall finished loading. If you prefer a delegate, set Sharklio.delegate (a weak reference) to an object that adopts SharklioDelegate with sharklioDidOpen(), sharklioDidClose(), and sharklioDidFail(with:).

EventWhenGood response in your app
onOpenedThe wall finished loadingStop any loading spinner
onClosedThe user closed the wallLoad the balance from your server
onErrorNOT_INITIALIZED, INVALID_APP_ID, INVALID_TOKEN, TOKEN_EXPIRED_OR_USED, NETWORK, or UNKNOWNCreate a new session and try again; check configure for the first two

Some offers credit within minutes and others only after a review or a later step, so the balance is not always higher right after the wall closes. A reward history in your app, filled from credited postbacks, answers most “where are my coins?” questions before they reach support.

Step 5: credit coins on your server from postbacks

Events can be faked on a user’s device, so the app never reports a reward. Sharklio sends a GET postback to the URL you save under Apps, your app, Postback, for every credit and every reversal. What is a postback URL? explains the macros; the rules that keep balances right are short:

StatusStateYour server
1creditedAdd {reward}, only once per transaction_id
2reversedTake {reward} back, only if you credited that transaction
  • Verify {hash} first: the HMAC-SHA256, lowercase hex, of transaction ID, user ID, reward, and status joined with colons, signed with your postback secret key. That key is not the link hash salt, and neither belongs in the app.
  • 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 and do not redirect. Failed attempts are retried up to 5 times.
  • Clicks from the SDK arrive with sdk in the {integration} macro, so you can tell iOS app traffic from your website.

The full checklist is in Postback security: verify every reward call, and the postback docs have complete handlers in PHP, Node.js, and Python. To price the coins themselves, see How to set your virtual currency exchange rate.

Mistakes that break an iOS offerwall integration

Putting the API key, link hash salt, or postback secret in the app bundle; crediting coins in onClosed; reusing a token for a second visit; letting the session endpoint accept any user ID from the app; and keeping the Earn button visible after a 403 app_not_live or user_blocked.

App Store privacy details and review rules

Privacy label

Apple’s app privacy details page says your answers in App Store Connect must include the practices of “third-party partners whose code you integrate into your app”. The SDK ships a privacy manifest, the PrivacyInfo.xcprivacy file Apple uses to record an SDK’s data collection. The Sharklio docs list these data types to add under App Privacy:

Data typeLinked to the userUsed for trackingPurposeWhy
User IDYesNoApp FunctionalityThe pseudonymous ID your server sends with the session
Device IDYesNoApp FunctionalityFraud prevention
Coarse LocationYesNoApp FunctionalityFrom the IP address
Product InteractionYesNoApp FunctionalityOffers the user views, starts, and completes

The docs also state that the SDK does not track users across apps and does not use the advertising identifier. Whether your app as a whole needs the App Tracking Transparency prompt depends on everything else you ship, such as ad and analytics SDKs; SKAdNetwork and ATT explained covers how that works. Never tie coins or the Earn tab to the user allowing tracking.

App Review, in brief

Apple’s App Review Guidelines do not name offerwalls. Guideline 3.2.2(x) says apps must not force users to “download other apps, or other store-related actions in order to access functionality”, while allowing incentives for specific actions (checked on October 21, 2026). In practice: keep the wall optional, pay only your own in-app currency, never reward ratings or reviews, keep it out of apps for children, and describe the Earn feature in your App Review notes. Can you put an offerwall in an iOS app? goes through every relevant guideline and includes a sample review note.

Step 6: test before you submit

  1. Create a test account in your app and note its user ID.
  2. Run a debug build on an iPhone or in the Simulator, tap Earn, and check that your server returns a token and the wall opens.
  3. Open an offer and check that it lands in the App Store app or Safari, then come back and check that the close button works and onClosed refreshes the balance.
  4. Tap Earn twice in a row and confirm each visit uses a new token, with no TOKEN_EXPIRED_OR_USED error.
  5. In Integration status on the Integration tab, use Send a test postback with status 1 for your test user. The balance should go up once. Test transaction IDs start with test_ and use offer_id 0.
  6. Check every attempt and your server’s answer under Reports, Postbacks.

Test on a normal connection

The wall blocks VPNs, proxies, Tor, and data center networks. On a VPN or a cloud test device you see a block page such as VPN detected instead of offers. Use your home Wi-Fi or mobile data, and remember that the offers you see depend on your country and device.

How to test your app before it goes live walks through the link checker and the test postback window step by step, and the testing docs list the common errors and fixes.

Building with a cross-platform framework instead? The Flutter offerwall guide, the React Native offerwall guide, and the Unity offerwall guide use packages that bring this iOS SDK with them, and the Android offerwall guide in Kotlin covers the same server session on Android.

How Sharklio fits your iOS app

Sharklio is a pay-per-result affiliate network: your users complete offers on the wall, earn your own currency, and you earn for each completed offer. The iOS SDK is one of several integrations, next to a signed offerwall link, an Offers API, and SDKs for Android, Unity, Flutter, and React Native. Each app sets its own currency name and icon, conversion rate, user split, rounding, and wall design with a live preview, and rewards reach your server through a signed postback for every credit and every reversal. End users must be at least 16, with parental permission under 18, and every publisher application and every app is reviewed by hand before the wall goes live. See how the wall works, read how to get approved as a publisher, or get in touch.

Frequently asked questions

Which iOS version does the Sharklio offerwall SDK need?

iOS 13 or newer on iPhone and iPad, built with Xcode 16 or newer. The SDK uses only UIKit and WebKit and has no third-party dependencies.

Does the offerwall SDK work with SwiftUI?

Yes. Call Sharklio.open(token:) without a view controller and the SDK presents from the top screen of your app, so a plain SwiftUI Button can open it.

Can I install the iOS offerwall SDK with CocoaPods?

Yes. Add pod 'SharklioOfferwall' with the 1.0.1 podspec URL from the docs, or use Swift Package Manager or the manual xcframework instead.

Why does the app need my server to open the offerwall?

Anyone can read the files inside an app, so the API key stays on your server, which creates a single-use token for the signed-in user. A token works once and for 10 minutes.

Can I add coins when the offerwall closes?

No. App events can be faked on the device, so only your server credits coins, from verified postbacks. Use onClosed only to reload the balance.

Does the iOS offerwall SDK use the IDFA?

No. The Sharklio docs state that the SDK does not track users across apps and does not use the advertising identifier. You still declare its data types in your App Store privacy details.

Can I test the offerwall in the iOS Simulator?

Yes, from a debug build on a normal connection. Through a VPN, proxy, or cloud test device, the wall shows a block page instead of offers.