How to Add an Offerwall to a Godot Game

Key takeaways

  • Sharklio has no Godot SDK, so a Godot game opens the signed offerwall link from your backend with HTTPRequest and OS.shell_open, which works on Android, iOS, desktop and web exports.
  • Only your server credits currency, from verified postbacks, and the game reloads the balance when the player comes back.
  • Keep the offerwall out of Steam builds with a separate export preset, and follow Apple and Google incentive rules in mobile builds.

How do you add an offerwall to a Godot game?

To add an offerwall to a Godot game, have your game backend build a signed offerwall link for the logged-in player, fetch it from GDScript with an HTTPRequest node, open it with OS.shell_open(), and credit the player’s currency on your server when the offerwall sends a postback. The game never holds a secret and never decides that a reward was earned. When the player returns, the game reloads the balance from your backend.

This guide uses Godot 4 and covers the code, the differences between Android, iOS, desktop and web exports, and the Steam rule. Placement and currency pricing are in Monetize a mobile game with an offerwall.

Is there a Godot offerwall SDK?

Not from Sharklio. Sharklio ships native packages for Android, iOS, Unity, Flutter and React Native, but none for Godot. That is less of a gap than it sounds, because the offerwall is a web page. Any engine that can make an HTTPS request and open a URL can run it, and Godot does both in its core API. You lose a native in-game screen, and you gain a build with no third-party SDK to update.

You have four realistic ways to show offers from a Godot game:

OptionWhere it fitsEffortPlayer stays in the game
Signed link with OS.shell_open()Android, iOS, Windows, macOS, Linux, webLow: one endpoint and one buttonNo, the system browser opens
Signed link in a WebView pluginAndroid and iOS, if you already use oneMedium: plugin, settings, device testingYes
Iframe or floating Earn button on your siteWeb exports hosted on the website you registeredLow: HTML around the game canvasYes, the wall opens on the same page
Your own offer list from the Offers APIAny platform, drawn with Godot UI nodesHigh: server calls, UI, offer start flowMostly, offers still open on the web

Start with the first row. The Offers API route is covered in Build your own offerwall with an Offers API, and the trade-offs between link, iframe and API are in Offerwall integration: iframe, link or API?

Who does what

PartDoesNever does
Godot clientAsks your backend for the link, opens it, reloads the balanceHolds the link salt or keys, adds currency by itself
Your backendSigns the link, receives postbacks, owns the walletTrusts the client about rewards
SharklioShows offers for the player’s country and device, tracks results, sends signed postbacksPays your players directly

A Godot game needs an account system and a server for this to work. If your game is offline only, with the wallet stored in a local save file, add a backend first. A local balance can be edited by anyone, and postbacks need somewhere to land.

Step 1: an endpoint that returns the signed link

The offerwall link is https://wall.sharklio.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, in lowercase hex. A Godot export can be unpacked, so the salt lives on your backend only. A Python version with Flask, behind your normal player authentication:

import hmac, hashlib, os
from urllib.parse import quote
from flask import Flask, jsonify

app = Flask(__name__)
APP_ID = os.environ["SHARKLIO_APP_ID"]
LINK_SALT = os.environ["SHARKLIO_LINK_SALT"]

@app.get("/offerwall-link")
@require_player  # your own auth check
def offerwall_link():
    user_id = str(current_player().id)
    hash = hmac.new(LINK_SALT.encode(), user_id.encode(), hashlib.sha256).hexdigest()
    url = f"https://wall.sharklio.com/{APP_ID}?user_id={quote(user_id)}&hash={hash}"
    return jsonify(url=url)

require_player and current_player stand for your own session code. Use the account ID your wallet uses as the user ID: not a device ID, which changes on reinstall, and not an email address. It can be 1 to 100 characters from A-Z a-z 0-9 . _ @ : + -. PHP and Node.js versions are in the Offerwall link docs.

Step 2: open the link from GDScript

Godot’s HTTPRequest node sends the request and emits request_completed with the result, the response code, the headers and the body. JSON.parse_string() returns null when parsing fails, so check the type before you read the URL. Attach this script to the Button in your shop or currency screen:

extends Button

const LINK_ENDPOINT := "https://api.yourgame.com/offerwall-link"
const WALL_PREFIX := "https://wall.sharklio.com/"

var http := HTTPRequest.new()

func _ready() -> void:
    add_child(http)
    http.request_completed.connect(_on_link_received)
    pressed.connect(_on_pressed)

func _on_pressed() -> void:
    disabled = true
    var headers := PackedStringArray(["Authorization: Bearer " + PlayerSession.token])
    var err := http.request(LINK_ENDPOINT, headers)
    if err != OK:
        disabled = false
        Toast.show_message("Offers are not available right now.")

func _on_link_received(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
    disabled = false
    if result != HTTPRequest.RESULT_SUCCESS or response_code != 200:
        Toast.show_message("Offers are not available right now.")
        return
    var data = JSON.parse_string(body.get_string_from_utf8())
    if typeof(data) != TYPE_DICTIONARY or not data.has("url"):
        return
    var url: String = data["url"]
    if url.begins_with(WALL_PREFIX):
        OS.shell_open(url)

PlayerSession and Toast stand for your own autoloads. The prefix check makes sure the game only opens Sharklio links, even if a bad response comes back. Fetch the link when the player taps the button, or when the earn screen opens, so it always belongs to the player who is logged in now.

Godot’s OS class reference says shell_open() “Requests the OS to open a resource identified by uri with the most appropriate program” and is implemented on Android, iOS, Web, Linux, macOS and Windows. For an https link, that is the default browser.

System browser or WebView plugin?

Godot’s class reference has no WebView node. Showing the offerwall inside the game on a phone means an Android plugin (the v2 plugin system needs Godot 4.2 or later) and an iOS plugin that wrap the platform’s own web view. You can write them yourself or use a community plugin. Before you rely on a community plugin, check that it supports your Godot version and both platforms, and that it had a release recently.

OS.shell_openWebView plugin
EffortOne linePlugins for two platforms, export settings, 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 OS.shell_open
Godot upgradesNothing to updateThe plugin must keep up with each Godot release

If you do use a WebView, test three things on real Android and iOS devices: JavaScript is on, store and app links leave the WebView for the system, and there is always a visible way back to the game.

Step 3: refresh the balance when the player returns

Rewards do not travel through the browser back into Godot. They go from Sharklio to your server. So when the player comes back, ask your server for the balance. Godot sends notifications to every node when the app regains focus:

func _notification(what: int) -> void:
    if what == NOTIFICATION_APPLICATION_FOCUS_IN:
        Wallet.refresh()

The Node class reference describes NOTIFICATION_APPLICATION_FOCUS_IN as “Implemented on desktop and mobile platforms.” On Android and iOS there is also NOTIFICATION_APPLICATION_RESUMED. With a WebView plugin, refresh when the view closes instead. In a web export, refresh when the player opens the earn screen or the shop again.

Some offers credit within minutes, others after a review or a later step, so tell players a reward may take a while, and show a reward history filled from status 1 postbacks.

Step 4: credit through your backend, never the client

A modified client can say anything, so the client never reports a reward. 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. One transaction keeps the same ID for its whole life, so store the last status you handled and skip repeats after a retry. Answer with a 2xx status within 6 seconds, with no redirect. The macros are explained in What is a postback URL?, a full handler is in the Python postback guide, and the checklist is in Postback security: verify every reward call. The exact format is in the Postbacks docs.

Platform by platform: mobile, desktop, Steam and web

Godot exports one project to many targets, and each target has its own rules. Policy and documentation facts in this section were checked on October 21, 2026.

ExportOfferwall?What to do
AndroidYes, within Google Play rulesEnable the INTERNET permission, open the link with OS.shell_open
iOSYes, within App Store rulesSame code; read Apple’s incentive rules first
Windows, macOS, Linux (not Steam)YesSame code; the default browser opens
Steam buildNoLeave the earn screen out of the Steam export
WebYesCORS on your backend, open from a click, or embed on your site

Android and iOS exports

Godot’s Making HTTP requests page says to enable the Internet permission in the Android export preset, “Otherwise, network communication of any kind will be blocked by the Android OS.”

Apple and Google both have rules about incentives, and both update them. In general terms: never reward ratings or reviews, keep the offerwall an optional extra rather than a requirement to play, check the stricter rules for apps aimed at children (Sharklio end users must be at least 16 anyway, with parental permission under 18), and be careful with crypto rewards. Read Can you put an offerwall in an iOS app? and Incentivized installs: Google Play policy before you submit.

Desktop builds and Steam

On Windows, macOS and Linux, OS.shell_open opens the default browser and the same code works. Steam is different. Valve’s Advertising on Steam page opens with “Steam does not contain any paid advertising, nor are advertising models supported in games distributed on Steam,” and says developers should not give players a reward for watching or engaging with advertising. An offerwall does exactly that, so it does not belong in a Steam build. The details are in Can you put ads in a Steam game?

Godot makes the split easy. Create a separate export preset for Steam, add a custom feature tag such as steam in it, and leave the earn scene out of that preset’s resources. Godot’s Feature tags page explains custom tags, which you read with OS.has_feature():

func _ready() -> void:
    if OS.has_feature("steam"):
        $EarnButton.queue_free()

Treat the check as a second guard. Also keep currency earned from offers out of the Steam economy, as the Steam guide explains.

Mistakes that break a Godot offerwall

  • Signing the link in GDScript, which ships the salt inside your export.
  • Adding currency in the game when the browser closes, instead of waiting for a postback.
  • Using OS.get_unique_id() or another device ID as the user ID, so rewards go to a new account after a reinstall.

Web exports

Godot’s Exporting for the Web page says HTTP requests from a web build are “Subject to same-origin policy”. If your backend sits on another domain than the game, it must answer with CORS headers. Because the request sends an Authorization header, the browser first sends a preflight, so MDN’s CORS guide applies: allow your game’s origin in Access-Control-Allow-Origin and list Authorization in Access-Control-Allow-Headers.

In a web build, Godot’s engine source implements shell_open with window.open() in a new tab. MDN’s window.open page says pop-up windows “must be opened in direct response to user input”. A tab opened after a network round trip can be blocked. So in a web build, fetch the link when the earn screen opens, keep it, and call OS.shell_open directly in the button’s pressed handler. Test in Chrome, Firefox and Safari.

If the game runs on the website you registered for your Sharklio app, over https, you can skip the new tab. Put the offerwall iframe on the page that hosts the game, or add the floating Earn button around the game canvas, as described in Add a floating Earn button to any website. Frames only load on that website and its subdomains, so a game on a third-party portal uses the link.

Step 5: test with a test player

  1. Create a test account in your game and note its user ID.
  2. In Integration status on the Sharklio Integration tab, click Check a link, use Test a link with that ID, and compare the result with what your endpoint returns. They must match exactly.
  3. Press the button in a debug export on every target you ship. The offerwall should open, not the Link not valid page.
  4. Test from a normal home or mobile connection. The wall blocks VPNs, proxies, Tor and data center networks, so a cloud test device shows a block page.
  5. Use Send a test postback for the test player’s ID with status 1. The balance should go up once after the game regains focus. A status 2 test uses a new transaction ID starting with test_ that you never credited, so it must change nothing.
  6. Export the Steam preset and confirm the earn button is gone.

Every postback attempt and your server’s answer is listed under Reports, Postbacks. The Testing and troubleshooting docs list common errors, and How to test your app before it goes live walks through a full pre-launch check.

Sharklio in a Godot game, through the offerwall link

Sharklio is a pay-per-result affiliate network. It has no Godot SDK, so a Godot game uses the signed offerwall link, which needs no code in your export beyond a button. Your app sets its own currency name, icon, conversion rate, user split, rounding and wall design, with a live preview. Offers depend on the player’s country and device, and every credit and reversal reaches your server through a signed server-to-server postback. Every publisher application and every app is reviewed by hand before the wall goes live. If you also ship the game with Unity, the comparison in Best offerwall SDK for Unity and the Unity offerwall guide cover that engine. See how the wall works, or get in touch.

Frequently asked questions

Does Godot have an offerwall SDK?

Sharklio has no Godot SDK, and Godot’s core API is enough without one: HTTPRequest fetches the signed link from your backend and OS.shell_open opens it. Your server credits players from postbacks.

How do I monetize a Godot 4 game without banner ads?

Common options are a paid game, in-app purchases, and an optional rewarded earn screen where players get your currency for completing offers. Which ones fit depends on the store: Steam, for example, does not allow advertising models.

Can I use rewarded offers in a Godot game on Steam?

No. Valve says developers should not reward players for watching or engaging with advertising, which covers offerwalls. Use a separate export preset that leaves the earn screen out of the Steam build.

Can I show the offerwall inside my Godot game on Android or iOS?

Only with a WebView, which Godot does not include, so you need an Android plugin and an iOS plugin that provide one. OS.shell_open in the system browser is simpler and works on both.

Why does the offerwall button do nothing in my Android export?

A frequent cause is the INTERNET permission missing from the Android export preset, which blocks all network traffic. Also check that your endpoint answers with status 200 and a JSON object with a url field.

Can I build the hash in GDScript?

No. That ships the link salt in your export, and anyone who extracts it could open the offerwall as any player. Always sign the link on your backend.