JUTERA Flutter guideSign in Get keys

Flutter Quickstart (webview_flutter)

You will: pass your signed-in user's id to your server, host one small web page (the bootstrap), show it in a WKWebView, listen for three events, and credit the user from a webhook. No SDK to install — the intelligence is in the web page; the app is a careful container.

How it fits together

  1. Your appKnow your user

    After sign-in, your app has a stable user id. That id — never an email — is who earns.

  2. Your serverMint a token

    Your server signs a 10-minute launch token for that user with your secret key.

  3. Bootstrap pageStart the widget

    A page on your server loads the widget with that token.

  4. WKWebViewShow and listen

    The webview displays the page; your app hears earn, height, and token-expired events.

  5. Your serverCredit from the webhook

    AIRewards POSTs reward.earned to your server. That — not the app event — is when you credit.

Step 1 · Pass the end-user id

Pick the id you already use for the signed-in user in your own system (for example user_88123). Rules: stable per user, at most 128 characters, never an email or a name. Caps, reporting, and the webhook all key on it, so changing a user's id later splits their history.

Step 2 · Mint the token on your server

Add one endpoint to your server, for example GET /airewards/token?user_id=…, that returns the signed launch token from the launch-token recipe (Node, Python, and PHP samples there). Authenticate the request the same way you authenticate the rest of your app — the token proves your server vouched for this user. Never put the secret key in the app.

Step 3 · Host the bootstrap page

Serve this page from your server (for example at /airewards/earn?user_id=…). It fetches the token and starts the widget. This exact page also opens in a desktop browser, which is your best debugging tool.

<!DOCTYPE html>
<html><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Earn</title></head>
<body>
<div id="airewards"></div>
<script src="https://airewards.b-cdn.net/loader@1.js" async></script>
<script>
  // This page is served by YOUR server. The user id arrives in the page
  // URL (put it there only after your user is signed in — never trust a
  // user id typed into a client-side form).
  var userId = new URLSearchParams(location.search).get('user_id');
  function initWidget() {
    fetch('/airewards/token?user_id=' + encodeURIComponent(userId), { credentials: 'same-origin' })
      .then(function (r) { return r.json(); })
      .then(function (data) {
        window.AIRewards.init({
          container: '#airewards',
          launchToken: data.airewardsToken,   // signed on your server, see /docs#token
          heightMode: 'auto',
          onEarn: function () { /* UI hint only — credit comes from the webhook */ },
          onTokenExpired: function () { location.reload(); }
        });
      });
  }
  if (window.AIRewards) initWidget();
  else window.addEventListener('airewards:ready', initWidget);
</script>
</body></html>

New chat and chat history from native code: evaluate AIRewards.newChat(), AIRewards.getHistory(), or AIRewards.openConversation(id) in this page. Per-platform snippets are in New chat & history.

Put the user id in the bootstrap URL only from a signed-in server session. Anyone who can change that id can earn as that user in test mode; live mode is protected by the token, but keep the habit clean.

Step 4 · Show it with WebViewController

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class EarnPage extends StatefulWidget {
  const EarnPage({super.key});
  @override
  State<EarnPage> createState() => _EarnPageState();
}

class _EarnPageState extends State<EarnPage> {
  late final WebViewController controller;

  @override
  void initState() {
    super.initState();
    controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted) // required
      // The widget posts to an object named AIRewardsBridge.
      ..addJavaScriptChannel('AIRewardsBridge', onMessageReceived: (message) {
        final msg = jsonDecode(message.message) as Map<String, dynamic>;
        if (msg['source'] != 'airewards') return;
        switch (msg['type']) {
          case 'reward.earned':
            refreshBalanceFromYourBackend(); // UI hint only
          case 'airewards:height':
            resizeWebView((msg['height'] as num).toDouble());
          case 'airewards:token-expired':
            controller.reload();             // fresh token
        }
      })
      // Your hosted bootstrap page, with your signed-in user's id.
      ..loadRequest(Uri.parse('https://your-server.example/airewards/earn?user_id=user_88123'));
  }

  void resizeWebView(double height) { /* update the SizedBox height for inline use */ }
  void refreshBalanceFromYourBackend() { /* re-read the credited balance */ }

  @override
  Widget build(BuildContext context) {
    // Fullscreen: the widget scrolls internally. For inline use, wrap in a
    // SizedBox whose height follows the airewards:height events instead.
    return Scaffold(body: WebViewWidget(controller: controller));
  }
}

Three platform facts that matter: add the official plugin first (flutter pub add webview_flutter); JavaScript must be set to JavaScriptMode.unrestricted or the widget cannot run; and the channel name must be exactly 'AIRewardsBridge' because that is the object the widget posts JSON strings to — message text arrives in message.message.

Step 5 · Embed elegantly: fullscreen or inline

Fullscreen (simplest, recommended first): give the webview the whole screen and let the widget scroll internally. Nothing else to manage.

Inline in a scrolling page: the bootstrap uses heightMode: 'auto', and the widget reports its content height with airewards:height events. Resize the webview to that height (disable the webview's own scrolling) so your page scrolls as one. Do not put an auto-height widget inside a fixed-height clipped container — growing content will be cut off.

Step 6 · Set the webhook and credit the user

In Dashboard → Webhook, save your HTTPS endpoint. When a user earns, AIRewards POSTs reward.earned there, signed with your webhook secret. Verify the signature (copy-paste verifiers in the main guide), credit that user_id in your own currency, and dedupe on event_id — retries reuse it. The app event in Step 4 only refreshes your UI; if your webhook is down, nobody is credited, and we email the account owners when that happens.

Before you ship

Questions: hello@jutera.com · iOS guide →