Native WebView (iOS & Android)
Embed Playgent inside a native iOS or Android app — and receive lifecycle events through a JavaScript bridge.
The web SDK targets browser iframes. For native apps you load the play shell directly into a WebView and exchange events through the platform's JavaScript bridge.
Native WebView embedding requires allow_webview: true on the player. Toggle it in Hub → Configuration → Players → (your player) → Allow embedding in iOS / Android WebViews.
How it works
- Your app loads the shell URL with a
bridge=iosorbridge=androidquery parameter. - The shell detects the bridge and routes lifecycle events to the matching native JS interface instead of
window.parent.postMessage. - Your native code receives the events and reacts (close the WebView, log to analytics, navigate, etc.).
Shell URL
https://static.playgent.com/play/v2/?player=plyr_acme&game=sudoku&mode=daily&bridge=ios
Required parameters: player, game, bridge. Optional: mode, content, lang, theme, externalUserId, externalUsername, difficulty, date.
See the full SDK init options reference for everything you can pass — most map 1-to-1 to query parameters.
iOS (WKWebView)
Register a script message handler named playgent before loading the URL. The shell calls window.webkit.messageHandlers.playgent.postMessage(payload) for every lifecycle event.
import WebKit
final class PlaygentViewController: UIViewController, WKScriptMessageHandler {
private var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "playgent")
webView = WKWebView(frame: view.bounds, configuration: config)
view.addSubview(webView)
let url = URL(string:
"https://static.playgent.com/play/v2/?player=plyr_acme&game=sudoku&mode=daily&bridge=ios"
)!
webView.load(URLRequest(url: url))
}
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
guard message.name == "playgent",
let payload = message.body as? [String: Any],
let type = payload["type"] as? String else { return }
switch type {
case "pg_ready": print("ready")
case "pg_start": print("started")
case "pg_hint": print("hint used", payload["hintsUsed"] ?? "")
case "pg_complete": print("done", payload["score"] ?? "")
case "pg_end": print("CTA pressed")
case "pg_error": print("error", payload["code"] ?? "")
default: break
}
}
}
message.body arrives as a Swift dictionary — keys match the SDK event payloads.
Android (WebView)
Add a JavaScript interface named PlaygentAndroidBridge before loading the URL. The shell calls window.PlaygentAndroidBridge.postMessage(jsonString) for every lifecycle event.
import android.webkit.JavascriptInterface
import android.webkit.WebView
import org.json.JSONObject
class PlaygentBridge {
@JavascriptInterface
fun postMessage(json: String) {
val payload = JSONObject(json)
when (payload.optString("type")) {
"pg_ready" -> println("ready")
"pg_start" -> println("started")
"pg_hint" -> println("hint used: ${payload.optInt("hintsUsed")}")
"pg_complete" -> println("done: ${payload.optInt("score")}")
"pg_end" -> println("CTA pressed")
"pg_error" -> println("error: ${payload.optString("code")}")
}
}
}
class PlaygentActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val webView = WebView(this)
webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(PlaygentBridge(), "PlaygentAndroidBridge")
setContentView(webView)
webView.loadUrl(
"https://static.playgent.com/play/v2/?player=plyr_acme&game=sudoku&mode=daily&bridge=android"
)
}
}
The payload arrives as a JSON string — parse it with JSONObject (or kotlinx.serialization). Keys match the SDK event payloads.
Identity from your native auth
Pass the signed-in user via URL parameters:
…&externalUserId=u_12345&externalUsername=alice
These flow into every lifecycle event payload — useful for tying completions back to your user records.
Native WebView gotchas
Bridge must exist before the page loads
Register the iOS message handler (or the Android JS interface) before calling webView.load(...). If the shell finishes loading before the bridge is attached, the first events (notably pg_ready) won't reach native code.
Android: @JavascriptInterface security
Only expose the postMessage method shown above — Android's addJavascriptInterface injects whatever you give it into the JS context. Don't expose database handles, file I/O, or anything that grants the WebView access to your app's internals.
iOS: shared WKProcessPool
If you want multiple games to share session state (rare, but possible), create them with a shared WKProcessPool on their configuration. Most apps don't need this — the default isolated process pool is fine.
Origin allowlisting doesn't apply
Origin allowlists are an iframe-embedding control. WebView traffic bypasses them entirely — the allow_webview toggle on the player is what gates native embedding. If you're testing in a browser and the embed is rejected, it's the origin allowlist; if you're in a WebView and it's rejected, it's the WebView toggle.
Network latency
WebViews don't share the user's Safari/Chrome cache. The first load downloads the shell + game assets fresh. For optimistic UX consider pre-warming the WebView during a previous screen.
Origin allowlist (still required for desktop / mobile web)
The allow_webview toggle is independent from the origin allowlist. If you also embed the same player on your marketing site or in a browser, the origin allowlist still applies there.