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:
| Option | Where it fits | Effort | Player stays in the game |
|---|---|---|---|
Signed link with OS.shell_open() | Android, iOS, Windows, macOS, Linux, web | Low: one endpoint and one button | No, the system browser opens |
| Signed link in a WebView plugin | Android and iOS, if you already use one | Medium: plugin, settings, device testing | Yes |
| Iframe or floating Earn button on your site | Web exports hosted on the website you registered | Low: HTML around the game canvas | Yes, the wall opens on the same page |
| Your own offer list from the Offers API | Any platform, drawn with Godot UI nodes | High: server calls, UI, offer start flow | Mostly, 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
| Part | Does | Never does |
|---|---|---|
| Godot client | Asks your backend for the link, opens it, reloads the balance | Holds the link salt or keys, adds currency by itself |
| Your backend | Signs the link, receives postbacks, owns the wallet | Trusts the client about rewards |
| Sharklio | Shows offers for the player’s country and device, tracks results, sends signed postbacks | Pays 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_open | WebView plugin | |
|---|---|---|
| Effort | One line | Plugins for two platforms, export settings, testing |
| Player stays in your UI | No, the browser opens | Yes, with your own close button |
| App store links in offers | Handled by the system | You may need to pass them to OS.shell_open |
| Godot upgrades | Nothing to update | The 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:
| Status | Meaning | Your backend |
|---|---|---|
| 1 | Credited | Add the reward, once per transaction ID |
| 2 | Reversed | Take 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.
| Export | Offerwall? | What to do |
|---|---|---|
| Android | Yes, within Google Play rules | Enable the INTERNET permission, open the link with OS.shell_open |
| iOS | Yes, within App Store rules | Same code; read Apple’s incentive rules first |
| Windows, macOS, Linux (not Steam) | Yes | Same code; the default browser opens |
| Steam build | No | Leave the earn screen out of the Steam export |
| Web | Yes | CORS 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
- Create a test account in your game and note its user ID.
- 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.
- Press the button in a debug export on every target you ship. The offerwall should open, not the Link not valid page.
- 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.
- 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. - 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.