Security model
- Create signed sessions from your backend—not from Flutter, Android, iOS or browser code.
- Every launch URL is short-lived, single-session and checked against an active subscription and game entitlement.
- Wallet callbacks are signed and idempotent; verify signatures and reject reused nonces.
- Validate WebView message origins and accept only documented event types.
Production launch flow
- 1Your app authenticates the user with your own login system.
- 2Your backend requests a one-time game session using the private app secret.
- 3Your backend returns only the launch URL to the mobile app.
- 4The app opens the URL in a WebView using full or voice-room layout.
- 5Fah Swe validates subscription, entitlement, expiry and session state on the server.
1. Create the session on your backend
// Your trusted backend only — never put appSecret in a mobile app
const response = await fetch("https://game-api.fahswe.com/v1/sessions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-App-Id": process.env.FAHSWE_APP_ID!,
"X-Timestamp": timestamp,
"X-Nonce": nonce,
"X-Signature": createHmacSignature(body, timestamp, nonce),
},
body: JSON.stringify({
gameId: "food-wheel",
externalUserId: user.id,
layout: "voice", // "full" or "voice"
}),
});
const { launchUrl } = await response.json();2. Open the launch URL in your client
final controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel('FahSweGame', onMessageReceived: (event) {
final message = jsonDecode(event.message);
if (message['type'] == 'game.close') Navigator.pop(context);
})
..loadRequest(Uri.parse(launchUrl)); // URL received from your backendwebView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.addJavascriptInterface(GameBridge(), "FahSweGame")
webView.loadUrl(launchUrl) // one-time URL from your backend
class GameBridge {
@JavascriptInterface fun postMessage(json: String) {
// Parse an allow-listed event: game.ready, game.close, wallet.refresh
}
}let controller = WKUserContentController()
controller.add(self, name: "FahSweGame")
let config = WKWebViewConfiguration()
config.userContentController = controller
let webView = WKWebView(frame: .zero, configuration: config)
webView.load(URLRequest(url: launchUrl)) // obtained from your backend<WebView
source={{ uri: launchUrl }}
javaScriptEnabled
domStorageEnabled
originWhitelist={["https://games.fahswe.com"]}
onMessage={({ nativeEvent }) => {
const event = JSON.parse(nativeEvent.data);
if (event.type === "game.close") navigation.goBack();
}}
/><iframe
src={launchUrl}
title="Fah Swe game"
allow="autoplay; fullscreen"
sandbox="allow-scripts allow-same-origin"
referrerPolicy="strict-origin-when-cross-origin"
/>
// Validate event.origin === "https://games.fahswe.com" before processing.Bridge events
Games send versioned JSON messages such as game.ready, game.close, game.result and wallet.refresh. Treat messages as untrusted input, validate the origin, type and payload, and never accept a balance supplied by the WebView.
Subscription and downtime behavior
Only active subscribers can create sessions. During the configured grace period, the game shows a payment warning. After grace expires, new sessions are blocked. A verified paid invoice reactivates service through the backend automatically.
Virtual entertainment only
Fah Swe does not provide gambling, cash wagering, cash redemption or transferable-value credits. Integrators are responsible for age controls, store policies, local law and truthful in-app disclosures.