> For the complete documentation index, see [llms.txt](https://docs.enable3.io/enable3/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.enable3.io/enable3/tech/integration/webview.md).

# WebView

{% hint style="warning" %}
For security reasons, your mobile app shouldn't connect to Enable3 on its own. Instead, your backend (server) should request a widget link on behalf of the current user and pass that link to the mobile app to load. This keeps your credentials safe and off the device.
{% endhint %}

### Integration flow <a href="#integration-flow" id="integration-flow"></a>

1. The user opens the Loyalty screen in your mobile app.
2. The mobile app requests a widget URL from your backend.
3. Your backend calls the Enable3 integration API.
4. Enable3 returns a widget URL with an embedded authentication token.
5. The mobile app opens this URL inside a WebView.
6. If the WebView receives a `401 Unauthorized` response, request a new widget URL and reload the WebView.

### Get widget URL <a href="#get-widget-url" id="get-widget-url"></a>

Your backend should call:

```
GET https://integration.enable3.io/api/v1/integration/user/{operatorUserId}/widget
```

#### Headers <a href="#headers" id="headers"></a>

```
X-API-KEY: <operator-api-key>
```

#### Optional query parameters <a href="#optional-query-parameters" id="optional-query-parameters"></a>

| Parameter      | Type     | Description                                                            |
| -------------- | -------- | ---------------------------------------------------------------------- |
| `referralCode` | `string` | Optional referral code provided by the operator on behalf of the user. |

#### Example request <a href="#example-request" id="example-request"></a>

{% code lineNumbers="true" %}

```
curl --request GET \
  'https://integration.enable3.io/api/v1/integration/user/user-123/widget?referralCode=REF123ABC' \
  --header 'X-API-KEY: <operator-api-key>'
```

{% endcode %}

#### Example response <a href="#example-response" id="example-response"></a>

{% code lineNumbers="true" %}

```
{
  "url": "https://app.enable3.io/?token=<widget-token>"
}
```

{% endcode %}

### Important notes <a href="#important-notes" id="important-notes"></a>

* Never expose your `X-API-KEY` inside a mobile application.
* The mobile app should receive only the generated widget URL.
* The widget URL already contains the required authentication token.
* The widget URL is temporary and should be refreshed when expired.
* If the WebView returns `401 Unauthorized`, request a new widget URL from your backend and reload the WebView.
* JavaScript must be enabled in the WebView.
* Load only trusted HTTPS URLs returned by Enable3.

***

### React Native

Use `react-native-webview` to open the Enable3 widget URL.

### Install dependency <a href="#install-dependency" id="install-dependency"></a>

```
npm install react-native-webview
```

or

```
yarn add react-native-webview
```

### Example <a href="#example" id="example"></a>

{% code lineNumbers="true" %}

```
import React, { useEffect, useState } from 'react';
import { ActivityIndicator, View } from 'react-native';
import { WebView } from 'react-native-webview';

type Enable3WebViewProps = {
  widgetUrl: string;
  refreshWidgetUrl: () => Promise<string>;
};

export const Enable3WebView = ({
  widgetUrl,
  refreshWidgetUrl,
}: Enable3WebViewProps) => {
  const [url, setUrl] = useState(widgetUrl);

  useEffect(() => {
    setUrl(widgetUrl);
  }, [widgetUrl]);

  const handleTokenExpired = async () => {
    const newUrl = await refreshWidgetUrl();
    setUrl(newUrl);
  };

  return (
    <WebView
      source={{ uri: url }}
      javaScriptEnabled
      domStorageEnabled
      startInLoadingState
      renderLoading={() => (
        <View style={{ flex: 1, justifyContent: 'center' }}>
          <ActivityIndicator />
        </View>
      )}
      onHttpError={(event) => {
        if (event.nativeEvent.statusCode === 401) {
          handleTokenExpired();
        }
      }}
      style={{ flex: 1 }}
    />
  );
};

```

{% endcode %}

***

### Android

Use the native Android `WebView` to open the Enable3 widget URL.

### AndroidManifest.xml <a href="#androidmanifest.xml" id="androidmanifest.xml"></a>

Make sure the app has internet access:

{% code lineNumbers="true" %}

```
<uses-permission android:name="android.permission.INTERNET" />

```

{% endcode %}

### Layout example <a href="#layout-example" id="layout-example"></a>

{% code lineNumbers="true" %}

```
<WebView 
android:id="@+id/enable3WebView" 
android:layout_width="match_parent" 
android:layout_height="match_parent" />

```

{% endcode %}

### Java example <a href="#java-example" id="java-example"></a>

{% code lineNumbers="true" %}

```
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import androidx.appcompat.app.AppCompatActivity;

public class Enable3WebViewActivity extends AppCompatActivity {

    private WebView webView;

    // This URL should be received from your backend.
    private String widgetUrl = "https://app.enable3.io/?token=<widget-token>";

    @SuppressLint("SetJavaScriptEnabled")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        webView = new WebView(this);
        setContentView(webView);

        WebSettings settings = webView.getSettings();
        settings.setJavaScriptEnabled(true);
        settings.setDomStorageEnabled(true);

        webView.setWebViewClient(new WebViewClient() {
            @Override
            public void onReceivedHttpError(
                    WebView view,
                    WebResourceRequest request,
                    WebResourceResponse errorResponse
            ) {
                super.onReceivedHttpError(view, request, errorResponse);

                if (request.isForMainFrame() && errorResponse.getStatusCode() == 401) {
                    requestNewWidgetUrl();
                }
            }
        });

        webView.loadUrl(widgetUrl);
    }

    private void requestNewWidgetUrl() {
        // Call your backend here.
        // Your backend should call Enable3 and return a fresh widget URL.
        // Then reload the WebView:
        //
        // webView.loadUrl(newWidgetUrl);
    }
}

```

{% endcode %}

***

## Flutter <a href="#flutter" id="flutter"></a>

Use the official `webview_flutter` package.

### Install dependency <a href="#install-dependency.1" id="install-dependency.1"></a>

Add the package to `pubspec.yaml`:

{% code lineNumbers="true" %}

```
dependencies:
  webview_flutter: ^4.14.0

```

{% endcode %}

### Example <a href="#example.1" id="example.1"></a>

{% code lineNumbers="true" %}

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

class Enable3WebView extends StatefulWidget {
  final String widgetUrl;
  final Future<String> Function() refreshWidgetUrl;

  const Enable3WebView({
    super.key,
    required this.widgetUrl,
    required this.refreshWidgetUrl,
  });

  @override
  State<Enable3WebView> createState() => _Enable3WebViewState();
}

class _Enable3WebViewState extends State<Enable3WebView> {
  late final WebViewController controller;

  @override
  void initState() {
    super.initState();

    controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(
        NavigationDelegate(
          onHttpError: (HttpResponseError error) async {
            final statusCode = error.response?.statusCode;

            if (statusCode == 401) {
              final newUrl = await widget.refreshWidgetUrl();
              controller.loadRequest(Uri.parse(newUrl));
            }
          },
          onWebResourceError: (WebResourceError error) {
            // Optional: handle network or rendering errors here.
          },
        ),
      )
      ..loadRequest(Uri.parse(widget.widgetUrl));
  }

  @override
  Widget build(BuildContext context) {
    return WebViewWidget(controller: controller);
  }
}

```

{% endcode %}

***

## iOS: Swift <a href="#ios-swift" id="ios-swift"></a>

Use `WKWebView` to open the Enable3 widget URL.

### Example

{% code lineNumbers="true" %}

```
import UIKit
import WebKit

final class Enable3WebViewController: UIViewController, WKNavigationDelegate {

    private let widgetUrl: URL
    private let refreshWidgetUrl: () async throws -> URL

    private lazy var webView: WKWebView = {
        let configuration = WKWebViewConfiguration()
        let webView = WKWebView(frame: .zero, configuration: configuration)
        webView.navigationDelegate = self
        webView.translatesAutoresizingMaskIntoConstraints = false
        return webView
    }()

    init(
        widgetUrl: URL,
        refreshWidgetUrl: @escaping () async throws -> URL
    ) {
        self.widgetUrl = widgetUrl
        self.refreshWidgetUrl = refreshWidgetUrl
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(webView)

        NSLayoutConstraint.activate([
            webView.topAnchor.constraint(equalTo: view.topAnchor),
            webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])

        webView.load(URLRequest(url: widgetUrl))
    }

    func webView(
        _ webView: WKWebView,
        decidePolicyFor navigationResponse: WKNavigationResponse,
        decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void
    ) {
        if let response = navigationResponse.response as? HTTPURLResponse,
           response.statusCode == 401 {
            decisionHandler(.cancel)

            Task {
                do {
                    let newUrl = try await refreshWidgetUrl()
                    await MainActor.run {
                        webView.load(URLRequest(url: newUrl))
                    }
                } catch {
                    // Optional: show an error state or retry button.
                }
            }

            return
        }

        decisionHandler(.allow)
    }
}

```

{% endcode %}

***

## iOS: Objective-C <a href="#ios-objective-c" id="ios-objective-c"></a>

Use `WKWebView` to open the Enable3 widget URL.

### Header <a href="#header" id="header"></a>

{% code lineNumbers="true" %}

```
#import <UIKit/UIKit.h>

@interface Enable3WebViewController : UIViewController

- (instancetype)initWithWidgetUrl:(NSURL *)widgetUrl;

@end

```

{% endcode %}

### Implementation <a href="#implementation" id="implementation"></a>

{% code lineNumbers="true" %}

```
#import "Enable3WebViewController.h"
#import <WebKit/WebKit.h>

@interface Enable3WebViewController () <WKNavigationDelegate>

@property (nonatomic, strong) WKWebView *webView;
@property (nonatomic, strong) NSURL *widgetUrl;

@end

@implementation Enable3WebViewController

- (instancetype)initWithWidgetUrl:(NSURL *)widgetUrl {
    self = [super initWithNibName:nil bundle:nil];

    if (self) {
        _widgetUrl = widgetUrl;
    }

    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];

    self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
    self.webView.navigationDelegate = self;
    self.webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    [self.view addSubview:self.webView];

    NSURLRequest *request = [NSURLRequest requestWithURL:self.widgetUrl];
    [self.webView loadRequest:request];
}

- (void)webView:(WKWebView *)webView
decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {

    NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;

    if ([response isKindOfClass:[NSHTTPURLResponse class]] && response.statusCode == 401) {
        decisionHandler(WKNavigationResponsePolicyCancel);

        [self requestNewWidgetUrl];

        return;
    }

    decisionHandler(WKNavigationResponsePolicyAllow);
}

- (void)requestNewWidgetUrl {
    // Call your backend here.
    // Your backend should call Enable3 and return a fresh widget URL.
    //
    // After receiving a new URL:
    //
    // NSURLRequest *request = [NSURLRequest requestWithURL:newWidgetUrl];
    // [self.webView loadRequest:request];
}

@end

```

{% endcode %}

***

## Expired widget URL handling <a href="#expired-widget-url-handling" id="expired-widget-url-handling"></a>

The widget URL contains a temporary authentication token. If the token expires, the WebView may return `401 Unauthorized`.

Recommended handling:

1. Detect `401 Unauthorized` in the WebView.
2. Call your backend to request a fresh Enable3 widget URL.
3. Reload the WebView with the new URL.
4. Avoid infinite reload loops. If refreshing fails, show an error state with a retry button.

{% hint style="danger" %}

## Security recommendations <a href="#security-recommendations" id="security-recommendations"></a>

* Do not store the Enable3 API key in the mobile app.
* Do not call the Enable3 integration API directly from the mobile app.
* Always request the widget URL through your backend.
* Use HTTPS only.
* Enable JavaScript only for trusted Enable3 widget URLs.
* Do not inject custom JavaScript unless it is required and reviewed.
* Do not allow the WebView to freely navigate to untrusted external domains.
  {% endhint %}
