# Documentation & Setup Guide

Learn WebToNative setup, APIs, plugins, and app configuration with step-by-step documentation for Android and iOS app development.

These docs are your reference for the **WebToNative JavaScript APIs** the native features you call straight from your web pages once your app is built on [webtonative.com](https://www.webtonative.com/convert). Push notifications, biometric login, geolocation, barcode scanning, in-app purchases, device info, and much more are all available through a single JavaScript bridge.

## How it works

When your site runs inside a WebToNative app, it gains access to a JavaScript bridge that connects your web code to the device's native capabilities. The idea is simple:

1. **Add the bridge to your site once.** You include the WebToNative JavaScript in your pages, either through a script tag or the npm package. The exact link and setup steps are on the [Getting Started](/javascript-apis/getting-started) page.
2. **Call the feature you need.** Once the bridge is loaded, every API becomes available either through the global `WTN` object or as a named import from the package. Each API page shows both styles, plus the values you get back on Android and iOS.

The same pattern works for every API. Reading device info, prompting for Face ID, scanning a barcode, or triggering a push notification all follow the identical load-once, then-call approach so once you've used one API, you've effectively learned them all.

## Find the API you need

Each API has its own page with usage examples and platform notes. Browse by area:

| Area            | APIs                                                                                                                                                                                                 |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| App & UI        | Status Bar, Pull to Refresh, Bottom Navigation, Screen Control, Close App, Dynamic App Icon                                                                                                          |
| Device          | [Device Info](/javascript-apis/device-info), [Geo Location](/javascript-apis/geo-location), [Background Location](/javascript-apis/background-location), Native Contacts, Haptic Feedback, Clipboard |
| Media & Files   | [Barcode Scan](/javascript-apis/barcode-scan-1), Media Player, Download Files, Download Manager, File Sharing, Printing                                                                              |
| Notifications   | OneSignal, Firebase Notifications, Notification View                                                                                                                                                 |
| Monetization    | AdMob, In-App Purchases, [RevenueCat](/javascript-apis/revenue-cat-1), Offer Card                                                                                                                    |
| Auth & Security | [Biometric Authentication](/javascript-apis/biometric-authentication), Social Login, App Tracking Transparency                                                                                       |
| Analytics       | Firebase Analytics, AppsFlyer, Facebook App Events, App Review                                                                                                                                       |

New to the bridge? Begin with [JS APIs → Getting Started](/javascript-apis/getting-started), then pick whichever API you need.

## Native Controls & JS Bridge

Beyond the APIs, you can control native UI elements and trigger JavaScript from the native side using the `w2n://` URL scheme. See [Native Controls & JS Bridge Functions](/javascript-apis/native-controls-and-js-bridge-functions) for the full reference..

## Setup & Integrations

Plugins that need keys or store-side setup before they work  push notifications, in-app purchases, social login, and AdMob are documented in our [support section](https://www.webtonative.com/support).

## Need help?

If something isn't covered here or isn't working as expected, reach out:

* Email: <support@webtonative.com>
* Support: [webtonative.com/support](https://www.webtonative.com/support)


# Introduction

Welcome to webtonative.com&#x20;

The best way to get acquainted with WebToNative is to head over to <https://www.webtonative.com/convert>, type in any website URL, and build a sample app.

Additional configuration options further enhance the native Android & iOS mobile apps that WebToNative will generate for you.

Some of the available configuration options are explained below, but sometimes the best approach is simple trial and error to see how things work in your app. Please [email us](mailto:support@webtonative.com) if we can answer any further questions.[<br>](https://docs.gonative.io/general-styling)


# JavaScript API Setup

Get started with WebToNative JavaScript APIs. Learn installation, integration, and native app functionality setup.

It enables you to control native functionalities in your app directly via JavaScript.

{% tabs %}
{% tab title="Plain Js" %}
You need to load following javascript link in your application.

{% code overflow="wrap" %}

```javascript
<script src="https://unpkg.com/webtonative@1.1.11/webtonative.min.js"></script>
```

{% endcode %}
{% endtab %}

{% tab title="npm" %}
Install npm plugin for the same

```
npm install webtonative
```

{% endtab %}
{% endtabs %}


# JavaScript Status Bar API

Customize your app status bar using the WebToNative JavaScript API. Set colors, icon styles, transparency, and Android overlay options.

{% hint style="info" %}

<pre class="language-html" data-overflow="wrap"><code class="lang-html">You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

Dynamically update status bar visibility and style. To update status bar styling call following function

{% tabs %}
{% tab title="Plain Javascript" %}

```
WTN.statusBar({
    style:"light",
    color:"80ff0000",
    overlay:true //Only for android
});
```

{% endtab %}

{% tab title="npm" %}

```
import { statusBar } from "webtonative"

statusBar({
    style:"light",
    color:"80ff0000",
    overlay:true //Only for android
});
```

{% endtab %}
{% endtabs %}

* **Style** : "light" or "dark". Sets the icon colors in the status bar. Default on iOS is dark and default on Android is light. Setting Android to dark icons requires Marshmallow 6.0 or later.
* **Color**: in RRBBGG or AARRBBGG format with hex values. Sets the status bar to a solid color. On Android, it requires Lollipop 5.0 or later. Use 00000000 for completely transparent.
* **Overlay (Only For Android)**: "true" or "false". If true, web content will extend underneath the status bar. If false (default behaviour), web content will start below the bottom of the status bar.

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# Pull To Refresh API

Enable or disable pull-to-refresh using the WebToNative JavaScript API. Improve app navigation with dynamic refresh controls for Android and iOS.

Lets users refresh the current page by pulling down on the screen — the standard native "swipe down to reload" gesture. It's enabled by default on every page and can be toggled at runtime from JavaScript. It can also be turned on/off and scoped to specific pages from the **WebToNative dashboard**, without any code changes.

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

***

## Enable / Disable

Enables or disables pull-to-refresh at runtime. Takes effect immediately on the current page.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.enablePullToRefresh(true);
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { enablePullToRefresh } from "webtonative";

enablePullToRefresh(true);
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key      | Type      | Required | Description                                          |
| -------- | --------- | -------- | ---------------------------------------------------- |
| `status` | `Boolean` | Yes      | `true` enables pull-to-refresh, `false` disables it. |

> **Does not persist across navigation:** This call sets a temporary, in-memory override for the current page. On the next page load, the app re-applies whatever is configured on the dashboard (see below) and may override what you just set. Call `enablePullToRefresh` again after each navigation if you need the override to stick.

***

## Scoping to Specific Pages (Dashboard)

Pull-to-refresh can also be turned on/off, or limited to specific pages, from the **WebToNative dashboard** — no code required. This is useful for disabling it on pages with their own internal scroll/refresh UI while keeping it enabled everywhere else.

***

**Notes:**

* Default state is enabled on all pages unless changed on the dashboard.
* The runtime JavaScript toggle and the dashboard setting are not independent — the dashboard setting takes over again on every page navigation, so treat `enablePullToRefresh` as a per-page override rather than a persistent setting.
* Feature was taken live on Android on May 17, 2023, and on iOS on August 12, 2026.


# Pull To Refresh API

Enable or disable pull-to-refresh using the WebToNative JavaScript API. Improve app navigation with dynamic refresh controls for Android and iOS.

{% hint style="info" %}

<pre class="language-markup" data-overflow="wrap"><code class="lang-markup">You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

Pull to Refresh is a feature that allows users to refresh the page or the content of the page by pulling down on the screen. It can be configured through the app's settings or programmatically using JavaScript. Whether the feature is disabled in the app settings, it can be re-enabled using a JavaScript function.&#x20;

{% tabs %}
{% tab title="Plain Javascript" %}
{% code overflow="wrap" %}

```
WTN.enablePullToRefresh(true);

//Pass values as true or false
//To enable pass true and to disable pass the value as false. By default Pull to refresh is enabled on all pages.
```

{% endcode %}
{% endtab %}

{% tab title="npm" %}
{% code overflow="wrap" %}

```
import { enablePullToRefresh } from "webtonative";

enablePullToRefresh(true);

//Pass values as true or false
//To enable pass true and to disable pass the value as false. By default Pull to refresh is enabled on all pages.
```

{% endcode %}
{% endtab %}
{% endtabs %}

> \*Feature was taken live on 17/05/2023

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# Close App JavaScript API

Close your Android or iOS app instantly using the WebToNative JavaScript API. Simple integration with Plain JavaScript and ES6 support.

Closes the app entirely the same result as the user quitting it from the OS app switcher. Use it to back a "Log Out & Exit" button in your website, end a kiosk-mode session, or let users leave the app from a menu instead of the hardware/gesture back action.

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

> **Platform support:** Android and iOS.

***

## JavaScript API Reference

### closeApp

Immediately closes the app. Takes no parameters and has no callback, there's nothing to configure and nothing to read back.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.closeApp();
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { closeApp } from "webtonative";

closeApp();
```

{% endtab %}
{% endtabs %}

> **This call cannot be cancelled or confirmed after the fact.** The app closes the instant it runs, with no native "Are you sure?" prompt. If you want the user to confirm first, gate the call behind your own confirmation UI, see [Common Patterns](#common-patterns) below.

***

## Common Patterns

### Confirm Before Closing

Since `closeApp` doesn't show any confirmation of its own, ask the user first with your own dialog and only call it once they agree. Replace the `window.confirm` below with your own modal if you don't want the plain browser dialog.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
function handleExitButtonClick() {
  const confirmed = window.confirm("Are you sure you want to exit the app?");

  if (confirmed) {
    window.WTN.closeApp();
  }
}
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { closeApp } from "webtonative";

function handleExitButtonClick() {
  const confirmed = window.confirm("Are you sure you want to exit the app?");

  if (confirmed) {
    closeApp();
  }
}
```

{% endtab %}
{% endtabs %}

### Closing After an Action Completes

If closing is the last step of a flow for example, logging the user out on your server first call `closeApp` only after that work finishes, so it isn't left half-done when the app quits.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
function logOutAndExit() {
  logOutFromYourBackend() // replace with your own logout call
    .then(() => {
      window.WTN.closeApp();
    })
    .catch((error) => {
      console.error("Logout failed, not closing the app:", error);
    });
}
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { closeApp } from "webtonative";

function logOutAndExit() {
  logOutFromYourBackend() // replace with your own logout call
    .then(() => {
      closeApp();
    })
    .catch((error) => {
      console.error("Logout failed, not closing the app:", error);
    });
}
```

{% endtab %}
{% endtabs %}

***

## Frequently Asked Questions

<details>

<summary>Does `closeApp` ask the user to confirm before quitting?</summary>

No. It closes the app the instant it's called, with no native prompt. Add your own confirmation dialog first if you want the user to be able to back out see [Confirm Before Closing](#confirm-before-closing).

</details>

<details>

<summary>Can I run code after `closeApp` is called?</summary>

No, treat it as the last line of any flow. The app is closing, so nothing scheduled after it (a `.then()`, a later line in the function) is guaranteed to run. Finish any required work first, then call `closeApp` last, as shown in [Closing After an Action Completes](#closing-after-an-action-completes).

</details>

***

**Notes:**

* `closeApp` takes no parameters and has no callback, there's no response to check for success or failure.

***

*Feature taken live on 05/10/2023.*


# Close App JavaScript API

Close your Android or iOS app instantly using the WebToNative JavaScript API. Simple integration with Plain JavaScript and ES6 support.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

```html
WTN.closeApp();
```

{% endtab %}

{% tab title="npm" %}

```
import { closeApp } from "webtonative";

closeApp();
```

{% endtab %}
{% endtabs %}

\*Feature was taken live on 05/10/2023

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# Device Info JavaScript API

The Device Info APIs let you read information about the device and the current state of the native app shell from your web code.

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

## Get Device Info

Returns details about the device and the installed app, such as the operating system, app version, model, language, time zone, and a unique installation id.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.deviceInfo().then(function (value) {
  console.log(value);
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { deviceInfo } from "webtonative";

deviceInfo().then((value) => {
  console.log(value);
});
```

{% endtab %}
{% endtabs %}

**Response (Android):**

```json
{
  "appId": "com.webtonative.myapp",
  "appVersion": "1",
  "appVersionCode": 1,
  "hardware": "OnePlus/OnePlus3/OnePlus3T:9/PKQ1.***203.001/****042108:user/release-keys",
  "installationId": "d29c4372-****-427b-bbc2-******d549da",
  "installationType": "debug",
  "language": "en",
  "model": "OnePlus ONEPLUS A3003",
  "operator": "Jio 4G",
  "os": "Android",
  "osVersion": "9",
  "platform": "android",
  "timeZone": "Asia/Kolkata"
}
```

**Response (iOS):**

```json
{
  "appId": "com.webtonative.com",
  "appVersion": "1",
  "appVersionCode": "1.1",
  "installationId": "C6A*****-D*69-4**E-8**2-76*****B8B2C",
  "language": "en",
  "model": "Testing iPhone",
  "os": "iOS",
  "osVersion": "15.1",
  "platform": "ios",
  "timeZone": "Asia/Kolkata"
}
```

**Response Fields:**

| Key                | Type             | Description                                                  |
| ------------------ | ---------------- | ------------------------------------------------------------ |
| `appId`            | `String`         | The application bundle / package identifier.                 |
| `appVersion`       | `String`         | The app version name.                                        |
| `appVersionCode`   | `Number\|String` | The app build / version code.                                |
| `hardware`         | `String`         | Device hardware fingerprint. **Android only.**               |
| `installationId`   | `String`         | A unique id generated for this installation of the app.      |
| `installationType` | `String`         | Build type, e.g. `"debug"` or `"release"`. **Android only.** |
| `language`         | `String`         | The device language code (e.g. `"en"`).                      |
| `model`            | `String`         | The device model name.                                       |
| `operator`         | `String`         | The mobile network operator. **Android only.**               |
| `os`               | `String`         | The operating system name (`"Android"` or `"iOS"`).          |
| `osVersion`        | `String`         | The operating system version.                                |
| `platform`         | `String`         | The platform identifier (`"android"` or `"ios"`).            |
| `timeZone`         | `String`         | The device time zone (e.g. `"Asia/Kolkata"`).                |

> **Note:** `hardware`, `installationType`, and `operator` are returned on **Android only** and are not present in the iOS response.

## Get Component Status

Returns the current visibility and state of the native UI components rendered around your web content (sidebar, navigation bars, floating buttons, modals, offer card, etc.), along with the current screen orientation and the WebView URL. Use it to keep your web UI in sync with what the native shell is currently showing.

> **Availability:** Available from **3rd June 2026** on **Android** and **iOS**.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.getComponentStatus({
  callback: function (response) {
    console.log(response);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { getComponentStatus } from "webtonative";

getComponentStatus({
  callback: (response) => {
    console.log(response);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                                                                                                    |
| ---------- | ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `keys`     | `String[]` | No       | Limit the response to specific sections. Allowed values: `"components"`, `"webView"`. When omitted, all sections are returned. |
| `callback` | `Function` | No       | Callback function invoked with the status response.                                                                            |

**Filtering the response with `keys`:**

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.getComponentStatus({
  keys: ["components", "webView"],
  callback: function (response) {
    console.log(response);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { getComponentStatus } from "webtonative";

getComponentStatus({
  keys: ["components", "webView"],
  callback: (response) => {
    console.log(response);
  },
});
```

{% endtab %}
{% endtabs %}

**Callback Response:**

```json
{
  "orientation": "portrait",
  "type": "getComponentStatus",
  "components": {
    "sidebar": {
      "visible": false
    },
    "secondaryNavigation": {
      "visible": true
    },
    "floatingButton": {
      "visible": true
    },
    "topAppBar": {
      "visible": true
    },
    "floatingActionMenu": {
      "visible": true
    },
    "multiPurposeModal": {
      "visible": false
    },
    "offerCard": {
      "visible": false
    },
    "advancedBottomNavigation": {
      "visible": true
    }
  },
  "webView": {
    "currentUrl": "Current URL"
  }
}
```

**Response Fields:**

| Key           | Type     | Description                                                                             |
| ------------- | -------- | --------------------------------------------------------------------------------------- |
| `type`        | `String` | Always `"getComponentStatus"`.                                                          |
| `orientation` | `String` | The current screen orientation (e.g. `"portrait"` or `"landscape"`).                    |
| `components`  | `Object` | The visibility state of each native component. Each entry contains a `visible` boolean. |
| `webView`     | `Object` | Information about the WebView, including `currentUrl`.                                  |

**Components:**

| Component                  | Type     | Description                                          |
| -------------------------- | -------- | ---------------------------------------------------- |
| `sidebar`                  | `Object` | Sidebar / drawer. `visible` indicates if it's shown. |
| `secondaryNavigation`      | `Object` | Secondary navigation bar.                            |
| `floatingButton`           | `Object` | Floating button.                                     |
| `topAppBar`                | `Object` | Top app bar.                                         |
| `floatingActionMenu`       | `Object` | Floating action menu.                                |
| `multiPurposeModal`        | `Object` | Multi-purpose modal.                                 |
| `offerCard`                | `Object` | Offer card.                                          |
| `advancedBottomNavigation` | `Object` | Advanced bottom navigation bar.                      |

> Each component object exposes a `visible` boolean indicating whether that component is currently shown.

**Notes:**

* getComponentStatus function Works on **Android** and **iOS** for apps built on or after **3rd June 2026**.
* Use the `keys` parameter to request only the sections you need (for example `["components", "webView"]`) to keep responses lightweight.


# Device Info JavaScript API

Retrieve device information using the WebToNative JavaScript API. Access platform, app version, OS details, and device-specific data easily.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

To Get device info call following function.

{% tabs %}
{% tab title="Plain Javascript" %}

```
WTN.deviceInfo().then(function(value){
  console.log(value)
});

//value for Android 
{
    appId: "com.webtonative.myapp"
    appVersion: "1"
    appVersionCode: 1
    hardware: "OnePlus/OnePlus3/OnePlus3T:9/PKQ1.***203.001/****042108:user/release-keys"
    installationId: "d29c4372-****-427b-bbc2-******d549da"
    installationType: "debug"
    language: "en"
    model: "OnePlus ONEPLUS A3003"
    operator: "Jio 4G"
    os: "Android"
    osVersion: "9"
    platform: "android"
    timeZone: "Asia/Kolkata"
}

//value for iOS 
{
    appId: "com.webtonative.com",
    appVersion: "1",
    appVersionCode: "1.1",
    installationId: "C6A*****-D*69-4**E-8**2-76*****B8B2C",
    language: "en",
    model: "Testing iPhone",
    os: "iOS",
    osVersion: "15.1",
    platform: "ios",
    timeZone: "Asia/Kolkata"
}
```

{% endtab %}

{% tab title="npm" %}

```
import { deviceInfo } from "webtonative"

deviceInfo().then((value) => {
  console.log(value)
});

//value for Android 
{
    appId: "com.webtonative.myapp"
    appVersion: "1"
    appVersionCode: 1
    hardware: "OnePlus/OnePlus3/OnePlus3T:9/PKQ1.***203.001/****042108:user/release-keys"
    installationId: "d29c4372-****-427b-bbc2-******d549da"
    installationType: "debug"
    language: "en"
    model: "OnePlus ONEPLUS A3003"
    operator: "Jio 4G"
    os: "Android"
    osVersion: "9"
    platform: "android"
    timeZone: "Asia/Kolkata"
}

//value for iOS 
{
    appId: "com.webtonative.com",
    appVersion: "1",
    appVersionCode: "1.1",
    installationId: "C6A*****-D*69-4**E-8**2-76*****B8B2C",
    language: "en",
    model: "Testing iPhone",
    os: "iOS",
    osVersion: "15.1",
    platform: "ios",
    timeZone: "Asia/Kolkata"
}
```

{% endtab %}
{% endtabs %}

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# Clear App Cache

This allows you to clear the application cache through a function

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](/javascript-apis/getting-started).
{% endhint %}

To clear the cache when app is running.

{% tabs %}
{% tab title="Plain Javascript" %}
WTN.clearAppCache(false);

Note :- If you want to reload the app after cache is cleared you can call the function as below.

WTN.clearAppCache(true);
{% endtab %}

{% tab title="ES5+" %}
import { clearAppCache } from "webtonative";

clearAppCache(false);

Note :- If you want to reload the app after cache is cleared you can call the function as below.

clearAppCache(true);
{% endtab %}
{% endtabs %}

Feature released on 15/08/2023


# Clear App Cache API

Clear your app cache with the WebToNative JavaScript API. Improve app performance and refresh stored data on Android and iOS.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

To clear the cache when app is running.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.clearAppCache(true);
```

Note :- If you want to reload the app after cache is cleared you can call the function with value true.

```javascript
window.WTN.clearAppData(true);
```

Note :- To clear app data call the above function.
{% endtab %}

{% tab title="npm" %}

```javascript
import { clearAppCache } from "webtonative";

clearAppCache(false);
```

Note :- If you want to reload the app after cache is cleared you can call the function as below.

```javascript
clearAppCache(true);
```

Note :- If you want to clear all stored app data (not just cache) you can call the function as below.

```javascript
clearAppData(true);
```

{% endtab %}
{% endtabs %}

Parameters:

| Key    | Type      | Description                                                                             |
| ------ | --------- | --------------------------------------------------------------------------------------- |
| Option | `Boolean` | If `true`, reloads the WebView after clearing. If `false`, the WebView is not reloaded. |

Feature released on Android on 15/08/2023\
Feature released on iOS on 01/03/2026\
Functions updated on Android and iOS on 19/05/26


# Push Notification API (One Signal)

Implement push notifications using the WebToNative JavaScript API. Send, receive, and manage notifications for Android and iOS apps.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

**getPlayerId**: It returns playerId from OneSignal - that can be used to send custom notification from OneSignalApis.

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { getPlayerId } = window.WTN.OneSignal;

getPlayerId().then(function(playerId){
  if(playerId){
    // handle for playerId
    console.log(playerId)
  }
});

```

{% endtab %}

{% tab title="npm" %}

```
import { getPlayerId } from "webtonative/OneSignal";

getPlayerId().then(function(playerId){
  if(playerId){
    // handle for playerId
    console.log(playerId)
  }
});

```

{% endtab %}
{% endtabs %}

**setExternalUserId**: To set unique user Id to OneSignal.

{% tabs %}
{% tab title="Plain Javascript" %}

<pre><code>const { setExternalUserId } = window.WTN.OneSignal;

<strong>setExternalUserId("#$%jfnkjf");
</strong>
</code></pre>

{% endtab %}

{% tab title="npm" %}

```
import { setExternalUserId } from "webtonative/OneSignal";

setExternalUserId("#$%jfnkjf");

```

{% endtab %}
{% endtabs %}

**removeExternalUserId**: To remove externalUserId

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { removeExternalUserId } = window.WTN.OneSignal;

removeExternalUserId();

```

{% endtab %}

{% tab title="npm" %}

```
import { removeExternalUserId } from "webtonative/OneSignal";

removeExternalUserId();

```

{% endtab %}
{% endtabs %}

**setTags**: To add custom data attributes to your OneSignal Users

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { setTags } = window.WTN.OneSignal;

setTags({
  tags:{
    type:'PREMIUM'
  }
});

```

{% endtab %}

{% tab title="npm" %}

```
import { setTags } from "webtonative/OneSignal";

setTags({
  tags:{
    type:'PREMIUM'
  }
});

```

{% endtab %}
{% endtabs %}

**Triggers:** [**https://documentation.onesignal.com/docs/iam-triggers**](https://documentation.onesignal.com/docs/iam-triggers)

**addTrigger**

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { addTrigger } = window.WTN.OneSignal;

addTrigger({
    key: "Trigger Key",
    value: "Trigger Value"
});

```

{% endtab %}

{% tab title="npm" %}

```
import { addTrigger } from "webtonative/OneSignal";

addTrigger({
    key: "Trigger Key",
    value: "Trigger Value"
});

```

{% endtab %}
{% endtabs %}

**addTriggers**

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { addTriggers } = window.WTN.OneSignal;

addTriggers({
    triggers: [
        {
            key: "Trigger Key 1",
            value: "Trigger Value 1"
        },
        {
            key: "Trigger Key 2",
            value: "Trigger Value 2"
        }
    ]
});

```

{% endtab %}

{% tab title="npm" %}

```
import { addTriggers } from "webtonative/OneSignal";

addTriggers({
    triggers: [
        {
            key: "Trigger Key 1",
            value: "Trigger Value 1"
        },
        {
            key: "Trigger Key 2",
            value: "Trigger Value 2"
        }
    ]
});

```

{% endtab %}
{% endtabs %}

**removeTrigger**

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { removeTrigger } = window.WTN.OneSignal;

removeTrigger({
    key: "Trigger Key"
});

```

{% endtab %}

{% tab title="npm" %}

```
import { removeTrigger } from "webtonative/OneSignal";

removeTrigger({
    key: "Trigger Key"
});

```

{% endtab %}
{% endtabs %}

**removeTriggers**

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { removeTriggers } = window.WTN.OneSignal;

removeTriggers({
    keys: [ "Trigger Key 1" , "Trigger Key 2" ]
});

```

{% endtab %}

{% tab title="npm" %}

```
import { removeTriggers } from "webtonative/OneSignal";

removeTriggers({
    keys: [ "Trigger Key 1" , "Trigger Key 2" ]
});

```

{% endtab %}
{% endtabs %}

**getTriggerValue - depricated**

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { getTriggerValue } = window.WTN.OneSignal;

getTriggerValue({
    key: "Trigger Key",
    callback: function(data){
        if(data.isSuccess){
            //data.value contains trigger value for corresponding key
        }
    }
});

```

{% endtab %}

{% tab title="npm" %}

```
import { getTriggerValue } from "webtonative/OneSignal";

getTriggerValue({
    key: "Trigger Key",
    callback: function(data){
        if(data.isSuccess){
            //data.value contains trigger value for corresponding key
        }
    }
});

```

{% endtab %}
{% endtabs %}

**getTriggers - depricated**

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { getTriggers } = window.WTN.OneSignal;

getTriggers({
    callback:function(data){
        if(data.isSuccess){
            //data.triggers contains list of active trigger
            /*
                e.g.     
                {
                    key: "Trigger Key 1",
                    value: "Trigger Value 1"
                },
                {
                    key: "Trigger Key 2",
                    value: "Trigger Value 2"
                }
            /*
        }
    }
});

```

{% endtab %}

{% tab title="npm" %}

```
import { getTriggers } from "webtonative/OneSignal";

getTriggers({
    callback:function(data){
        if(data.isSuccess){
            //data.triggers contains list of active trigger
            /*
                e.g.     
                {
                    key: "Trigger Key 1",
                    value: "Trigger Value 1"
                },
                {
                    key: "Trigger Key 2",
                    value: "Trigger Value 2"
                }
            /*
        }
    }
});

```

{% endtab %}
{% endtabs %}

## Setting Email and SMS Numbers

{% hint style="info" %}
Feature released on 12/06/2023
{% endhint %}

Available only on Android

**Setting and Logging Out Email**

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { setEmail, logoutEmail } = window.WTN.OneSignal;

setEmail({
    emailId:"abc@xyz.com"
});

logoutEmail({
    emailId:"abc@xyz.com"
});

```

{% endtab %}

{% tab title="npm" %}

```
import { setEmail, logoutEmail } from "webtonative/OneSignal";

setEmail({
    emailId:"abc@xyz.com"
});

logoutEmail();
```

{% endtab %}
{% endtabs %}

**Setting and Logging Out SMS Number**

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { setEmail, logoutEmail } = window.WTN.OneSignal;

setSMSNumber({
    smsNumber:"+911234567890"
});
//Pass Mobile Number with ISD Code

logoutSMSNumber({
    smsNumber:"+911234567890"
}); 
```

{% endtab %}

{% tab title="npm" %}

```
import { setSMSNumber, logoutSMSNumber } from "webtonative/OneSignal";

setSMSNumber({
    smsNumber:"+911234567890"
});
//Pass Mobile Number with ISD Code

logoutSMSNumber();
```

{% endtab %}
{% endtabs %}

* Changes done on 26th June, 2024 for Android targetSdk 34. Now for logout the email and number is mandatory for the required functions.

## OptIn and OptOut Functions

{% hint style="info" %}
Feature added on 06/12/2024
{% endhint %}

This can be used to manually optin and optout users from OnrSignal.

{% tabs %}
{% tab title="Plain Javascript" %}
const { optInUser, optOutUser } = window\.WTN.OneSignal;

optInUser();

optOutUser();
{% endtab %}

{% tab title="npm" %}
import { optInUser, optOutUser } from "webtonative/OneSignal";

optInUser();

optOutUser();
{% endtab %}
{% endtabs %}

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# File Download JavaScript API

Download files securely using the WebToNative JavaScript API. Enable seamless file downloads and storage within your Android and iOS app.

{% tabs %}
{% tab title="Ios" %}
You need to append "wtn-download-file=true" in query parameter.

```
<a href="download-url?wtn-download-file=true"> download File</a>
or 
window.location.href = "download-url?wtn-download-file=true";
```

{% endtab %}

{% tab title="Android" %}
For Android you don't need to do any extra handling. it will work for App if it works for Browser.
{% endtab %}
{% endtabs %}

In iOS For blob files download

{% tabs %}
{% tab title="iOS" %}
You need to append "filename=your-file-name.extension" in query parameter.

```
For example 
For video - blob:https://video_url?filename="abc.mp4"
For image - blob:https://image_url?filename="myImage.png"
For pdf - blob:https://pdf_url?filename="document.pdf"
```

{% endtab %}
{% endtabs %}

Blob feature added on 03/11/2023

Additonal Support for iOS Blob Download

{% tabs %}
{% tab title="iOS" %}
Alternate method

```html
You'll need to import the javascript file in your website.

<script src="https://unpkg.com/webtonative@1.0.89/webtonative.min.js"></script>

window.WTN.downloadBlobFile({
    fileName:"your_file_name.pdf",
    downloadUrl:link_to_the_file,    
    shareFileAfterDownload: true,
    openFileAfterDownload: false
})

Pass the filename and blob file url for example - blob:https://pdf_url
shareFileAfterDownload - Opens the share option
openFileAfterDownload - opens the file in a window inside app

Function added on 06/08/2024
Function updated on 02/02/2026
```

{% endtab %}
{% endtabs %}

Additional Support for Android File Download

{% tabs %}
{% tab title="Android" %}

```
You'll need to import the javascript file in your website.

<script src="https://unpkg.com/webtonative@1.0.58/webtonative.min.js"></script>

window.WTN.customFileDownload({
    fileName:"your_file_name.pdf",
    downloadUrl:line_to_the_file
    mimeType:"application/pdf",
    cookies:"",
    isBlob:false,
    userAgent:"",
    openFileAfterDownload:true
})

Pass the filename and blob file url for example - blob:https://pdf_url

Function added on 12/09/2024
```

{% endtab %}
{% endtabs %}

fileName - The desired file name for download

downloadUrl - Link to the file download location

mimeType (optional) - The download file mimeType to support opening the file in respective application and have proper extension while download

cookies (optional) - To pass any user cookies for downloading

isBlob (optional) - Boolean value, to specify if download method uses blob fle download

userAgent (optional) - To customise the userAgent while downloading

openFileAfterDownload (optional) -  Boolean Value, to open the file after it has been successfully downloaded, it is compulsory to pass fileName and mimeType for this to work when setting it true.


# Printing Options API

Print web content directly using the WebToNative JavaScript API. Configure printing options for a seamless Android experience.

The default page size is ISO\_A4, a function to set any other custom size

{% hint style="info" %}
You'll need to import the JavaScript file into your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

{% tabs %}
{% tab title="Plain JS" %}

```
window.WTN.Printing.setPrintSize({
    "printSize":"Your print size value", //Eg. ISO_A4, ISO_B1, JIS_B3
    "label":"Any custom value you want" //optional
})

```

{% endtab %}

{% tab title="npm" %}

```
import { Printing } from "webtonative"

Printing.setPrintSize({
    "printSize":"Your print size value", //Eg. ISO_A4, ISO_B1, JIS_B3
    "label":"Any custom value you want" //optional
})
```

{% endtab %}
{% endtabs %}

printSize - Value of the page size that you want. Refer to the official docs for the supported values [link](https://developer.android.com/reference/android/print/PrintAttributes.MediaSize).

label (Optional) - To set label which is supported on selected devices when the print preview is loaded.

Feature taken live on 21/08/2024

## Print Using Function

Call the function to print either by passing an html content or the url of the page/website. (Available on Android Only)

{% tabs %}
{% tab title="Plain JS" %}
{% code overflow="wrap" %}

```
window.WTN.printFunction({
    "type":"Your print type", //Eg. html or url
    "url":"Link or html content" // Url of the page/website or html content that needs to be printed
})
```

{% endcode %}
{% endtab %}

{% tab title="npm" %}

<pre><code><strong>import { printFunction } from "webtonative"
</strong><strong>
</strong><strong>printFunction({
</strong>    "type":"Your print type", //Eg. html or url
    "url":"Link or html content" // Url of the page/website or html content that needs to be printed
})
</code></pre>

{% endtab %}
{% endtabs %}

\*Feature was taken live on 01/07/2024 (Available on Android Only)


# AdMob

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

```
const {
    bannerAd,
    fullScreenAd,
    rewardsAd,
} = WTN.AdMob;

bannerAd({
  adId:"ca-app-pub-3940256099942544/6300978111"
})

fullScreenAd({
  adId:"ca-app-pub-3940256099942544/1033173712",
  fullScreenAdCallback: (value) => {
    console.log(value)
  }
})

rewardsAd({
  adId:"ca-app-pub-3940256099942544/5224354917",
  rewardsAdCallback: (value) => {
    console.log(value)
  }
})


/** Example for fullScreenAd
  fullScreenAd({
    adId:"ca-app-pub-3940256099942544/1033173712",
    fullScreenAdCallback: (value) => {
      console.log(value);
      try{
        let response = JSON.parse(value);
        let status = response.status;
        let error = response.error;
        let additionalData = response.additionalData;
        let rewardsData = response.rewardsData;
        // Your code to handle the values accordingly.
      }
      catch(e){
        console.log("JSON parse error : ",e);
      }
    }
  })
*/
```

{% endtab %}

{% tab title=" ES5+" %}

```
import {
  bannerAd,
  fullScreenAd,
  rewardsAd,
} from "webtonative/AdMob";

bannerAd({
  adId:"ca-app-pub-3940256099942544/6300978111"
})

fullScreenAd({
  adId:"ca-app-pub-3940256099942544/1033173712",
  fullScreenAdCallback: (value) => {
    console.log(value)
  }
})

rewardsAd({
  adId:"ca-app-pub-3940256099942544/5224354917",
  rewardsAdCallback: (value) => {
    console.log(value)
  }
})


/** Example for fullScreenAd
  fullScreenAd({
    adId:"ca-app-pub-3940256099942544/1033173712",
    fullScreenAdCallback: (value) => {
      console.log(value);
      try{
        let response = JSON.parse(value);
        let status = response.status;
        let error = response.error;
        let additionalData = response.additionalData;
        let rewardsData = response.rewardsData;
        // Your code to handle the values accordingly.
      }
      catch(e){
        console.log("JSON parse error : ",e);
      }
    }
  })
*/
```

{% endtab %}
{% endtabs %}

**bannerAd, fullScreenAd and rewardsAd** can be called for displaying the particular ad.

* **fullScreenAdCallback and rewardsAdCallback** - This callback function is called on successfully displaying the ad, when it is dismissed or in case of any error with values like **status, error, additionalData and rewardsData.**
* **status -** Values for status will be&#x20;
  * "success" - When ad is show successfully.
  * "adDismissed" - When ad is dismissed by user.
  * "adLoadFailure" - When ad could not be loaded.
  * "adError" - When ad could not be loaded on full screen.
  * "rewardSuccess" - When user completes the reward ad.

#### AdMob Ad Load Success/Failure Callback (Webtonative Configuration)

`window.admobLoadCallback` is called to report the success or failure of AdMob ads loaded via Webtonative configuration.

\*Taken Live on 19/12/25

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# AdMob JavaScript API

Integrate Google AdMob into your app using the WebToNative JavaScript API. Display banner, interstitial, and rewarded ads with ease.

{% hint style="info" %}
You'll need to import the JavaScript file into your website before starting from this [link](/javascript-apis/getting-started).
{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
const {
    bannerAd,
    fullScreenAd,
    rewardsAd,
} = WTN.AdMob;

bannerAd({
  adId:"ca-app-pub-3940256099942544/6300978111"
})

fullScreenAd({
  adId:"ca-app-pub-3940256099942544/1033173712",
  fullScreenAdCallback: (value) => {
    console.log(value)
  }
})

rewardsAd({
  adId:"ca-app-pub-3940256099942544/5224354917",
  rewardsAdCallback: (value) => {
    console.log(value)
  }
})


/** Example for fullScreenAd
  fullScreenAd({
    adId:"ca-app-pub-3940256099942544/1033173712",
    fullScreenAdCallback: (value) => {
      console.log(value);
      try{
        let response = JSON.parse(value);
        let status = response.status;
        let error = response.error;
        let additionalData = response.additionalData;
        let rewardsData = response.rewardsData;
        // Your code to handle the values accordingly.
      }
      catch(e){
        console.log("JSON parse error : ",e);
      }
    }
  })
*/
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import {
  bannerAd,
  fullScreenAd,
  rewardsAd,
} from "webtonative/AdMob";

bannerAd({
  adId:"ca-app-pub-3940256099942544/6300978111"
})

fullScreenAd({
  adId:"ca-app-pub-3940256099942544/1033173712",
  fullScreenAdCallback: (value) => {
    console.log(value)
  }
})

rewardsAd({
  adId:"ca-app-pub-3940256099942544/5224354917",
  rewardsAdCallback: (value) => {
    console.log(value)
  }
})


/** Example for fullScreenAd
  fullScreenAd({
    adId:"ca-app-pub-3940256099942544/1033173712",
    fullScreenAdCallback: (value) => {
      console.log(value);
      try{
        let response = JSON.parse(value);
        let status = response.status;
        let error = response.error;
        let additionalData = response.additionalData;
        let rewardsData = response.rewardsData;
        // Your code to handle the values accordingly.
      }
      catch(e){
        console.log("JSON parse error : ",e);
      }
    }
  })
*/
```

{% endtab %}
{% endtabs %}

**bannerAd, fullScreenAd and rewardsAd** can be called for displaying the particular ad.

* **fullScreenAdCallback and rewardsAdCallback** - This callback function is called on successfully displaying the ad, when it is dismissed or in case of any error with values like **status, error, additionalData and rewardsData.**
* **status -** Values for status will be
  * "success" - When ad is shown successfully.
  * "adDismissed" - When ad is dismissed by user.
  * "adLoadFailure" - When ad could not be loaded.
  * "adError" - When ad could not be loaded on full screen.
  * "rewardSuccess" - When user completes the reward ad.

#### AdMob Ad Load Success/Failure Callback (Webtonative Configuration)

`window.admobLoadCallback` is called to report the success or failure of AdMob ads loaded via Webtonative configuration.

\*Taken Live on 19/12/25

***

### AdMob UMP (GDPR Consent)

The following functions allow you to integrate Google's User Messaging Platform (UMP) for handling user consent as required by GDPR and other regional privacy regulations. Use these before loading ads to ensure compliance.

***

#### Request AdMob Consent

Requests the AdMob User Consent form from Google's User Messaging Platform (UMP).

If consent is required for the user (for example due to GDPR or regional privacy regulations), the consent screen will automatically appear. If consent is already obtained or not required for the user's region, the form will not be shown and the SDK will return the current consent status.

Use this method before loading ads to ensure compliance with privacy regulations.

```javascript
WTN.AdMob.requestAdmobConsent({ callback: (value) => {} });

// Response
{
  "type": "requestAdmobConsent",
  "status": "notRequired", // "obtained", "required", "unknown"
  "canRequestAds": true / false,

  // if got any error
  "error": "error_string"
}
```

**Response Fields**

| Field           | Type    | Description                                                                  |
| --------------- | ------- | ---------------------------------------------------------------------------- |
| `type`          | String  | Always `"requestAdmobConsent"`                                               |
| `status`        | String  | Consent status — `"notRequired"`, `"obtained"`, `"required"`, or `"unknown"` |
| `canRequestAds` | Boolean | Whether the app is allowed to request ads                                    |
| `error`         | String  | Error message, present only if an error occurred                             |

***

#### Check AdMob Consent Status

Checks the current AdMob consent status **without** displaying the consent form.

This method is useful when you only want to verify whether consent has already been obtained or is required before requesting ads. It returns the current consent status and whether the app is allowed to request ads.

```javascript
WTN.AdMob.checkAdmobConsentStatus({ callback: (value) => {} });

// Response
{
  "type": "checkAdmobConsentStatus",
  "status": "notRequired", // "obtained", "required", "unknown"
  "canRequestAds": true / false,

  // if got any error
  "error": "error_string"
}
```

**Response Fields**

| Field           | Type    | Description                                                                  |
| --------------- | ------- | ---------------------------------------------------------------------------- |
| `type`          | String  | Always `"checkAdmobConsentStatus"`                                           |
| `status`        | String  | Consent status — `"notRequired"`, `"obtained"`, `"required"`, or `"unknown"` |
| `canRequestAds` | Boolean | Whether the app is allowed to request ads                                    |
| `error`         | String  | Error message, present only if an error occurred                             |

***

#### Request AdMob Privacy Form

Displays the AdMob Privacy Options form provided by Google's User Messaging Platform.

This form allows users to review and modify their privacy and consent choices at any time, as required by GDPR and other privacy regulations. If the privacy form is not required for the user, it will not be shown and the current status will be returned.

```javascript
WTN.AdMob.requestAdmobPrivacyForm({ callback: (value) => {} });

// Response
{
  "type": "requestAdmobPrivacyForm",
  "status": "notRequired", // "required", "unknown"
  "canRequestAds": true / false,

  // if got any error
  "error": "error_string"
}
```

**Response Fields**

| Field           | Type    | Description                                                         |
| --------------- | ------- | ------------------------------------------------------------------- |
| `type`          | String  | Always `"requestAdmobPrivacyForm"`                                  |
| `status`        | String  | Privacy form status — `"notRequired"`, `"required"`, or `"unknown"` |
| `canRequestAds` | Boolean | Whether the app is allowed to request ads                           |
| `error`         | String  | Error message, present only if an error occurred                    |

***

#### Check AdMob Privacy Form Required

Checks whether the AdMob Privacy Options form needs to be shown to the user.

This does **not** display the form. It only returns the requirement status so the website can decide whether to provide a privacy options button or trigger the form later.

```javascript
WTN.AdMob.checkAdmobPrivacyFormRequired({ callback: (value) => {} });

// Response
{
  "type": "checkAdmobPrivacyFormRequired",
  "status": "notRequired", // "required", "unknown"
  "canRequestAds": true / false,

  // if got any error
  "error": "error_string"
}
```

**Response Fields**

| Field           | Type    | Description                                                         |
| --------------- | ------- | ------------------------------------------------------------------- |
| `type`          | String  | Always `"checkAdmobPrivacyFormRequired"`                            |
| `status`        | String  | Privacy form status — `"notRequired"`, `"required"`, or `"unknown"` |
| `canRequestAds` | Boolean | Whether the app is allowed to request ads                           |
| `error`         | String  | Error message, present only if an error occurred                    |

***

#### Recommended Usage Flow

{% stepper %}
{% step %}
On app start, call `requestAdmobConsent` to handle consent automatically before loading any ads.
{% endstep %}

{% step %}
Use `checkAdmobConsentStatus` if you need to verify consent state without prompting the user.
{% endstep %}

{% step %}
Provide a "Privacy Settings" button in your app that calls `requestAdmobPrivacyForm` so users can update their choices anytime.
{% endstep %}

{% step %}
Use `checkAdmobPrivacyFormRequired` to conditionally show or hide the privacy settings button.
{% endstep %}
{% endstepper %}

Feature taken live on Android and iOS on 16/03/2026


# Geolocation JavaScript API

Access device location using the WebToNative JavaScript API. Retrieve accurate geolocation data for Android and iOS applications.

On secure(https) pages browser like default navigator functions will work.

```
navigator.geolocation.getCurrentPosition(success, error, [options])
```

For reference can follow the link below.

{% embed url="<https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition>" %}

{% hint style="info" %}

<pre><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

To get GPS Status of device

{% tabs %}
{% tab title="Plain Javascript" %}

```
WTN.isDeviceGPSEnabled({
    callback:function(data){
        console.log(data.value);
    }
});
```

{% endtab %}

{% tab title="npm" %}

```
import { isDeviceGPSEnabled } from "webtonative"
```

```
isDeviceGPSEnabled({
    callback:function(data){
        console.log(data.value);
    }
});
```

{% endtab %}
{% endtabs %}

```
```

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# External Browser API

Open links in the device’s default browser using the WebToNative JavaScript API. Control external URL handling in your mobile app.

To load in external or mobile default browser.

You need to append "loadIn=defaultBrowser" in query parameter of the url you are trying to load.

```
<a href="https://www.example.com?loadIn=defaultBrowser">Load In Browser</a>
or 
window.location.href = "https://www.example.com?loadIn=defaultBrowser";
```

\*It works for both Android and iOS.

The below function is supported in Android and you'll need to import the javascript file in your website before starting from this [link](/javascript-apis/getting-started).

{% tabs %}
{% tab title="Plain Javascript" %}

```
WTN.openUrlInBrowser("https://www.webtonative.com"); 
//pass the url you want to open in the external browser.
```

{% endtab %}

{% tab title="npm" %}

```
import { openUrlInBrowser } from "webtonative";

openUrlInBrowser("https://www.webtonative.com");
//pass the url you want to open in the external browser.
```

{% endtab %}
{% endtabs %}


# Barcode Scanner API

Scan barcodes and QR codes using the WebToNative JavaScript API. Enable fast, accurate code scanning in Android and iOS apps.

Functions to scan barcodes and QR codes from your website using the device's camera — or a photo from the gallery. The WebToNative Barcode Scan plugin renders a native scanner UI (with optional multi-scan sessions, gallery picking, and full styling control) and returns the result to your website through a JavaScript callback.

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

> **Platform support:** Android and iOS, kept in close feature parity. A few small differences are called out inline below and summarized in [Platform Differences](#platform-differences).

***

## Setting Up Barcode Scan

1. Go to your **WebToNative dashboard** → **Add-ons** → **Barcode Scan** and enable it.
2. Optionally configure default scanner styling (title, colors, animation, border style, etc.) on the same add-on page — every one of these can also be overridden per call from JavaScript. See [Style Options](#style-options) for the full list and the override priority order.
3. If you want **multi-scan** sessions available at all, configure the Multi-Scan section in the dashboard (confirm modal, confirm button text, count badge text). This doesn't turn multi-scan *on* by itself — see [Multi-Scan Mode](#multi-scan-mode).

{% hint style="info" %}
**Dashboard settings are defaults, not requirements.** Anything you set in the dashboard is just the fallback used when a given call doesn't override it — see the priority order in [Style Options](#style-options).
{% endhint %}

***

## JavaScript API Reference

### BarcodeScan

Opens the native barcode/QR scanner.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
const { Format, BarcodeScan } = WTN.Barcode;

BarcodeScan({
  format: Format.QR_CODE, // optional — omit to scan all supported formats
  onBarcodeSearch: (value) => {
    console.log(value);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { Format, BarcodeScan } from "webtonative/barcode";

BarcodeScan({
  format: Format.QR_CODE,
  onBarcodeSearch: (value) => {
    console.log(value);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key               | Type       | Required | Description                                                                                                                                                                                  |
| ----------------- | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `format`          | `Format`   | No       | A single format to restrict scanning to. Omit (or pass no `format`) to scan all supported formats. See [Format Types](#format-types).                                                        |
| `onBarcodeSearch` | `Function` | No       | Called with just the scanned **string** value. Only fires for a single successful scan — it does not fire for multi-scan results, `SCAN_LIMIT_REACHED`, or errors. Use `callback` for those. |
| `callback`        | `Function` | No       | Called with the **full response object** for every outcome — single scan, multi-scan, scan-limit-reached, and errors. This is the one to use once you turn on multi-scan.                    |
| `multiScan`       | `Boolean`  | No       | Opt in to a multi-scan session for this call. Only takes effect if multi-scan is also configured in the dashboard — see [Multi-Scan Mode](#multi-scan-mode). Defaults to `false`.            |
| `maxCount`        | `Number`   | No       | Overrides the dashboard's max scan count for this call (single-scan sessions ignore this).                                                                                                   |
| `allowDuplicates` | `Boolean`  | No       | Overrides the dashboard's duplicate-scan setting for this call.                                                                                                                              |
| `style`           | `Object`   | No       | Per-call overrides for scanner appearance and behavior — see [Style Options](#style-options).                                                                                                |

{% hint style="info" %}
**Use `callback`, not `onBarcodeSearch`, once you turn on multi-scan.** `onBarcodeSearch(value)` only ever receives a single scanned string and is a no-op for multi-scan results, which arrive as a `scans` array instead — see [Callback Response Format](#callback-response-format).
{% endhint %}

***

## Format Types

Pass one of these to `format` to restrict scanning to a single symbology. Omit `format` entirely to scan for all of them at once.

| Constant             | Numeric code |
| -------------------- | ------------ |
| `Format.ALL_FORMATS` | `0`          |
| `Format.CODE_128`    | `1`          |
| `Format.CODE_39`     | `2`          |
| `Format.CODE_93`     | `4`          |
| `Format.CODABAR`     | `8`          |
| `Format.DATA_MATRIX` | `16`         |
| `Format.EAN_13`      | `32`         |
| `Format.EAN_8`       | `64`         |
| `Format.ITF`         | `128`        |
| `Format.QR_CODE`     | `256`        |
| `Format.UPC_A`       | `512`        |
| `Format.UPC_E`       | `1024`       |
| `Format.PDF417`      | `2048`       |
| `Format.AZTEC`       | `4096`       |

The scanned `format` in a **result** can also be one of these additional names, which aren't independently selectable via `format` but can come back when scanning all formats:

| Result-only format                                           | Notes                                                                            |
| ------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| `MICRO_QR`                                                   |                                                                                  |
| `GS1_DATABAR`, `GS1_DATABAR_EXPANDED`, `GS1_DATABAR_LIMITED` |                                                                                  |
| `MICRO_PDF417`                                               | **iOS 17.4+ only** — Android has no equivalent symbology and never returns this. |
| `UNKNOWN`                                                    |                                                                                  |

***

## Multi-Scan Mode

By default, `BarcodeScan` closes the scanner immediately after one successful scan. Multi-scan keeps the scanner open, collecting codes into a session, until the user taps "Done" or the configured max count is reached.

**Turning it on requires both of these — either alone is not enough:**

1. Multi-Scan is configured in the dashboard (Add-ons → Barcode Scan → Multi-Scan section).
2. The call itself passes `multiScan: true`.

```javascript
BarcodeScan({
  multiScan: true,
  maxCount: 10,
  allowDuplicates: false,
  callback: (response) => {
    if (response.scans) {
      console.log(`Collected ${response.scans.length} codes`, response.scans);
    }
  },
});
```

* The session ends when the user taps "Done", **or** automatically once `maxCount` is reached (whichever happens first) — see the two multi-scan response shapes in [Callback Response Format](#callback-response-format).
* `maxCount` itself is always capped at 10,000 scans per session regardless of what you configure.
* `allowDuplicates: false` (the default) means the same code scanned twice in one session is only counted once.

***

## Style Options

Every field below can be set in three places, in this priority order (highest wins):

1. **This call** — pass it under `style` in `BarcodeScan({ style: { ... } })`.
2. **Dashboard default** — set once in Add-ons → Barcode Scan.
3. **Hard-coded default** — used if neither of the above is set.

```javascript
BarcodeScan({
  style: {
    title: "Scan a product",
    instructionText: "Align the code within the frame",
    scanWindowSize: "medium",
    scanAnimation: "sweep",
    borderStyle: "cornered",
    overlayDarkness: 45,
    flashlightButton: true,
    galleryButton: true,
    successVisualFeedback: true,
    visualFeedbackType: "checkmark",
    beepOnScan: true,
    vibrateOnScan: true,
    multiScanMode: { confirmModal: false, confirmButtonText: "Done", countBadgeText: "{{count}} scanned" },
  },
  callback: (response) => console.log(response),
});
```

| Key                               | Type      | Default                                                                                                                             | Description                                                                                                                           |
| --------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `title` / `subtitle`              | `String`  | `""`                                                                                                                                | Header text above the scan window.                                                                                                    |
| `instructionText`                 | `String`  | `""`                                                                                                                                | Guidance text near the scan window.                                                                                                   |
| `instructionPosition`             | `String`  | `"above"`                                                                                                                           | `"above"` \| `"inside"` \| `"below"`                                                                                                  |
| `footerText`                      | `String`  | `""`                                                                                                                                | Text shown at the bottom of the screen.                                                                                               |
| `discardDialog`                   | `Object`  | `{title:"Discard items?", subtitle:"All scanned results will be lost", primaryButtonText:"Go Back", secondaryButtonText:"Discard"}` | Confirmation dialog shown when the user backs out with unsaved scans.                                                                 |
| `scanWindowSize`                  | `String`  | `"medium"`                                                                                                                          | `"small"` \| `"medium"` \| `"large"`                                                                                                  |
| `scanAnimation`                   | `String`  | `"sweep"`                                                                                                                           | `"sweep"` \| `"pulse"` — identical on both platforms.                                                                                 |
| `borderStyle`                     | `String`  | `"cornered"`                                                                                                                        | `"cornered"` \| `"full"`                                                                                                              |
| `cornerRadius`                    | `Number`  | `0`                                                                                                                                 | Corner radius of the scan window border. **Android clamps this to ≥ 0; iOS does not** — a negative value on iOS passes through as-is. |
| `borderThickness`                 | `String`  | `"default"`                                                                                                                         | `"default"` \| `"thin"` \| `"bold"` (also accepted as `"thick"`)                                                                      |
| `defaultZoom`                     | `String`  | `"1"`                                                                                                                               | Initial camera zoom. **Parsing differs by platform** — see the hint below.                                                            |
| `overlayDarkness`                 | `Number`  | `45`                                                                                                                                | `0`–`100`, darkness of the area outside the scan window.                                                                              |
| `flashlightButton`                | `Boolean` | `true`                                                                                                                              | Show a flashlight toggle.                                                                                                             |
| `galleryButton`                   | `Boolean` | `true`                                                                                                                              | Show a "scan from gallery photo" button.                                                                                              |
| `successVisualFeedback`           | `Boolean` | `true`                                                                                                                              | Show a visual confirmation on successful scan.                                                                                        |
| `visualFeedbackType`              | `String`  | `"checkmark"`                                                                                                                       | `"checkmark"` \| `"flash"` `"`                                                                                                        |
| `beepOnScan`                      | `Boolean` | `true`                                                                                                                              | Play a beep on successful scan.                                                                                                       |
| `vibrateOnScan`                   | `Boolean` | `true`                                                                                                                              | Vibrate on successful scan.                                                                                                           |
| `multiScanMode.confirmModal`      | `Boolean` | `false`                                                                                                                             | Ask for confirmation before finalizing a multi-scan session.                                                                          |
| `multiScanMode.confirmButtonText` | `String`  | `"Done"`                                                                                                                            |                                                                                                                                       |
| `multiScanMode.countBadgeText`    | `String`  | `""`                                                                                                                                | Supports a `{{count}}` template token, e.g. `"{{count}} scanned"`.                                                                    |

***

## Callback Response Format

All responses are delivered to `callback` (and, for single scans only, the scanned value alone is also delivered to `onBarcodeSearch`) tagged with `"type": "BARCODE_SCAN"`.

### Single scan

```json
{ "type": "BARCODE_SCAN", "success": true, "value": "1234567890128", "format": "EAN_13" }
```

### Multi-scan — user tapped "Done"

```json
{
  "type": "BARCODE_SCAN",
  "success": true,
  "scans": [
    { "value": "1234567890128", "format": "EAN_13" },
    { "value": "9788809000000", "format": "EAN_13" }
  ]
}
```

### Multi-scan — hit the max count automatically

Same shape as above, plus a `status` field. This is **not an error** — `success` is still `true`.

```json
{
  "type": "BARCODE_SCAN",
  "success": true,
  "status": "SCAN_LIMIT_REACHED",
  "scans": [ { "value": "...", "format": "..." } ]
}
```

### Error

```json
{ "type": "BARCODE_SCAN", "success": false, "error": "CAMERA_PERMISSION_DENIED" }
```

**All possible `error` values:**

| Code                           | Platform     | Fires when                                                                                                                                                       |
| ------------------------------ | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CAMERA_PERMISSION_DENIED`     | Both         | Camera permission denied.                                                                                                                                        |
| `CAMERA_INITIALIZATION_FAILED` | Both         | The camera couldn't be started (no back camera, or the capture session failed to initialize).                                                                    |
| `NO_BARCODE_FOUND`             | Both         | A gallery photo was picked but no recognizable barcode was found in it.                                                                                          |
| `INVALID_IMAGE`                | Both         | The picked gallery image couldn't be read/decoded. On iOS, this is also used if the photo picker itself times out or returns nothing.                            |
| `SCANNER_ERROR`                | **iOS only** | The underlying image-recognition request throws while scanning a gallery photo. Android folds this same failure into `INVALID_IMAGE` instead of a separate code. |
| `SCAN_DISCARDED`               | Both         | The user backed out of the scanner (back press, outside tap, or confirming "Discard" in the dialog) with zero scans collected.                                   |

***

## Platform Differences

* **`defaultZoom` parsing** — see the hint under [Style Options](#style-options).
* **`cornerRadius` clamping** — Android clamps to `≥ 0`; iOS allows negative values through unmodified.
* **`SCANNER_ERROR` vs `INVALID_IMAGE`** — iOS reports gallery-scan image-processing failures as `SCANNER_ERROR`; Android reports the same situation as `INVALID_IMAGE`.
* **`MICRO_PDF417` result format** — only ever returned on iOS 17.4+; Android's scanning engine has no equivalent symbology.

***

## Frequently Asked Questions

<details>

<summary>Why doesn't `onBarcodeSearch` fire for my multi-scan results?</summary>

`onBarcodeSearch` only ever receives a single scanned string, for single-scan sessions. Use `callback` instead — it receives the full response object for every outcome, including the `scans` array for multi-scan.

</details>

<details>

<summary>I set `multiScan: true` but the scanner still closes after one scan — why?</summary>

Multi-scan also requires the Multi-Scan section to be configured in your WebToNative dashboard (Add-ons → Barcode Scan). Passing `multiScan: true` from JavaScript alone is not enough — see [Multi-Scan Mode](#multi-scan-mode).

</details>

<details>

<summary>Is `SCAN_LIMIT_REACHED` an error I need to handle differently?</summary>

No — it's delivered with `success: true` and a full `scans` array, exactly like a normal "Done"-triggered multi-scan completion. Treat it the same way; the only difference is *why* the session ended.

</details>

<details>

<summary>What format types can I select with `format`, versus what can come back in a result?</summary>

`format` only accepts the fourteen selectable constants in [Format Types](#format-types). A handful of additional names — `MICRO_QR`, the `GS1_DATABAR` variants, `MICRO_PDF417` (iOS only), and `UNKNOWN` — can appear in a scan **result** but aren't individually selectable; they only show up when scanning for all formats.

</details>


# Barcode Scanner API

Scan barcodes and QR codes using the WebToNative JavaScript API. Enable fast, accurate code scanning in Android and iOS apps.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { Format, BarcodeScan } = WTN.Barcode;
BarcodeScan({
  format: Format.QR_CODE, // optional
  onBarcodeSearch: (value) => {
    console.log(value);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```
import { Format, BarcodeScan } from "webtonative/barcode"
BarcodeScan({
  format: Format.QR_CODE, // optional
  onBarcodeSearch: (value) => {
    console.log(value);
  },
});
```

{% endtab %}
{% endtabs %}

**BarcodeScan**: Call BarcodeScan to scan barcode from native apps.

* **format**: This is optional parameter is you don't pass it, it will try to scan all the available barcode formats listed below.
* **onBarcodeSearch**: This callback function will be called when it successfully scans barcode.

## Format Types

Following is the list of all the supported format types

1. Format.ALL\_FORMATS
2. Format.QR\_CODE
3. Format.UNKNOWN
4. Format.CODE\_128
5. Format.CODE\_39
6. Format.CODE\_93
7. Format.CODABAR
8. Format.EAN\_13
9. Format.EAN\_8
10. Format.ITF
11. Format.UPC\_A
12. Format.PDF417
13. Format.AZTEC

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# In-App Purchase - iOS Integration

Integrate iOS in-app purchases using the WebToNative JavaScript API. Enable secure subscriptions and one-time purchases in your app.

Functions to sell and restore Apple In-App Purchases from your app. The WebToNative In-App Purchase plugin wraps Apple's StoreKit natively, giving you a single JavaScript API to initiate a purchase, return the App Store receipt to your page, and fetch previously completed transactions for verification.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

{% hint style="info" %}
If you have not set up In-App Purchase in your Apple account yet, see [In-App Purchase iOS Setup](https://docs.webtonative.com/plugin/in-app-purchase-ios-setup) for how to configure IAP in iOS.
{% endhint %}

***

## Initiate a Purchase

Starts the StoreKit purchase flow for the given product ID. On completion, the callback receives the App Store receipt, which you then verify with Apple (see [Verify a Transaction](#verify-a-transaction)).

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.inAppPurchase({
  productId: "Product Id of IAP",
  accountToken: "11112222-3333-4444-5555-666677778888",
  callback: function (data) {
    if (data.isSuccess) {
      // Send data.receiptData to your server to verify the transaction.
      // refer: https://developer.apple.com/documentation/appstorereceipts/verifyreceipt
      console.log(data.receiptData);
    }
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { inAppPurchase } from "webtonative/InAppPurchase";

inAppPurchase({
  productId: "Product Id of IAP",
  accountToken: "11112222-3333-4444-5555-666677778888",
  callback: (data) => {
    if (data.isSuccess) {
      // Send data.receiptData to your server to verify the transaction.
      // refer: https://developer.apple.com/documentation/appstorereceipts/verifyreceipt
      console.log(data.receiptData);
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key            | Type       | Required | Description                                                                                                                                                                              |
| -------------- | ---------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `productId`    | `String`   | Yes      | The full product ID of the IAP as configured in App Store Connect.                                                                                                                       |
| `accountToken` | `String`   | No       | A UUID that associates the purchase with a user in your system. Surfaced in the verified receipt as `app_account_token` for server-side reconciliation. **See availability note below.** |
| `callback`     | `Function` | No       | Callback function invoked with the response.                                                                                                                                             |

{% hint style="warning" %}
**`accountToken` availability:** The `accountToken` parameter went live on iOS on **09/06/26**. It is only honored by iOS builds generated on or after **09/06/26** — earlier builds ignore the field. Regenerate your build after this date to use it.
{% endhint %}

**Callback Response:**

| Key           | Type      | Description                                                                      |
| ------------- | --------- | -------------------------------------------------------------------------------- |
| `isSuccess`   | `Boolean` | `true` if the purchase completed successfully, `false` otherwise.                |
| `receiptData` | `String`  | The base64-encoded App Store receipt. Send this to your server for verification. |

<details>

<summary>Troubleshooting — no products returned?</summary>

If the purchase was unsuccessful and you didn't see any products, check the following:

1. Does the project's Bundle ID match the App ID from the iOS Developer Center?
2. Is the full product ID being used when calling the purchase method?
3. Is the Paid Applications Contract in effect on App Store Connect? It can take hours to days to move from pending to accepted after you submit it.
4. Have you waited several hours since adding your product to App Store Connect? Product additions may be active immediately or may take some time.
5. Check [Apple Developer System Status](https://developer.apple.com/system-status/). If the sandbox doesn't respond with a status value, the iTunes sandbox may be down.
6. Have IAPs been enabled for the App ID? (Did you select **Cleared for Sale** earlier?)
7. Have you tried deleting the app from your device and reinstalling it?
8. Still stuck? Contact us.

</details>

***

## Verify a Transaction

There are two ways to fulfill a user's purchase: **server-side** and **on-device**. Server-side verification is recommended when purchases are tied to a user account, as it is more secure. When a purchase is made, the receipt is returned to your page through the callback; send it to your server, which verifies it with Apple and then credits or unlocks the purchased item. During this process, associate the purchase with the logged-in user in your system.

For example, your website may have a free membership tier and a premium tier. Display the purchase page only inside the logged-in section of your site. When the purchase completes, the callback fires with `receiptData`.

Your web server should POST to `https://buy.itunes.apple.com/verifyReceipt` with the following body:

```json
{
  "receipt-data": "xxxxxxxxxxxxxxx",
  "password": "shared secret from App Store Connect",
  "exclude-old-transactions": true
}
```

If `exclude-old-transactions` is set to `true`, Apple returns only the latest transaction for auto-renewing subscriptions. Otherwise, you get the entire subscription history.

Apple's server returns HTTP status 200 with a JSON object (see example below). If the JSON object is `{"status":21007}`, the receipt was generated from the sandbox/test environment — in that case, re-do the POST to `https://sandbox.itunes.apple.com/verifyReceipt`.

Assuming the response has `status` 0, verify that the receipt's `bundle_id` matches your app and check what products were purchased. Save the `receipt-data` in your database so you can verify successful auto-renewals — it serves as a token you can reuse to get updated subscription information. At this point, your server should provide whatever the user purchased (premium content, virtual currency, etc.).

If `status` is any value other than 0, or Apple's endpoint does not return HTTP 200, or the request to Apple fails, **do not fulfill the purchase**. See the [App Store receipt status codes](https://developer.apple.com/documentation/appstorereceipts/status) for other possible values. Your web server should respond with a JSON object with `success` set to `false`. We recommend logging Apple's response — especially the `status` field — for troubleshooting.

An example response from App Store Connect looks as follows:

```json
{
  "receipt": {
    "receipt_type": "ProductionSandbox",
    "adam_id": 0,
    "app_item_id": 0,
    "bundle_id": "com.webtonative.com",
    "application_version": "1",
    "download_id": 0,
    "version_external_identifier": 0,
    "receipt_creation_date": "2022-04-27 11:20:28 Etc/GMT",
    "receipt_creation_date_ms": "1651058428000",
    "receipt_creation_date_pst": "2022-04-27 04:20:28 America/Los_Angeles",
    "request_date": "2022-07-07 13:52:28 Etc/GMT",
    "request_date_ms": "1657201948298",
    "request_date_pst": "2022-07-07 06:52:28 America/Los_Angeles",
    "original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
    "original_purchase_date_ms": "1375340400000",
    "original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
    "original_application_version": "1.0",
    "in_app": [
      {
        "quantity": "1",
        "product_id": "com.webtonative.com.extralives",
        "transaction_id": "2000000042206189",
        "original_transaction_id": "2000000042206189",
        "purchase_date": "2022-04-27 11:20:28 Etc/GMT",
        "purchase_date_ms": "1651058428000",
        "purchase_date_pst": "2022-04-27 04:20:28 America/Los_Angeles",
        "original_purchase_date": "2022-04-27 11:20:28 Etc/GMT",
        "original_purchase_date_ms": "1651058428000",
        "original_purchase_date_pst": "2022-04-27 04:20:28 America/Los_Angeles",
        "is_trial_period": "false",
        "in_app_ownership_type": "PURCHASED"
      }
    ]
  },
  "environment": "Sandbox",
  "status": 0
}
```

{% hint style="info" %}
When you pass `accountToken` to `inAppPurchase`, the verified receipt includes a matching `app_account_token` field. Use it on your server to reconcile the transaction with the correct user account.
{% endhint %}

***

## Auto-renewable Subscriptions

Apple automatically bills users who have purchased auto-renewable subscriptions. To check the status of a user's subscription, POST the receipt again to Apple's endpoint and inspect the `latest_receipt_info` field. You may wish to set up a regular job to walk through all active subscriptions.

Apple can also notify you of subscription status changes by posting to an endpoint you configure to handle the change events. Go to **App Store Connect → Your App → App Information** and enter the URL. See the "Status Update Notifications" section in Apple's [In-App Purchase Programming Guide](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions.html).

***

## Fetch Previous Transactions

Returns the receipt for transactions previously completed on the device. Use this to restore purchases or re-verify an existing subscription.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.getReceiptData({
  callback: function (data) {
    if (data.isSuccess) {
      // Send data.receiptData to your server to verify the transaction.
      // refer: https://developer.apple.com/documentation/appstorereceipts/verifyreceipt
      console.log(data.receiptData);
    }
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { getReceiptData } from "webtonative";

getReceiptData({
  callback: (data) => {
    if (data.isSuccess) {
      // Send data.receiptData to your server to verify the transaction.
      // refer: https://developer.apple.com/documentation/appstorereceipts/verifyreceipt
      console.log(data.receiptData);
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                  |
| ---------- | ---------- | -------- | -------------------------------------------- |
| `callback` | `Function` | No       | Callback function invoked with the response. |

**Callback Response:**

| Key           | Type      | Description                                                                      |
| ------------- | --------- | -------------------------------------------------------------------------------- |
| `isSuccess`   | `Boolean` | `true` if a receipt was retrieved successfully, `false` otherwise.               |
| `receiptData` | `String`  | The base64-encoded App Store receipt. Send this to your server for verification. |

***

## Official References

* [verifyReceipt — Apple Developer](https://developer.apple.com/documentation/appstorereceipts/verifyreceipt)
* [App Store receipt status codes](https://developer.apple.com/documentation/appstorereceipts/status)
* [In-App Purchase Programming Guide — Subscriptions](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions.html)


# In App Purchase - iOS Integration

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

If you have not done setup for In App Purchase in your apple account [click here](/plugin/in-app-purchase-ios-setup) to know how to setup IAP in iOS.

**To initiate In app purchase in your app call following method from javascript**

{% tabs %}
{% tab title="Plain JS" %}

```
window.WTN.inAppPurchase({ 
    productId : ‘Product Id of IAP’, 
    callback : function(data){ 
        var receiptData = data.receiptData;
        if(data.isSuccess){
            // use this receipt data to verify transaction from app store 
            // refer : https://developer.apple.com/documentation/appstorereceipts/verifyreceipt
        }
     } 
 })
 
 
```

{% endtab %}

{% tab title="ES 6+" %}

```
import { inAppPurchase } from "webtonative/InAppPurchase"

inAppPurchase({ 
    productId : ‘Product Id of IAP’, 
    callback : function(data){ 
        var receiptData = data.receiptData;
        if(data.isSuccess){
            // use this receipt data to verify transaction from app store 
            // refer : https://developer.apple.com/documentation/appstorereceipts/verifyreceipt
        }
     } 
 })
```

{% endtab %}
{% endtabs %}

![](https://lh4.googleusercontent.com/mpHbKsU8hHjzQfsDItuuV3GYr-VQtUWhhjVQsoF7xcMvlHoYcC6X7DE1HlC7CJyx8q-5zTPGhHL2wFxuvE1crwmDqPWvavHY8GOUJ4TyZwW5cVExpr06FLVG4rlBjXNNqHbQpEc0uUg4wVy1vkc)

Note: If the run was unsuccessful and you didn’t see any products, then there are a number of things to check.

1. Does the project’s Bundle ID match the App ID from the iOS Development Center?&#x20;
2. Is the full product ID being used when calling purchase method.&#x20;
3. Is the Paid Applications Contract in effect on iTunes Connect? It can take hours to days for them to go from pending to accepted from them moment you submit them.&#x20;
4. Have you waited several hours since adding your product to App Store Connect? Product additions may be active immediately or may take some time.&#x20;
5. Check Apple Developer System Status. Alternatively, try this link. If it doesn’t respond with a status value, then the iTunes sandbox may be down. The status codes are explained in Apple’s Validating Receipts With the App Store documentation.&#x20;
6. Have IAPs been enabled for the App ID? (Did you select Cleared for Sale earlier?)&#x20;
7. Have you tried deleting the app from your device and reinstalling it?&#x20;
8. Still stuck? Contact Us

## Verify Transaction

There are two methods available to fulfill your user’s purchases: server-side, and on-device. Server-side verification is generally recommended if purchases are to be associated with a user account, as it is more secure. When an in-app purchase is made, the purchase data will be sent to your web server, which will credit or fulfill the purchased item after it has verified the purchase with Apple. During this process, you should associate the purchase with the logged-in user in your system.

For example, your website may have user logins with a free membership tier, and a premium membership tier. In this case, you should only display the purchase page within the logged-in section of your website. When the purchase is made, callback function will be called with receiptData

Your web server needs to create a post to <https://buy.itunes.apple.com/verifyReceipt> with the contents:

```
{
    "receipt-data": "xxxxxxxxxxxxxxx",
    "password": "shared secret from iTunes connect",
    "exclude-old-transactions": true
}
```

If `exclude-old-transactions` is set to `true`, Apple will only return the latest transaction for auto-renewing subscriptions. Otherwise, you will get back the entire history of subscriptions.

Apple's server should return HTTP status 200 with a JSON object (see example below). If the JSON object is `{"status":21007}`, the receipt was generated from the sandbox/test environment. In that case, re-do the POST to the following url: <https://sandbox.itunes.apple.com/verifyReceipt>.

Assuming the response from Apple’s server has status 0, verify the receipt's bundle\_id matches your app, and what products have been purchased. Additionally, save the receipt-data in your database so that you can verify successful auto-renews. The receipt-data serves as a “token” you can use to get updated subscription information. At this point, your server should provide whatever it is the user has purchased (premium content, virtual currency, etc.)

If the status in the JSON is any value other than 0, or Apple’s endpoint does not return an HTTP status 200, or the request to Apple fails, do not fulfill the purchase. See <https://developer.apple.com/documentation/appstorereceipts/status> for other possible JSON status values. Your web server should respond with a JSON object with `success` set to `false`. We recommend logging the response from Apple for troubleshooting purchases, especially the `status` field.

An example response from App Store Connect will look as follows:

```
{
    "receipt": {
        "receipt_type": "ProductionSandbox",
        "adam_id": 0,
        "app_item_id": 0,
        "bundle_id": "com.webtonative.com",
        "application_version": "1",
        "download_id": 0,
        "version_external_identifier": 0,
        "receipt_creation_date": "2022-04-27 11:20:28 Etc/GMT",
        "receipt_creation_date_ms": "1651058428000",
        "receipt_creation_date_pst": "2022-04-27 04:20:28 America/Los_Angeles",
        "request_date": "2022-07-07 13:52:28 Etc/GMT",
        "request_date_ms": "1657201948298",
        "request_date_pst": "2022-07-07 06:52:28 America/Los_Angeles",
        "original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
        "original_purchase_date_ms": "1375340400000",
        "original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
        "original_application_version": "1.0",
        "in_app": [
            {
                "quantity": "1",
                "product_id": "com.webtonative.com.extralives",
                "transaction_id": "2000000042206189",
                "original_transaction_id": "2000000042206189",
                "purchase_date": "2022-04-27 11:20:28 Etc/GMT",
                "purchase_date_ms": "1651058428000",
                "purchase_date_pst": "2022-04-27 04:20:28 America/Los_Angeles",
                "original_purchase_date": "2022-04-27 11:20:28 Etc/GMT",
                "original_purchase_date_ms": "1651058428000",
                "original_purchase_date_pst": "2022-04-27 04:20:28 America/Los_Angeles",
                "is_trial_period": "false",
                "in_app_ownership_type": "PURCHASED"
            }
        ]
    },
    "environment": "Sandbox",
    "status": 0
}
```

## Auto-renewable Subscriptions

Apple will automatically bill users who have purchased auto-renewable subscriptions. To check on the status of a user’s subscription, POST the receipt again to Apple’s endpoint and check the `latest_receipt_info` field. You may wish to set up a regular job to go through all active subscriptions.

Apple can also notify you of subscription status changes by posting to an endpoint you have set up to handle the change events. Go to App Store Connect -> Your App -> App Information and enter the URL. See the “Status Update Notifications” section in the [In-App Purchase Programming guide](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions.html).<br>

## To fetch previous transactions

To get details of the preious transactions done on the device the following function can be called.

{% tabs %}
{% tab title="Plain JS" %}

```
window.WTN.getReceiptData({ 
    callback : function(data){ 
        var receiptData = data.receiptData;
        if(data.isSuccess){
            // use this receipt data to verify transaction from app store 
            // refer : https://developer.apple.com/documentation/appstorereceipts/verifyreceipt
        }
     } 
 })
```

{% endtab %}

{% tab title="ES5+" %}

```
import { getReceiptData } from "webtonative"

getReceiptData({ 
    callback : function(data){ 
        var receiptData = data.receiptData;
        if(data.isSuccess){
            // use this receipt data to verify transaction from app store 
            // refer : https://developer.apple.com/documentation/appstorereceipts/verifyreceipt
        }
     } 
 })
```

{% endtab %}
{% endtabs %}

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# In-App Purchase - Android Integration

Integrate Android in-app purchases using the WebToNative JavaScript API. Enable secure subscriptions and one-time purchases in your app.

Functions to sell and restore Google Play In-App Purchases from your app. The WebToNative In-App Purchase plugin wraps Google Play Billing natively, giving you a single JavaScript API to initiate a purchase (one-time or subscription) and query the purchases a user already owns.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

{% hint style="info" %}
If you have not set up In-App Purchase in your Google Developer account yet, see [In-App Purchase Android Setup](https://docs.webtonative.com/plugin/in-app-purchase-android-setup) for how to configure IAP in Android.
{% endhint %}

***

## Initiate a Purchase

Starts the Google Play Billing purchase flow for the given product. On completion, the callback receives the purchase receipt, which you then verify with the Google Play Developer API.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.inAppPurchase({
  productId: "Product Id of IAP",
  productType: "INAPP",
  isConsumable: true,
  accountToken: "11112222-3333-4444-5555-666677778888",
  callback: function (data) {
    if (data.isSuccess) {
      // Send data.receiptData to your server to verify the purchase.
      console.log(data.receiptData);
    }
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { inAppPurchase } from "webtonative/InAppPurchase";

inAppPurchase({
  productId: "Product Id of IAP",
  productType: "INAPP",
  isConsumable: true,
  accountToken: "11112222-3333-4444-5555-666677778888",
  callback: (data) => {
    if (data.isSuccess) {
      // Send data.receiptData to your server to verify the purchase.
      console.log(data.receiptData);
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key            | Type       | Required | Description                                                                                                                       |
| -------------- | ---------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `productId`    | `String`   | Yes      | The product ID exactly as created in the Google Play Console.                                                                     |
| `productType`  | `String`   | Yes      | `"INAPP"` for one-time purchases, or `"SUBS"` for subscriptions.                                                                  |
| `isConsumable` | `Boolean`  | Yes      | Whether the product can be purchased again. See the guidance below.                                                               |
| `accountToken` | `String`   | No       | A token that associates the purchase with a user in your system, for server-side reconciliation. **See availability note below.** |
| `callback`     | `Function` | No       | Callback function invoked with the response.                                                                                      |

> **`accountToken` availability:** The `accountToken` parameter went live on **09/06/26**. It is only honored by Android builds generated on or after **09/06/26** — earlier builds ignore the field. Regenerate your build after this date to use it.

**Choosing `isConsumable`:**

| Product                     | `productType` | `isConsumable` | Behavior                                       |
| --------------------------- | ------------- | -------------- | ---------------------------------------------- |
| One-time **consumable**     | `"INAPP"`     | `true`         | User can purchase the product again and again. |
| One-time **non-consumable** | `"INAPP"`     | `false`        | User can purchase the product only once.       |
| **Subscription**            | `"SUBS"`      | `false`        | Treated as a non-consumable product.           |

<div align="center"><img src="/files/085326cb5d9088b9ae63e1fc0efabd66fbd78a72" alt=""> <figure><img src="/files/kySZIN2lRHyplcS5MtnN" alt=""><figcaption></figcaption></figure></div>

**Callback Response:**

| Key           | Type      | Description                                                       |
| ------------- | --------- | ----------------------------------------------------------------- |
| `type`        | `String`  | Always `"inAppPurchase"`. Use this to filter the callback.        |
| `isSuccess`   | `Boolean` | `true` if the purchase completed successfully, `false` otherwise. |
| `receiptData` | `Object`  | The purchase receipt. Send this to your server for verification.  |

**Example response:**

```json
{
  "type": "inAppPurchase",
  "receiptData": { },
  "isSuccess": true
}
```

***

## Query Purchases

Returns all purchases the user currently owns — active subscriptions and non-consumed one-time purchases. Use this to restore purchases or re-verify entitlements.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.getAllPurchases({
  callback: function (data) {
    if (data.isSuccess) {
      console.log(data.purchaseData);
    }
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { getAllPurchases } from "webtonative/InAppPurchase";

getAllPurchases({
  callback: (data) => {
    if (data.isSuccess) {
      console.log(data.purchaseData);
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                  |
| ---------- | ---------- | -------- | -------------------------------------------- |
| `callback` | `Function` | No       | Callback function invoked with the response. |

**Callback Response:**

| Key            | Type      | Description                                                          |
| -------------- | --------- | -------------------------------------------------------------------- |
| `type`         | `String`  | Always `"purchaseList"`. Use this to filter the callback.            |
| `isSuccess`    | `Boolean` | `true` if the query completed successfully, `false` otherwise.       |
| `purchaseData` | `Array`   | The user's active subscriptions and non-consumed one-time purchases. |

**Example response:**

```json
{
  "type": "purchaseList",
  "isSuccess": true,
  "purchaseData": [{ }, { }]
}
```

***

## Official References

* [Google Play Billing — Overview](https://developer.android.com/google/play/billing)
* [Verify purchases — Google Play Developer API](https://developer.android.com/google/play/billing/security#verify)
* [Google Play Console](https://play.google.com/console)


# In App Purchase - Android Integration

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

If you have not done setup for In App Purchase in your Google Developer account [click here](/plugin/in-app-purchase-android-setup) to know how to setup IAP in Android.

**To initiate In app purchase in your app call following method from javascript**

{% tabs %}
{% tab title="Plain JS" %}

```javascript
window.WTN.inAppPurchase({ 
    productId : ‘Product Id of IAP’,
    productType : ‘Product Type of IAP’,
    isConsumable : true or false,
    callback : function(data){
	var receiptData = data.receiptData;
	if(data.isSuccess){
	    		
	}
    }
})
```

{% endtab %}

{% tab title="npm" %}

```
import { inAppPurchase } from "webtonative/InAppPurchase"

inAppPurchase({ 
    productId : ‘Product Id of IAP’,
    productType : ‘Product Type of IAP’,
    isConsumable : true or false,
    callback : function(data){
	var receiptData = data.receiptData;
	if(data.isSuccess){
    		
	}
    } 
}) 
```

{% endtab %}
{% endtabs %}

Use the productId, the one you have used while creating a product via play console.&#x20;

productType will be “INAPP” for one time purchases and “SUBS” for subscriptions.

If the product is consumable you’ll need to pass isConsumable ‘true’ else ‘false’.

For one-time consumable products,&#x20;

isConsumable would be ‘true’. By passing true you make sure that the user can purchase again and again.

For one-time non-consumable products, isConsumable would be ‘false’. By passing false you make sure that the user can purchase only once.

Subscription can be treated as a non-consumable product i.e. isConsumable would be ‘false’.

![](https://lh4.googleusercontent.com/Ul-ASvSZWAgQ1XtPNhkkpUOFbWUI4XE0RL7tNgdlsAWdYnGiL5r1ccAFcOVsZkuJdJ_kPI04a6XtufpkvhGsQWL7s0fGrId-sMfHdEvR4dJfraKj6qXVL1kSvHTZUMlmzjNFshPCLWlUHFNr_etDujKmj3BOt-LXJX-zCGdhSLGwir9TBjUDh2raGn9V-g)                             ![](https://lh4.googleusercontent.com/sWo4XvEMj1UDKeYRKf_9d_EgNDsnoluu7E25_YPOmfsklegCAlc-fa4cd5TIR723T7ujoLqLSf9P53dGSmWKNDN1ShwZGb45MXfVAzguKcabXDy2FiLkZfC_PFkRxw59HumS-UXw31ZDf_5EZ6UW3gUeYCTD6nS9yxPjNWzmXPKydSIiZF6e7Th7jD-d4g)

<br>

**To query purchases made by user call following method from javascript**

{% tabs %}
{% tab title="Plain JS" %}

```
window.WTN.getAllPurchases({ 
    callback : function(data){
	var receiptData = data.receiptData;
	if(data.isSuccess){
	    		
	}
    }
})
```

{% endtab %}

{% tab title="npm" %}

```
import { getAllPurchases } from "webtonative/InAppPurchase"
getAllPurchases({ 
    callback : function(data){
	var purchaseData = data.purchaseData;
	//console.log(purchaseData)
    } 
}) 
```

{% endtab %}
{% endtabs %}

You can get all the purchases made by the user with active subscriptions and non-consumed one-time purchases.

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# Social Login API

Enable Google, Apple, and Facebook sign-in using the WebToNative JavaScript API. Add secure social login to Android and iOS apps.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { facebook, google, apple } = WTN.socialLogin;

//Login Commands
facebook.login({
  callback : function(value){
    console.log(value)
  }
});

google.login({
  callback : function(value){
    console.log(value)
  }
});

apple.login({
  callback : function(value){
    console.log(value)
  }
})

//Logout Commands
facebook.logout({
  callback : function(value){
    console.log(value)
  }
});

google.logout({
  callback : function(value){
    console.log(value)
  }
});
```

{% endtab %}

{% tab title="npm" %}

```
import { 
  login as loginFacebook, 
  logout as logoutFacebook 
} from "webtonative/SocialLogin/facebook"
import { 
  login as loginGoogle, 
  logout as logoutGoogle 
} from "webtonative/SocialLogin/google"
import { 
  login as loginApple 
} from "webtonative/SocialLogin/apple"

//Login Commands
loginFacebook({
  callback : function(value){
    console.log(value)
  }
});

loginGoogle({
  callback : function(value){
    console.log(value)
  }
});

loginApple({
  callback : function(value){
    console.log(value)
  }
})

//Logout Commands
logoutFacebook({
  callback : function(value){
    console.log(value)
  }
});

logoutGoogle({
  callback : function(value){
    console.log(value)
  }
});
```

{% endtab %}

{% tab title="Wordpress Plugin" %}
We  have wordpress plugin for social login integration. Kindly find webtonative wordpress plugin from below link. [**https://wordpress.org/plugins/webtonative/**](https://wordpress.org/plugins/webtonative/)
{% endtab %}
{% endtabs %}

**Callback parameter object**

{% tabs %}
{% tab title="Facebook" %}
Login :-&#x20;

```
{
    "isSuccess":true,
    "accessToken":"EXXXXXQ0iSXpNGVOCMVi000ZAnlslBJIHgXXX2dfkW4HtGLUAuuZCcESjfZXXXQZBZBV",
    "userId":"1XXXXXXXXX519425",
    "type":"fbLoginToken"
}
```

Login Error :-

```
{
    "isSuccess":false,
    "error":"Error message for logout",
    "type":"fbLoginToken"
}
```

Logout :-

```
{
    "isSuccess":true,
    "message":"Logout Success",
    "type":"fbLogOut"
}
```

{% endtab %}

{% tab title="Google" %}
Login :-&#x20;

```
{
    "isSuccess":true,
    "idToken":"EXXXXXQ0iSXpNGVOCMVi000ZHgXXX2dfkW4HtGLUAuuXXXQZBZBV",
    "type":"googleLoginToken"
}
```

Login Error :-

```
{
    "isSuccess":false,
    "error":"Error message for logout",
    "type":"googleLoginToken"
}
```

Logout :-

```
{
    "isSuccess":true,
    "message":"Logout Success",
    "type":"googleLogOut"
}
```

Logout Error:-

```
{
    "isSuccess":false,
    "error":"Error message for logout",
    "type":"googleLogOut"
}
```

{% endtab %}

{% tab title="Apple" %}
Login :-&#x20;

```
{
    "isSuccess":true,
    "idToken":"********",
    "code":"***********",
    "type":"appleLoginToken",
    *"firstName":"FirstNameHere",
    *"lastName":"LastNameHere",
    *"emailId":"support@webtonative.com"
}

```

* \*Apple only returns the user's information the first time the user authorizes the app. Persist this information from your app; subsequent authorization requests won’t contain this information.
* Once you have the user’s token(idToken), you can decode it using any general-purpose JWT library to retrieve user's information.
  {% endtab %}
  {% endtabs %}

**Wordpress plugin:** We  have wordpress plugin for social login integration. Kindly find webtonative wordpress plugin from below link. [**https://wordpress.org/plugins/webtonative/**](https://wordpress.org/plugins/webtonative/)

**Shopify plugin**: We have shopify plugin for social login integration. \
<https://apps.shopify.com/social-login-webtonative>

Note:-

Apple Login is available only in iOS.

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# Facebook App Events API

Track user actions with the WebToNative Facebook App Events API. Integrate event logging to measure app engagement and conversions.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

## Automatically Logged Events

When using the Facebook SDK, certain events in your app are automatically logged and collected for Facebook Events Manager unless you disable automatic event logging. These events are relevant for all use cases - targeting, measurement and optimisation.\
Learn more about Automatically Logged Events : \
**Android:** <https://developers.facebook.com/docs/app-events/getting-started-app-events-android#auto-events>\
**iOS:** <https://developers.facebook.com/docs/app-events/getting-started-app-events-ios#auto-events>

## Manually Log Events(Custom Events) <a href="#log-manually" id="log-manually"></a>

1\) Regular Events\
To send a custom event to Facebook, create a javascript object with the fields:

{% tabs %}
{% tab title="Plain JS" %}

```markup
const { events: FacebookEvents} = window.WTN.facebook

FacebookEvents.send({
    event: "MY_EVENT", 
    valueToSum: 10, //optional
    parameters: { // optional
        name: "webtonative"
    }
})

```

{% endtab %}

{% tab title="npm" %}

```
import { send } from "webtonative/Facebook/events"

send({
    event: "MY_EVENT", 
    valueToSum: 10, //optional
    parameters: { // optional
        name: "webtonative"
    }
})

```

{% endtab %}
{% endtabs %}

2\) Purchase Events\
There is a special case for purchases and Facebook's SDK will send the event more immediately:

{% tabs %}
{% tab title="Plain JS" %}

```markup
const { events: FacebookEvents} = window.WTN.facebook

FacebookEvents.sendPurchase({
    amount: 3.14, 
    currency: "INR",
    parameters: { // optional
        name: "webtonative"
    }
})

```

{% endtab %}

{% tab title="npm" %}

```
import { sendPurchase } from "webtonative/Facebook/events"

sendPurchase({
    amount: 3.14, 
    currency: "INR",
    parameters: { // optional
        name: "webtonative"
    }
})

```

{% endtab %}
{% endtabs %}

The list of string constants for event names and parameter keys can be found here:

* **Android**: <https://developers.facebook.com/docs/app-events/reference#standard-events>
* **iOS**: <https://developers.facebook.com/docs/app-events/reference#standard-events-2>

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# Meta Ads Apis

Functions to display Meta Audience Network ads - banner, fullscreen (interstitial), and rewarded video inside your app.

The WebToNative Meta Ads plugin integrates the native Meta Audience Network Android and iOS SDKs, so ad requests, rendering, and lifecycle events all happen natively, and are reported back to your website through a JavaScript callback.

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

> **Platform support:** Android and iOS. `setMetaAdsTestMode` currently only has an effect on Android see the note under [setMetaAdsTestMode](#setmetaadstestmode).

***

## Setting Up Meta Audience Network

### 1. Create Your Audience Network Placements

1. Sign in to [Meta Audience Network](https://www.facebook.com/audiencenetwork) via your Facebook Business account.
2. Add your app under **Apps** and create one **Placement** per ad unit you want to show one for your banner, one for your fullscreen/interstitial, one for your rewarded video (placement IDs differ by ad format, e.g. an image-format ID can't be used for a rewarded video slot).
3. Copy your **Facebook App ID** and the **Placement ID** for each placement you created.

### 2. Enable the Add-on in WebToNative

1. Go to your **WebToNative dashboard** → **Add-ons** → **Meta Ads** and enable it.
2. Enter your **Facebook App ID** (Android also requires a **Client Token**, iOS also requires a **Client Token** both are found on the same Meta for Developers app dashboard as your App ID). WebToNative wires these into the platform-specific native config (`facebook_app_id` on Android, `FacebookAppID`/`FacebookClientToken` in `Info.plist` on iOS) for you — you don't set these from JavaScript.

{% hint style="warning" %}
**Android also requires at least one Placement Rule saved in the dashboard add-on, even if you only intend to trigger ads manually from JavaScript.** If the add-on is disabled, or enabled with zero placement rules configured, every `WTN.MetaAds.*` call below silently does nothing no callback fires at all. Enter at least one placement (it doesn't have to be the one you actually trigger manually) to make the bridge available. iOS does not have this extra requirement enabling the add-on and setting the App ID/Client Token is enough.
{% endhint %}

***

## JavaScript API Reference

### showMetaBannerAd

Displays a banner ad docked to the top or bottom of the screen.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.MetaAds.showMetaBannerAd({
  placementId: "YOUR_BANNER_PLACEMENT_ID",
  position: "BOTTOM",
  callback: function (response) {
    console.log(response);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { showMetaBannerAd } from "webtonative/MetaAds";

showMetaBannerAd({
  placementId: "YOUR_BANNER_PLACEMENT_ID",
  position: "BOTTOM",
  callback: (response) => {
    console.log(response);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key           | Type       | Required | Description                                                                                                |
| ------------- | ---------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| `placementId` | `String`   | Yes      | The Audience Network Placement ID for this banner, from the Meta Audience Network dashboard.               |
| `position`    | `String`   | No       | Where to dock the banner `"TOP"` or `"BOTTOM"`. Defaults to `"BOTTOM"`.                                    |
| `callback`    | `Function` | No       | Function invoked with every ad lifecycle event. See [Callback Response Format](#callback-response-format). |

{% hint style="warning" %}
**`position` is case-sensitive and only accepts uppercase `"TOP"` / `"BOTTOM"`.** Passing `"top"` or `"bottom"` in lowercase does not match on either platform and silently falls back to the bottom position.
{% endhint %}

***

### showMetaFullscreenAd

Displays a fullscreen interstitial ad over your app.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.MetaAds.showMetaFullscreenAd({
  placementId: "YOUR_INTERSTITIAL_PLACEMENT_ID",
  callback: function (response) {
    console.log(response);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { showMetaFullscreenAd } from "webtonative/MetaAds";

showMetaFullscreenAd({
  placementId: "YOUR_INTERSTITIAL_PLACEMENT_ID",
  callback: (response) => {
    console.log(response);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key           | Type       | Required | Description                                                                                                                 |
| ------------- | ---------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `placementId` | `String`   | Yes      | The Audience Network Placement ID for this interstitial (must be a video- or image-format placement created for this slot). |
| `callback`    | `Function` | No       | Function invoked with every ad lifecycle event. See [Callback Response Format](#callback-response-format).                  |

***

### showMetaRewardedAd

Displays a rewarded video ad. The user watches the full video in exchange for an in-app reward you grant yourself, the SDK only tells you the video was **completed**, it does not grant anything on your behalf.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.MetaAds.showMetaRewardedAd({
  placementId: "YOUR_REWARDED_PLACEMENT_ID",
  callback: function (response) {
    if (response.status === "onAdCompleted") {
      console.log("Grant the reward now");
    }
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { showMetaRewardedAd } from "webtonative/MetaAds";

showMetaRewardedAd({
  placementId: "YOUR_REWARDED_PLACEMENT_ID",
  callback: (response) => {
    if (response.status === "onAdCompleted") {
      console.log("Grant the reward now");
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key           | Type       | Required | Description                                                                                                |
| ------------- | ---------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| `placementId` | `String`   | Yes      | The Audience Network Placement ID for this rewarded video slot.                                            |
| `callback`    | `Function` | No       | Function invoked with every ad lifecycle event. See [Callback Response Format](#callback-response-format). |

{% hint style="info" %}
**Only `onAdCompleted` means the user watched the whole video.** If the user closes the ad early, you get `onAdClosed` instead, with no `onAdCompleted` event, check specifically for `onAdCompleted` before granting a reward, don't grant on `onAdClosed`.
{% endhint %}

***

### setMetaAdsTestMode

Toggles Meta Audience Network's test mode, so ad requests return Meta's shared test creatives instead of live ads, useful for development and app-review builds without registering individual device hashes.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.MetaAds.setMetaAdsTestMode({
  state: "TRUE",
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { setMetaAdsTestMode } from "webtonative/MetaAds";

setMetaAdsTestMode({
  state: "TRUE",
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key     | Type     | Required | Description                                                     |
| ------- | -------- | -------- | --------------------------------------------------------------- |
| `state` | `String` | Yes      | `"TRUE"` to request test ads, `"FALSE"` to go back to live ads. |

There is no `callback` for this function and no response is emitted.

{% hint style="danger" %}
**Android only.** `setMetaAdsTestMode` calls Meta's `AdSettings.setTestMode()` on Android, which affects every ad request made afterwards for the rest of the session. On **iOS this call currently has no effect at all** it's silently ignored. iOS instead auto-enables Meta's test device mode on its own, but only inside Debug builds, and this is not controllable from JavaScript. Don't rely on this function to turn test ads on/off on iOS.
{% endhint %}

***

## Callback Response Format

All callbacks receive a JSON object. The `type` field always matches the function name that triggered the callback, so you can safely share one callback across multiple calls and branch on `type`.

### Success Response

```json
{
  "status": "onAdLoaded",
  "type": "showMetaBannerAd"
}
```

```json
{
  "status": "onAdDisplayed",
  "type": "showMetaFullscreenAd"
}
```

### Error Response

```json
{
  "status": "adError",
  "type": "showMetaFullscreenAd",
  "error": {
    "message": "No fill available for this placement",
    "code": 1001
  }
}
```

`error.message` and `error.code` are passed straight through from Meta's own Audience Network SDK, so the exact set of codes/messages you may see is defined by Meta, not WebToNative, see [Meta's Audience Network error codes](https://developers.facebook.com/docs/audience-network/reference/error-codes) for the full list.

### All Possible `status` Values

| Status          | Fires for                    | Description                                                                                                    |
| --------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `onAdLoaded`    | Banner, Fullscreen, Rewarded | Ad has been successfully loaded and is ready.                                                                  |
| `onAdDisplayed` | Fullscreen                   | Ad is now visible on screen. Banners do not emit this treat `onAdLoaded` as "visible" for banners.             |
| `onAdClicked`   | Banner, Fullscreen, Rewarded | User tapped/clicked the ad.                                                                                    |
| `onAdDismissed` | Fullscreen                   | User closed the interstitial.                                                                                  |
| `onAdClosed`    | Rewarded                     | User closed the rewarded video (with or without finishing it, check `onAdCompleted` separately to know which). |
| `onAdCompleted` | Rewarded                     | User watched the full rewarded video. This is the only signal you should grant a reward on.                    |
| `adError`       | Banner, Fullscreen, Rewarded | Ad failed to load or display see `error` object.                                                               |

### All Possible `type` Values

| Type                   | Triggered By             |
| ---------------------- | ------------------------ |
| `showMetaBannerAd`     | `showMetaBannerAd()`     |
| `showMetaFullscreenAd` | `showMetaFullscreenAd()` |
| `showMetaRewardedAd`   | `showMetaRewardedAd()`   |

***

## Implementation Checklist

### Meta Audience Network

* [ ] App added and one Placement created per ad format you plan to use (banner / interstitial / rewarded)
* [ ] Facebook App ID and Client Token copied

### WebToNative Dashboard

* [ ] Meta Ads add-on enabled
* [ ] Facebook App ID (and Client Token) entered
* [ ] *(Android only)* At least one Placement Rule saved, even if ads are only triggered manually from JavaScript

### Your Website

* [ ] Imported the [WebToNative JavaScript bridge](https://docs.webtonative.com/javascript-apis/getting-started)
* [ ] Called the relevant `showMeta*Ad()` function with the correct `placementId` for that ad format
* [ ] Callback checks `response.type` before branching, since one callback can receive events from multiple ad calls
* [ ] Rewarded flow grants the reward only on `status === "onAdCompleted"`, never on `onAdClosed`
* [ ] Handled `adError` (e.g. retry, or just skip showing the ad) instead of assuming every call succeeds

***

## Frequently Asked Questions

<details>

<summary>Why isn't my banner/interstitial/rewarded ad showing at all no callback fires?</summary>

On Android, the Meta Ads add-on must be enabled **and** have at least one Placement Rule saved in the WebToNative dashboard, or the native bridge for all four functions is never initialized every call silently no-ops with no callback. Add at least one placement in the dashboard, then retry.

</details>

<details>

<summary>Why does my banner never fire `onAdDisplayed`?</summary>

Banners only emit `onAdLoaded`, `onAdClicked`, and `adError` on both platforms there's no separate "displayed" event for banners in the underlying Audience Network SDK. Treat `onAdLoaded` as your signal that the banner is now visible.

</details>

<details>

<summary>Why does `position: "top"` show my banner at the bottom?</summary>

`position` is matched as an exact, case-sensitive string against `"TOP"` on both platforms. Any other casing (including `"top"` or `"Top"`) doesn't match and falls back to the bottom position. Always send uppercase `"TOP"` or `"BOTTOM"`.

</details>

<details>

<summary>Does `setMetaAdsTestMode` work on iOS?</summary>

No. It's fully functional on Android but currently has no effect on iOS the call is silently ignored there. On iOS, test ads are only enabled automatically in Debug builds, independent of this function.

</details>

<details>

<summary>How do I know when to grant the reward for a rewarded ad?</summary>

Only on `status === "onAdCompleted"`. If the user closes the ad before it finishes, you get `onAdClosed` instead, with no `onAdCompleted` event don't grant a reward for that case.

</details>

<details>

<summary>What do the numeric `error.code` values mean?</summary>

They're Meta Audience Network's own error codes, passed straight through unchanged (e.g. `1001` = no fill). See [Meta's Audience Network error code reference](https://developers.facebook.com/docs/audience-network/reference/error-codes) for the full, authoritative list.

</details>

***

## Official References

* [Meta Audience Network](https://www.facebook.com/audiencenetwork)
* [Audience Network error codes](https://developers.facebook.com/docs/audience-network/reference/error-codes)


# Bottom Navigation API

Control your app’s bottom navigation using the WebToNative JavaScript API. Show, hide, and customize navigation for Android and iOS apps.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

```
WTN.bottomNavigation.hide()
```

{% endtab %}

{% tab title="npm" %}

```
import { hide as hideBottomNavigation } from "webtonative/BottomNavigation"

hideBottomNavigation()
```

{% endtab %}
{% endtabs %}

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# Clipboard JavaScript API

Save and Retrieve clipboard content using the WebToNative JavaScript API. Enable seamless clipboard access in Android and iOS apps.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

{% tabs %}
{% tab title="Plain JS" %}

```
const { clipboard } = window.WTN
 
clipboard.get({
  callback:function(data){
   console.log(data.value)
  }
 })
 
 
clipboard.set({
 data:'CONTENT TO PUT IN CLIPBOARD'
})
 
```

{% endtab %}

{% tab title="npm" %}

```
import { get, set } from "webtonative/Clipboard"
 
get({
  callback:function(data){
   console.log(data.value)
  }
 })
 
 
set({
 data:'CONTENT TO PUT IN CLIPBOARD'
})

```

{% endtab %}
{% endtabs %}

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# Screen Control API

Manage screen behavior using the WebToNative JavaScript API. Control screen orientation, brightness, and wake settings for your mobile app.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

**To Keep device screen on all the time.**

{% tabs %}
{% tab title="Plain JS" %}

```
const { screen } = window.WTN

//to keep device screen on all the time
screen.keepScreenOn()

//to revert back to normal screen behaviour
screen.keepScreenNormal()

 
```

{% endtab %}

{% tab title="npm" %}

```
import { keepScreenOn, keepScreenNormal } from "webtonative/Screen"

//to keep device screen on all the time
keepScreenOn()

//to revert back to normal screen behaviour
keepScreenNormal()

 
```

{% endtab %}
{% endtabs %}

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# Background Location API

Track device location in the background using the WebToNative JavaScript API. Enable continuous location updates for Android and iOS apps.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

**To retrieve device location even when app is in background state**

{% tabs %}
{% tab title="Plain JS" %}

<pre><code>//to start recieving location updates
<strong>window.WTN.backgroundLocation.start({
</strong> apiUrl,
 timeout,
 data,
 backgroundIndicator : false,
 pauseAutomatically : true,
 distanceFilter : 0.0,
 desiredAccuracy : "best",
 activityType : "other",
})

//to stop recieving location updates
<strong>window.WTN.backgroundLocation.stop()
</strong> 
 
</code></pre>

{% endtab %}

{% tab title="ES 6+" %}

```
import { start, stop } from "webtonative/BackgroundLocation"

//to start recieving location updates
start({
 apiUrl,
 timeout,
 data,
 backgroundIndicator : false,
 pauseAutomatically : true,
 distanceFilter : 0.0,
 desiredAccuracy : "best",
 activityType : "other",
})

//to stop recieving location updates
stop()
```

{% endtab %}
{% endtabs %}

### Call the method with the following data

**apiUrl** (Mandatory): The API endpoint that will be called using the POST method to send location data.

* **Value Type**: **String**
* **Example**: `https://backgrounlocation.free.beeceptor.com/`

**timeout**: The time interval between API calls, measured in milliseconds.

* **Value Type**: Number
* **Example**: `1000 milliseconds = 1 second`

**data** (Optional)**:** Extra data to send along with the location update. Useful for identifying users or adding metadata. Can also use query parameters.

* **Value Type**: JSON object
* **Example**: `{ "keyName": "value" }`

**backgroundIndicator** (Optional, iOS Only)**:** Modifies the iOS status bar to indicate that the app is using location services when in the background.

* **Value Type**: Boolean
* **Default Value**: `false`

**pauseAutomatically** (Optional, iOS Only): When set to "true," The location manager pauses updates (and powers down hardware) when location data is unlikely to change, improving battery life.

* **Value Type**: Boolean
* **Default Value**: `true`

**distanceFilter**: The minimum horizontal distance (in meters) a device must move before an update is generated.

* **Value Type**: Double
* **Default Value**: `0.0` meters
* **Example**: If set to `10.0`, data is logged only after the device moves 10 meters.

**desiredAccuracy** (Optional, iOS Only)**:** The desired accuracy of location data. Higher accuracy increases battery consumption.

* **Value Type**: String
* **Default Value**: `best`
* **Possible Values**:
  * `best` : The best accuracy possible (default).
  * `bestForNavigation`: Highest accuracy using additional sensor data for navigation apps.
  * `tenMeters`: Accurate to within 10 meters.
  * `hundredMeters`: Accurate to within 100 meters.
  * `kilometer`: Accurate to the nearest kilometer.
  * `threekilometers`: Accurate to the nearest 3 kilometers.

**activityType** (Optional, iOS Only): The user activity associated with location updates.

* **Value Type**: String
* **Default Value**: `other`
* **Possible Values**:
  * `other`: Unknown activity (default).
  * `automotiveNavigation`: Vehicular navigation for automobiles.
  * `otherNavigation`: Non-automobile vehicular navigation.
  * `fitness`: Tracking fitness activities (e.g., walking, running, cycling).
  * `airborne`: Tracking airborne activities.

### Notes

* **Foreground Behavior**: Location updates are sent to the `apiUrl`.
* **Background Behavior**: Location updates are sent only to `apiUrl` when the app is in the background.

### Location Object

The location data sent to your API or callback function includes the following fields:

* `latitude`: Latitude in degrees.
* `longitude`: Longitude in degrees.
* `altitude`: Altitude in meters.
* `timestamp`: Time of the location update (milliseconds since Jan 1, 1970).
* `data`: The extra data provided in the `start` method.
* `floor` (iOS Only): The logical floor of the building.
* `deviceID`: Device identifier.
* `playerId`: OneSignal Player ID (if OneSignal is enabled).
* `speed`: Instantaneous speed of the device (meters per second).
* `direction` (iOS Only): Device pointing direction (e.g., N, NE, E, SE, S, SW, W, NW, or none).
* `horizontalAccuracy` (iOS Only): Radius of uncertainty for the location (in meters).
* `verticalAccuracy`: Validity and estimated uncertainty of altitude values (in meters).

***

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# Native Contacts API

Access and manage device contacts using the WebToNative JavaScript API. Read, create, and update contacts in Android and iOS apps.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

**To Retrive All contacts of device.**

{% tabs %}
{% tab title="Plain JS" %}

```
const { contacts } = window.WTN

contacts.getPermissionStatus({
  callback: function(data){
   //data.status contains permission status
  }
 })
 

contacts.getAll({
 callback: function(data){
  //data.contacts contains all contact   
 }
})
```

{% endtab %}

{% tab title="npm" %}

```
import { getPermissionStatus, getAll } from "webtonative/NativeContacts"

getPermissionStatus({
  callback: function(data){
   //data.status contains permission status
  }
 })
 

getAll({
 callback: function(data){
  //data.contacts contains all contact   
 }
})
```

{% endtab %}
{% endtabs %}

**the possible status are**

* **authorized**: Permission has been granted
* **denied**: Permission has been denied
* **restricted**: access has been administratively prohibited
* **notDetermined**: User has not yet been asked for permission

**contacts will be an array of contacts with following keys**

* givenName
* familyName
* middleName
* birthday
* namePrefix
* previousFamilyName
* nameSuffix
* nickname
* organizationName
* departmentName
* jobTitle
* phoneNumbers\* (Array of Objects Described Below)
  * label
  * phoneNumber
* emailAddresses\* (Array of Objects Described Below)
  * label
  * emailAddress
* postalAddresses\* (Array of Objects Described Below)
  * label
  * street
  * subLocality
  * city
  * subAdministrativeArea
  * state
  * postalCode
  * country
  * isoCountryCode

Feature added in Android on 15/03/24

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# iOS App Tracking Transparency API

Implement iOS App Tracking Transparency using the WebToNative JavaScript API. Request user permission for tracking and comply with Apple guidelines.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

For iOS 14.5+, Apps must take Tracking Consent from user if your app collects data about end users and shares it with other companies for purposes of tracking across apps and web sites.

To use AppTrackingTransparency you will need to enable Request Tracking Authorization from App permission tab.

<figure><img src="/files/mI2J1xcd1eMV7SY2qfiZ" alt=""><figcaption><p>App Tracking Transparency Setting</p></figcaption></figure>

There are two ways apps can prompt user for permission

* **On App load**: if Request Tracking consent on load is enabled then app will prompt user for Tracking consent on app launch
* **Manually**: If Request Tracking consent on load is disabled, you can call below javascript interface manually to ask for Tracking consent

{% tabs %}
{% tab title="Plain JS" %}

```markup
const { ATTConsent } = window.WTN

ATTConsent.request({
 callback:function(result){
   if(result.granted){
     //Permission Granted or ios version 14.4 or lower
   }else{
     //Permission Denied / not Determined due to some restrictions
   }
 }
})

```

{% endtab %}

{% tab title="npm" %}

```
import { request } from "webtonative/ATTConsent"

request({
 callback:function(result){
   if(result.granted){
     //Permission Granted or ios version 14.4 or lower
   }else{
     //Permission Denied / not Determined due to some restrictions
   }
 }
})

```

{% endtab %}
{% endtabs %}

To check whether Tracking consent was given (either through the manual prompt or automatic prompt), you can call following method:

{% tabs %}
{% tab title="Plain JS" %}

```markup
const { ATTConsent } = window.WTN

ATTConsent.status({
 callback:function(result){
   if(result.granted){
     //Permission Granted or ios version 14.4 or lower
   }else{
     //Permission Denied / not Determined due to some restrictions / not asked
   }
 }
})

```

{% endtab %}

{% tab title="npm" %}

```
import { status } from "webtonative/ATTConsent"

status({
 callback:function(result){
   if(result.granted){
     //Permission Granted or ios version 14.4 or lower
   }else{
     //Permission Denied / not Determined due to some restrictions / not asked
   }
 }
})

```

{% endtab %}
{% endtabs %}

**Note :**&#x20;

* The result object will be { granted: true|false } based on the user's response to the consent prompt.
* For iOS 14.4 and lower, result object will always be { granted: true}

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# Google Firebase Analytics API

Integrate Firebase Analytics using the WebToNative JavaScript API. Track user behavior, app events, and performance on Android and iOS.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

1\) To enable/disable Analytics data collection

{% tabs %}
{% tab title="Plain JS" %}

```markup
const { Analytics: FirebaseAnalytics } = window.WTN.Firebase

FirebaseAnalytics.setCollection({
    enabled: true/false
})

```

{% endtab %}

{% tab title="npm" %}

```
import { setCollection } from "webtonative/Firebase/Analytics"

setCollection({
    enabled: true/false
})

```

{% endtab %}
{% endtabs %}

2\) To identify user

{% tabs %}
{% tab title="Plain JS" %}

```markup
const { Analytics: FirebaseAnalytics } = window.WTN.Firebase

FirebaseAnalytics.setUserId({
    userId: "customuserId"
})

```

{% endtab %}

{% tab title="npm" %}

```
import { setUserId } from "webtonative/Firebase/Analytics"

setUserId({
    userId: "customuserId"
})

```

{% endtab %}
{% endtabs %}

3\) To set user properties like name, gender etc

{% tabs %}
{% tab title="Plain JS" %}

```markup
const { Analytics: FirebaseAnalytics } = window.WTN.Firebase

FirebaseAnalytics.setUserProperty({
    key: 'name',
    value:'Webtonative'
})

```

{% endtab %}

{% tab title="npm" %}

```
import { setUserProperty } from "webtonative/Firebase/Analytics"

setUserProperty({
    key: 'name',
    value:'Webtonative'
})

```

{% endtab %}
{% endtabs %}

4\) To set default parameter which will be passed along with all future events

{% tabs %}
{% tab title="Plain JS" %}

```markup
const { Analytics: FirebaseAnalytics } = window.WTN.Firebase

FirebaseAnalytics.setDefaultEventParameters({
    parameters: {
        "level_name": "Caverns01",
        "level_difficulty": 4
    }
})

```

{% endtab %}

{% tab title="npm" %}

```
import { setDefaultEventParameters } from "webtonative/Firebase/Analytics"

setDefaultEventParameters({
    parameters: {
        "level_name": "Caverns01",
        "level_difficulty": 4
    }
})

```

{% endtab %}
{% endtabs %}

5\) To Log events

{% tabs %}
{% tab title="Plain JS" %}

```markup
const { Analytics: FirebaseAnalytics } = window.WTN.Firebase

FirebaseAnalytics.logEvent({
    eventName:'Your event name',
    parameters:{
        "level_name": "Caverns01",
        "level_difficulty": 4
    }
})

```

{% endtab %}

{% tab title="npm" %}

```
import { logEvent } from "webtonative/Firebase/Analytics"

logEvent({
    eventName:'Your event name',
    parameters:{
        "level_name": "Caverns01",
        "level_difficulty": 4
    }
})

```

{% endtab %}
{% endtabs %}

6\) To Track screen view in your website

{% tabs %}
{% tab title="Plain JS" %}

```markup
const { Analytics: FirebaseAnalytics } = window.WTN.Firebase

FirebaseAnalytics.logScreen({
    screenName:"Screen Name",
    screenClass:"Screen Class"
})

```

{% endtab %}

{% tab title="npm" %}

```
import { logScreen } from "webtonative/Firebase/Analytics"

logScreen({
    screenName:"Screen Name",
    screenClass:"Screen Class"
})

```

{% endtab %}
{% endtabs %}

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# Haptic Feedback API

Add haptic feedback using the WebToNative JavaScript API. Trigger vibration responses to enhance user interactions on Android and iOS apps.

Functions to trigger haptic feedback effects on the device and to check whether haptic feedback is supported. Haptic effects use predefined vibration patterns that help users recognize the significance of different interactions.

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

## Trigger

Triggers a haptic feedback effect on the device. If `effect` is omitted or an invalid value is provided, a default effect is applied. Optionally, pass `soundName` to also play a custom sound (uploaded via the Dashboard) at the same time — see [Sound.play](broken://pages/1a01c70be0efcb505936031d69093822409806c8) for the full rules for this parameter, and [OS Notification Sound](broken://pages/0d6b437cba5fe0fc40f6d8d4a6a6e8663171aa74) for how to upload a sound file.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Haptics.trigger({
  effect: "impactMedium",
  soundName: "your_sound_name", // optional — see Sound.play
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { trigger } from "webtonative/Haptics";

trigger({
  effect: "impactMedium",
  soundName: "your_sound_name", // optional — see Sound.play
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key         | Type     | Required | Description                                                                                                                                                                                                                   |
| ----------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `effect`    | `String` | No       | The vibration pattern to play. See the supported values below.                                                                                                                                                                |
| `soundName` | `String` | No       | Name of an uploaded sound file to play alongside the haptic effect, using the exact same lookup rules as [`Sound.play`](broken://pages/1a01c70be0efcb505936031d69093822409806c8) — omit it to trigger the haptic effect only. |

**Supported `effect` values:**

| Value                 | Description                                             |
| --------------------- | ------------------------------------------------------- |
| `impactLight`         | A light impact, suitable for small UI interactions.     |
| `impactMedium`        | A medium impact, suitable for standard UI interactions. |
| `impactHeavy`         | A heavy impact, suitable for prominent UI interactions. |
| `notificationSuccess` | Indicates a successful action or outcome.               |
| `notificationWarning` | Indicates a warning or cautionary state.                |
| `notificationError`   | Indicates an error or failed action.                    |

***

## Is Haptic Supported

Checks whether the device supports haptic feedback.

> This function is currently only available on **Android**. On iOS the callback will not be invoked.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Haptics.isHapticSupported({
  callback: function (response) {
    console.log(response.isSupported);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { isHapticSupported } from "webtonative/Haptics";

isHapticSupported({
  callback: (response) => {
    console.log(response.isSupported);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                  |
| ---------- | ---------- | -------- | -------------------------------------------- |
| `callback` | `Function` | No       | Callback function invoked with the response. |

**Callback Response:**

| Key           | Type      | Description                                                       |
| ------------- | --------- | ----------------------------------------------------------------- |
| `type`        | `String`  | Always `"isHapticSupported"`.                                     |
| `isSupported` | `Boolean` | `true` if the device supports haptic feedback, `false` otherwise. |


# Haptic Feedback API

Add haptic feedback using the WebToNative JavaScript API. Trigger vibration responses to enhance user interactions on Android and iOS apps.

Functions to trigger haptic feedback effects on the device and to check whether haptic feedback is supported. Haptic effects use predefined vibration patterns that help users recognize the significance of different interactions.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

## Trigger

Triggers a haptic feedback effect on the device. If `effect` is omitted or an invalid value is provided, a default effect is applied.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Haptics.trigger({
  effect: "impactMedium",
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { trigger } from "webtonative/Haptics";

trigger({
  effect: "impactMedium",
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key      | Type     | Required | Description                                                    |
| -------- | -------- | -------- | -------------------------------------------------------------- |
| `effect` | `String` | No       | The vibration pattern to play. See the supported values below. |

**Supported `effect` values:**

| Value                 | Description                                             |
| --------------------- | ------------------------------------------------------- |
| `impactLight`         | A light impact, suitable for small UI interactions.     |
| `impactMedium`        | A medium impact, suitable for standard UI interactions. |
| `impactHeavy`         | A heavy impact, suitable for prominent UI interactions. |
| `notificationSuccess` | Indicates a successful action or outcome.               |
| `notificationWarning` | Indicates a warning or cautionary state.                |
| `notificationError`   | Indicates an error or failed action.                    |

***

## Is Haptic Supported

Checks whether the device supports haptic feedback.

{% hint style="info" %}
This function is currently only available on **Android**. On iOS the callback will not be invoked.
{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Haptics.isHapticSupported({
  callback: function (response) {
    console.log(response.isSupported);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { isHapticSupported } from "webtonative/Haptics";

isHapticSupported({
  callback: (response) => {
    console.log(response.isSupported);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                  |
| ---------- | ---------- | -------- | -------------------------------------------- |
| `callback` | `Function` | No       | Callback function invoked with the response. |

**Callback Response:**

| Key           | Type      | Description                                                       |
| ------------- | --------- | ----------------------------------------------------------------- |
| `type`        | `String`  | Always `"isHapticSupported"`.                                     |
| `isSupported` | `Boolean` | `true` if the device supports haptic feedback, `false` otherwise. |


# Haptic Feedback API

Add haptic feedback using the WebToNative JavaScript API. Trigger vibration responses to enhance user interactions on Android and iOS apps.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

This module enables you to trigger Haptics Feedback using predefined vibration patterns shared by all apps, thus helping users understand that various types of feedback carry special significance

{% tabs %}
{% tab title="Plain JS" %}

```markup
const { haptics } = window.WTN

haptics.trigger({
    effect: 'Haptic Effect Type Described Below'
})

```

{% endtab %}

{% tab title="ES 6+" %}

```
import { trigger } from "webtonative/Haptics"

trigger({
    effect: 'Haptic Effect Type Described Below'
})

```

{% endtab %}
{% endtabs %}

Different Effects Are as followed

* impactLight
* impactMedium
* impactHeavy
* notificationSuccess
* notificationWarning
* notificationError

**Note**: effect argument is optional. if you don't provide anything in effect/wrong value is provided then default effect will be generated

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# Google Firebase Notification API

Integrate Firebase Cloud Messaging using the WebToNative JavaScript API. Send and manage push notifications for Android and iOS apps.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

1\) To Retrive FCM Token

{% tabs %}
{% tab title="Plain JS" %}

```markup
const { Messaging: FirebaseMessaging } = window.WTN.Firebase

FirebaseMessaging.getFCMToken({
    callback:function(data){
        //data.token contains fcm token
        //store it in your backend to send notification
    }
})

```

{% endtab %}

{% tab title="npm" %}

```
import { getFCMToken } from "webtonative/Firebase/Messaging"

getFCMToken({
    callback:function(data){
        //data.token contains fcm token
        //store it in your backend to send notification
    }
})

```

{% endtab %}
{% endtabs %}

2\) To Subscribe to particular topic

{% tabs %}
{% tab title="Plain JS" %}

```markup
const { Messaging: FirebaseMessaging } = window.WTN.Firebase

FirebaseMessaging.subscribe({
    toTopic: "Your Topic Name"
})

```

{% endtab %}

{% tab title="npm" %}

```
import { subscribe } from "webtonative/Firebase/Messaging"

subscribe({
    toTopic: "Your Topic Name"
})

```

{% endtab %}
{% endtabs %}

3\) To Unsubscribe from particular topic

{% tabs %}
{% tab title="Plain JS" %}

```markup
const { Messaging: FirebaseMessaging } = window.WTN.Firebase

FirebaseMessaging.unsubscribe({
    fromTopic: "Your Topic Name"
})

```

{% endtab %}

{% tab title="npm" %}

```
import { unsubscribe } from "webtonative/Firebase/Messaging"

unsubscribe({
    fromTopic: "Your Topic Name"
})

```

{% endtab %}
{% endtabs %}

> In order to specify the desired URL that will be accessed upon clicking the notification, it is necessary to pass the corresponding URL through the "**deepLink**" key.

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# Apps Flyer API

Integrate AppsFlyer analytics in your WebToNative app to track custom user IDs, log events, and retrieve the AppsFlyer device identifier.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](/javascript-apis/getting-started).
{% endhint %}

{% stepper %}
{% step %}

### setCustomerUserId(userId)

To Set Custom User Id

{% tabs %}
{% tab title="Plain JS" %}

```javascript
const { appsflyer: AppsFlyer } = window.WTN

AppsFlyer.setCustomerUserId("CUSTOM_USER_ID")
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { setCustomerUserId } from "webtonative/AppsFlyer"

setCustomerUserId("CUSTOM_USER_ID")
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### logEvent(eventName, eventValues)

To Push Event

{% tabs %}
{% tab title="Plain JS" %}

```javascript
const { appsflyer: AppsFlyer } = window.WTN

AppsFlyer.logEvent("ADD_TO_CART", {
    name: "Cadburry",
    quantity: 1
})
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { logEvent } from "webtonative/AppsFlyer"

logEvent("ADD_TO_CART", {
    name: "Cadburry",
    quantity: 1
})
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### getAppsFlyerAppId()

To retrieve the AppsFlyer unique device identifier (AppsFlyer ID) for the current app installation.

{% hint style="success" %}
**Available on Android and iOS from 21st March, 2026.**
{% endhint %}

{% tabs %}
{% tab title="Plain JS" %}

```javascript
const { appsflyer: AppsFlyer } = window.WTN

AppsFlyer.getAppsFlyerAppId({
    callback: function (response) {
        console.log(response.appsFlyerId) // AppsFlyer unique device ID
    }
})
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { getAppsFlyerAppId } from "webtonative/AppsFlyer"

getAppsFlyerAppId({
    callback: (response) => {
        console.log(response.appsFlyerId) // AppsFlyer unique device ID
    }
})
```

{% endtab %}
{% endtabs %}

#### Callback Response

| Property      | Type   | Description                            |
| ------------- | ------ | -------------------------------------- |
| `type`        | string | Always `"getAppsFlyerAppId"`           |
| `appsFlyerId` | string | The AppsFlyer unique device identifier |

#### Example Response

```json
{
    "type": "getAppsFlyerAppId",
    "appsFlyerId": "1234567890123-1234567"
}
```

{% endstep %}
{% endstepper %}


# App Review API

Request in-app reviews using the WebToNative JavaScript API. Prompt users to rate your Android and iOS app seamlessly.

{% hint style="info" %}

<pre data-overflow="wrap"><code>You'll need to import the javascript file in your website before starting from this <a data-footnote-ref href="#user-content-fn-1">link</a>.
</code></pre>

{% endhint %}

The App Review Add-on enables you to prompt your users to rate and review your app on respective app store listing page.

<div align="left"><figure><img src="/files/WrkUZDFCYWmeExaha2Ib" alt=""><figcaption><p>Apple App Store App Review</p></figcaption></figure> <figure><img src="/files/bqe0d3upp9XCqrVUMfFn" alt=""><figcaption></figcaption></figure></div>

To show Native App Review

{% tabs %}
{% tab title="Plain JS" %}

```markup
const { appReview: AppReview } = window.WTN

AppReview.prompt()

```

{% endtab %}

{% tab title="npm" %}

```
import { prompt } from "webtonative/AppReview"

prompt()

```

{% endtab %}
{% endtabs %}

## Notes on prompting for a review

The Apple App Store controls the actual display of this alert so it may not show for some cases. This is to prevent frequent rating prompts which can be irritating for users. See more details in [Apple's Documentation](https://developer.apple.com/documentation/storekit/requesting_app_store_reviews)

[^1]: <https://docs.webtonative.com/javascript-apis/getting-started>


# iOS Calendar API

Access and manage iOS calendar events using the WebToNative JavaScript API. Create, update, and read calendar entries with ease.

The Calendar add-on allows adding events to the user's calendar on iOS. The add-on provides a built-in UI which displays the event details and a button for the user to add the event. Events can be added via an .ics file on your website or an embedded  ics calendar invitation.

This add-on can automatically detect ics files hence no custom javascript code is required to use this add-on.

ics calendar invitation can be hosted on your website or alternatively embedded in html&#x20;

```
<a href="data:text/calendar;charset=utf-8,BEGIN:VCALENDAR%0AVERSION:2.0%0ABEGIN:VEVENT%0AURL:https://www.webtonative.com/%0ADTSTART:20251210T120000%0ADTEND:20251210T122500%0ASUMMARY:Webtonative%20Event%0ADESCRIPTION:Webtonative%20Party%0AEND:VEVENT%0AEND:VCALENDAR">
    Add Event
</a>
```


# Android Calendar API

Access and manage Android calendar events using the WebToNative JavaScript API. Create, update, and read calendar entries seamlessly.

Native calendar with language support and an option to take time also from the user.

Can be configured to only take time input from the user.

{% hint style="info" %}
You'll need to import the JavaScript file into your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

{% tabs %}
{% tab title="Plain JS" %}

```html
window.WTN.showDateTimePicker({
    showDate:true,
    showTime:true,
    callback:function(data){
        console.log("Date Data ->",JSON.stringify(data));
    }
});
```

{% endtab %}

{% tab title="npm" %}

```
import { showDateTimePicker } from "webtonative"

showDateTimePicker({
    showDate:true,
    showTime:true,
    callback:function(data){
        console.log("Date Data ->",JSON.stringify(data));
    }
});
```

{% endtab %}
{% endtabs %}

{% code overflow="wrap" %}

```javascript
Data returned - 

{"success":true,"type":"DATE_TIME_PICKER","date":"2024-7-7","time":"18:30","timestamp":"1723035604669"}

To capture only date - set showDate:true and showTime:false
To capture only time - set showDate:false and showTime:true
To capture both - set showDate:true and showTime:true
```

{% endcode %}

<div align="left"><figure><img src="/files/xI0pNlAbBIZLVZK39YkD" alt=""><figcaption><p>Date view</p></figcaption></figure> <figure><img src="/files/SsZAsv3F9bI49VisoAvW" alt=""><figcaption><p>Time view</p></figcaption></figure></div>


# Biometric Authentication API

Enable fingerprint and Face ID authentication using the WebToNative JavaScript API. Add secure biometric login for Android and iOS apps.

{% hint style="info" %}
You'll need to import the JavaScript file into your website before starting from this [link](/javascript-apis/getting-started).
{% endhint %}

This module explains the ways to use the Biometric authentication functions, where you can use the device's Touch/Face ID to unlock an app or use it to secure pages.

If you choose to handle biometrics yourself, the following JS APIs will be required.

## Show Biometric option

Use the following code to show biometric in the app. If it is authenticated successfully, then the **callback** function will be called.

{% tabs %}
{% tab title="Plain JS" %}
{% code overflow="wrap" %}

```javascript
window.WTN.Biometric.show({
    prompt:"Authenticate to continue!",
    callback:function(data){
        /* data returns the object below 
        {
            isSuccess: true,
            secret: 'saved secret token'
        }
        */
        
    }
});
```

{% endcode %}
{% endtab %}

{% tab title="npm" %}

```javascript
import { show } from "webtonative/Biometric";

show({
    prompt:"Authenticate to continue!",
    callback:function(data){
         /* data returns the object below 
        {
            isSuccess: true,
            secret: 'saved secret token'
        }
        */
    }
});


```

{% endtab %}
{% endtabs %}

```
Prompt - Used to show text to user when aithentication prompt is shown.

Callback - function called on user authenticating or cancelling the promt.
```

<figure><img src="/files/8uXIbga643S1UQJ2HlXU" alt="" width="188"><figcaption><p>Biometric Auth Screen</p></figcaption></figure>

### Save secret

Use following code to save secret in the app. This secret will be returned when you show the biometric when user opens the app. Secret can be used for example to store a login token, using that token you can handle custom handling like get the user logged in.

{% tabs %}
{% tab title="Plain JS" %}

```javascript
window.WTN.Biometric.saveSecret({
    secret:"send secret token here",
    callback:function(data){
        /* data returns the object in below format
        
        {
           isSuccess: true
        }
        */
    }
});
```

{% endtab %}

{% tab title="npm" %}

<pre class="language-javascript"><code class="lang-javascript">import { saveSecret } from "webtonative/Biometric";
<strong>
</strong><strong>saveSecret({
</strong>    secret:"send secret token here",
    callback:function(data){
         /* data returns the object in below format
        
        {
           success: true
        }
        */
    }
});
</code></pre>

{% endtab %}
{% endtabs %}

### Delete Secret

Use following code to delete secret in the app.

{% tabs %}
{% tab title="Plain JS" %}

<pre class="language-javascript"><code class="lang-javascript"><strong>window.WTN.Biometric.deleteSecret({
</strong>    callback:function(data){
        /* data returns the object below 
        {
            isSuccess: true
        }
        */
    }
});
</code></pre>

{% endtab %}

{% tab title="npm" %}

```javascript
import { deleteSecret } from "webtonative/Biometric";

deleteSecret({
    callback:function(data){
       /* data returns the object below 
        {
            success: true
        }
        */
    }
});
```

{% endtab %}
{% endtabs %}

### Check Status

Check status function will return if biometric is active.

{% tabs %}
{% tab title="Plain JS" %}

<pre class="language-javascript"><code class="lang-javascript"><strong>window.WTN.Biometric.checkStatus({
</strong>    callback:function(data){
        /* data returns the object below */
        {
            isSuccess: true,
            hasTouchId: true,
            hasSecret: true/false
        }
    }
});
</code></pre>

{% endtab %}

{% tab title="npm" %}

```javascript
import { checkStatus } from "webtonative/Biometric";

checkStatus({
    callback:function(data){
        console.log("Function called",data);
    }
});
```

{% endtab %}
{% endtabs %}

**Show Biometric without closing the app on cancel click - Android**

This let's you open the prompt and clicking on cancel won't close the app

{% tabs %}
{% tab title="Plain JS" %}

```javascript
window.WTN.Biometric.biometricAuthWithDismissOnCancel({
    prompt:"Authenticate to continue!"
    isAuthenticationOptional:true/false,
    callback:function(data){
        console.log("Function called",data);
    }
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { biometricAuthWithDismissOnCancel } from "webtonative/Biometric";

biometricAuthWithDismissOnCancel({
    prompt:"Authenticate to continue!"
    isAuthenticationOptional:true/false,
    callback:function(data){
        console.log("Function called",data);
    }
});
```

{% endtab %}
{% endtabs %}

{% code overflow="wrap" %}

```
Prompt - Used to show text to user when aithentication prompt is shown.
isAuthenticationOptional (Boolean) - Passing it true will allow closing the proompt without closing thr app on cancle.
Callback - function called on user authenticating or cancelling the promt.
```

{% endcode %}

Function - biometricAuthWithDismissOnCancel - Available on Android from 30/06/25


# Media Player API

Control audio and video playback using the WebToNative JavaScript API. Play, pause, and manage media seamlessly in Android and iOS apps.

Play audio in the background with native notification controls — play, pause, and stop from the notification panel.

The Custom Media Player add-on lets your app play audio (music, podcasts, radio streams, or any media URL) in the background while showing a native media notification with playback controls. When a user starts audio through your website, the media continues playing even when the app is minimized, and the device's notification panel displays Play, Pause, and Stop buttons — just like Spotify, Apple Music, or any native audio app.

This is powered by the native media session APIs on both Android and iOS, so the controls integrate with the device's lock screen, notification shade, and any connected Bluetooth/headphone controls automatically.

## When to use this plugin

Use the Custom Media Player when your app needs to play audio that should continue in the background. Common use cases include music or podcast players, live radio or audio streaming, guided meditation or workout audio, and language learning apps with audio playback. If your audio only needs to play while the user is actively viewing the page (e.g. a sound effect or short clip), you may not need this plugin — standard HTML5 `<audio>` will work.

{% hint style="info" %}
**Prerequisites:** Import the WebToNative JavaScript bridge into your website before using any of the functions below. See the [Getting Started](https://docs.webtonative.com/javascript-apis/getting-started) guide.
{% endhint %}

## Step 1 — Enable the Add-On in WebToNative

{% stepper %}
{% step %}

### Open your **WebToNative Dashboard → Add-ons**

Open your **WebToNative Dashboard → Add-ons**.
{% endstep %}

{% step %}

### Find **Custom Media Player** and click **+Add**

Find **Custom Media Player** and click **+Add**.
{% endstep %}

{% step %}

### Select the platform

Select the platform — **Add for Android** or **Add for Android and iOS**.
{% endstep %}

{% step %}

### Enable the add-on in Settings

After adding, click **Settings** and make sure the toggle is **enabled**.
{% endstep %}

{% step %}

### Save and rebuild

Click **Save & Rebuild** to generate a new build with the media player plugin active.
{% endstep %}
{% endstepper %}

That's it for dashboard configuration — there are no additional settings to fill in. All playback control happens through JavaScript.

> For step-by-step screenshots, see the [Custom Media Player add-on guide](https://www.webtonative.com/support/addons/customplayer).

## JavaScript API Reference

The Media Player API has three functions: start playback, pause it, and stop it entirely.

### Play Media

Starts audio playback from the provided URL and shows a native media notification with playback controls. The audio continues playing in the background when the app is minimized.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.MediaPlayer.playMedia({
  url: "https://example.com/audio.mp3",
  imageUrl: "https://example.com/cover-art.jpg",
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { playMedia } from "webtonative/MediaPlayer";

playMedia({
  url: "https://example.com/audio.mp3",
  imageUrl: "https://example.com/cover-art.jpg",
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type     | Required | Description                                                                                                                 |
| ---------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `url`      | `String` | Yes      | The URL of the audio file or stream to play. Supports MP3, AAC, HLS streams, and other standard audio formats.              |
| `imageUrl` | `String` | No       | URL of an image to display in the media notification (e.g. album art, podcast cover). If omitted, a default image is shown. |

***

### Pause Media

Pauses the currently playing audio. The media notification remains visible so the user can resume playback from the notification panel.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.MediaPlayer.pauseMedia();
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { pauseMedia } from "webtonative/MediaPlayer";

pauseMedia();
```

{% endtab %}
{% endtabs %}

This function takes no parameters.

***

### Stop Media

Stops the audio playback entirely and dismisses the media notification from the notification panel.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.MediaPlayer.stopMedia();
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { stopMedia } from "webtonative/MediaPlayer";

stopMedia();
```

{% endtab %}
{% endtabs %}

This function takes no parameters.

> **Pause vs. Stop:** `pauseMedia()` keeps the notification visible and the media session alive — the user can resume from the notification. `stopMedia()` ends the session entirely and removes the notification.

## Typical Implementation Flow

Here's how a typical media player integration looks:

{% stepper %}
{% step %}

### User taps play on your website

Your web UI calls `playMedia()` with the audio URL and an optional cover image.
{% endstep %}

{% step %}

### Background playback begins

The native media notification appears with Play/Pause/Stop controls. Audio continues even when the app is minimized or the screen is locked.
{% endstep %}

{% step %}

### User pauses from notification or your UI

Call `pauseMedia()` from your web UI. The user can also tap Pause directly on the notification — the native SDK handles that automatically.
{% endstep %}

{% step %}

### User resumes

Call `playMedia()` again with the same URL to resume, or the user taps Play on the notification.
{% endstep %}

{% step %}

### User stops or leaves

Call `stopMedia()` to end playback and dismiss the notification.
{% endstep %}
{% endstepper %}

### Example: Simple Audio Player UI

This example shows a minimal implementation with play, pause, and stop buttons wired to the bridge functions:

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
var currentTrack = "https://example.com/podcast-episode-42.mp3";
var coverArt = "https://example.com/podcast-cover.jpg";

function onPlayClick() {
  window.WTN.MediaPlayer.playMedia({
    url: currentTrack,
    imageUrl: coverArt,
  });
}

function onPauseClick() {
  window.WTN.MediaPlayer.pauseMedia();
}

function onStopClick() {
  window.WTN.MediaPlayer.stopMedia();
}
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { playMedia, pauseMedia, stopMedia } from "webtonative/MediaPlayer";

const currentTrack = "https://example.com/podcast-episode-42.mp3";
const coverArt = "https://example.com/podcast-cover.jpg";

function onPlayClick() {
  playMedia({
    url: currentTrack,
    imageUrl: coverArt,
  });
}

function onPauseClick() {
  pauseMedia();
}

function onStopClick() {
  stopMedia();
}
```

{% endtab %}
{% endtabs %}

## Implementation Checklist

### WebToNative Dashboard

* [ ] Added the **Custom Media Player** add-on from the Add-ons section
* [ ] Enabled the add-on via the toggle in Settings
* [ ] Clicked **Save & Rebuild** to generate a new build

### Your Website

* [ ] Imported the [WebToNative JavaScript bridge](https://docs.webtonative.com/javascript-apis/getting-started)
* [ ] Implemented `playMedia()` with a valid audio URL when the user starts playback
* [ ] Implemented `pauseMedia()` for pause controls in your UI
* [ ] Implemented `stopMedia()` for stop controls or when the user navigates away from audio content
* [ ] Provided an `imageUrl` for a polished notification appearance (optional but recommended)
* [ ] Tested background playback — minimized the app and confirmed audio continues
* [ ] Tested notification controls — confirmed Play/Pause/Stop buttons work from the notification panel

## Frequently Asked Questions

<details>

<summary>What audio formats are supported?</summary>

The media player supports any format that the device's native audio engine can handle. This includes MP3, AAC, M4A, WAV, OGG (Android), and HLS streams. For broadest compatibility, MP3 or AAC is recommended.

</details>

<details>

<summary>Does audio continue playing when the app is minimized?</summary>

Yes. That's the primary purpose of this plugin. Once `playMedia()` is called, audio continues in the background with a native notification showing playback controls.

</details>

<details>

<summary>What happens if the user taps the notification controls?</summary>

The native media session handles Play, Pause, and Stop actions from the notification panel automatically. You don't need to write any additional JavaScript to handle notification control taps — they work out of the box.

</details>

<details>

<summary>Can I show custom artwork in the notification?</summary>

Yes. Pass an `imageUrl` parameter to `playMedia()` with a URL to your cover art, album artwork, or podcast logo. If omitted, the notification shows a default image.

</details>

<details>

<summary>Do I need to call `stopMedia()` when switching tracks?</summary>

It's good practice to call `stopMedia()` before calling `playMedia()` with a new URL. This cleanly ends the previous session and starts a fresh one for the new track.

</details>

<details>

<summary>Does this work with live streams?</summary>

Yes. You can pass a streaming URL (e.g. an HLS stream or an Icecast/Shoutcast URL) to `playMedia()` and it will play as a live stream with notification controls.

</details>

<details>

<summary>Is this available on both Android and iOS?</summary>

Yes. The Custom Media Player add-on supports both platforms.

</details>

*Feature for Android was taken live on 26/09/2023*\
*Feature for iOS was taken live on 18/06/2025*


# Media Player

Custom Media Player AdOn Controls

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](/javascript-apis/getting-started).
{% endhint %}

Functions required to have a media player like notification and control it in Android and iOS devices.

### Start Media

To start the media with controls call the function.

{% tabs %}
{% tab title="Plain JS" %}

<pre class="language-javascript"><code class="lang-javascript"><strong>window.WTN.MediaPlayer.playMedia({
</strong>    url:"Your Media URL",
    imageUrl:"Custom Image that should be shown in the notifcation"
})
</code></pre>

{% endtab %}

{% tab title="ES 6+" %}

```javascript
import { playMedia } from "webtonative/MediaPlayer";

playMedia({
    url:"Your Media URL",
    imageUrl:"Custom Image that should be shown in the notifcation"
})
```

{% endtab %}
{% endtabs %}

image (optional) : It's an optional parameter to show the image in notification else default image will be shown.

### Pause Media

{% tabs %}
{% tab title="Plain JS" %}

<pre class="language-javascript"><code class="lang-javascript"><strong>window.WTN.MediaPlayer.pauseMedia();
</strong></code></pre>

{% endtab %}

{% tab title="ES 6+" %}

```javascript
import { pauseMedia } from "webtonative/MediaPlayer";

pauseMedia();
```

{% endtab %}
{% endtabs %}

### Stop Media

{% tabs %}
{% tab title="Plain JS" %}

<pre class="language-javascript"><code class="lang-javascript"><strong>window.WTN.MediaPlayer.stopMedia();
</strong></code></pre>

{% endtab %}

{% tab title="ES 6+" %}

```javascript
import { stopMedia } from "webtonative/MediaPlayer";

stopMedia();
```

{% endtab %}
{% endtabs %}

\*Feature for Android was taken live on 26/09/2023\
\*Feature for iOS was taken live on 18/06/2025


# Notification View API

Access and manage notification views using the WebToNative JavaScript API. Handle notification interactions seamlessly in Android and iOS apps.

```javascript
<a href="w2n://notification-screen">Open Notification Screen</a>
```

We offer several query parameters to tailor this screen to your preferences:

* “title” : Defaults to "Notifications"
* "titleBarContentColor": Specifies the color for the title text and back icon button, with the default set to #111111.
* "titleBarBgColor": Sets the background color of the title bar, with the default being #FFFFFF

You can include these options as query parameters in the link, like so:

```javascript
w2n://notification-screen?title=Notifications&titleBarContentColor=#abcdef
```

The URL mentioned above can be utilised within an anchor tag or any URL field in WebToNative. For instance, you can incorporate this URL in the floating action button URL or bottom navigation item URL.

<div><figure><img src="/files/pNZXol1GYm0jnGvD11l3" alt=""><figcaption><p>IOS</p></figcaption></figure> <figure><img src="/files/eaDcCIRRqZODRAe5d9MT" alt=""><figcaption><p>Android</p></figcaption></figure></div>

Note : For iOS, notifications that are clicked will only be retained and will be displayed in this Notification Screen.


# Offer Card API

Display and manage offer cards using the WebToNative JavaScript API. Showcase promotions and personalized offers in Android and iOS apps.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](/javascript-apis/getting-started).
{% endhint %}

It is a UI component commonly used to present special promotions, pieces of information, or deals to users.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
const { loadOfferCard } = window.WTN

loadOfferCard({
    "action": "showOfferCard",
    "data": {
        "action": {
            "url": "https://www.webtonative.com",
            "button": {
                "textColor": "#FFFFFF",
                "bgColor": "#111111",
                "text": "WebToNative",
            }
        
        },
        "card":{
            "size": "SMALL",
            "position": "RIGHT",
            "bgColor":"#000000",
            "content": {
                "type": "IMAGE",
                "url": "https://wallpaperaccess.com/full/2083830.jpg"
            }
        },
        "id": "abc", // optional - specify only when to use the scheduling feature
        "schedule": {
            "duration": 1,
            "unit": "minutes"
        }
    }
    
})
```

{% endtab %}

{% tab title="npm" %}

<pre class="language-javascript"><code class="lang-javascript">import { loadOfferCard } from "webtonative"

<strong>loadOfferCard({
</strong>    "action": "showOfferCard",
    "data": {
        "action":{
            "url": "https://www.webtonative.com",
            "button": {
                "textColor":"#FFFFFF",
                "bgColor":"#111111",
                "text": "WebToNative",
            }
        
        },
        "card":{
            "size": "SMALL",
            "position": "RIGHT",
            "bgColor":"#000000",
            "content": {
                "type": "IMAGE",
                "url": "https://wallpaperaccess.com/full/2083830.jpg"
            }
        },
        "id": "abc", // optional - specify only when to use the scheduling feature
        "schedule": {
            "duration": 1,
            "unit": "minutes"
        }
    }
})
</code></pre>

{% endtab %}
{% endtabs %}

## **Parameters**&#x20;

**action**

* url
* button (optional)
  * textColor = Button text colour&#x20;
  * bgColor = Button background colour&#x20;
  * text = Button text&#x20;

**card**&#x20;

* size = SMALL | FULL\_SCREEN | FULL\_WIDTH   (required)
* position = LEFT | RIGHT (SMALL size card position default position right) (Has no impact in case of FULL\_SCREEN | FULL\_WIDTH )
* bgColor = Card background colour
* content (required)
  * type = IMAGE | VIDEO
  * url = Url of image or video they show in card

**id**

* To use the scheduling feature, you need to provide an offer card ID. You can use any valid ID.

**schedule** (Show Again in) - Controls how long the offer card remains hidden before it can be shown again.

* duration = Number&#x20;
* unit = days/minutes/hours
* Functionality for scheduling added on 12/12/2025

<div align="center" data-full-width="true"><figure><img src="/files/5K1PvF57KiS5b5SiQ6bp" alt="" width="375"><figcaption><p>iOS</p></figcaption></figure> <figure><img src="/files/emQtS0ReX1fgasdM2vgj" alt="" width="339"><figcaption><p>Android</p></figcaption></figure></div>


# Cookie Update API

Update and manage website cookies using the WebToNative JavaScript API. Sync cookie changes seamlessly across Android and iOS apps.

To force a cookie update in the Android app via a function in case cookies need to be updated immediately.

{% hint style="info" %}
You'll need to import the JavaScript file into your website before starting from this [link](/javascript-apis/getting-started).
{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.forceUpdateCookies();
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { forceUpdateCookies } from "webtonative"

forceUpdateCookies();
```

{% endtab %}
{% endtabs %}

Available on Android Only from 04/01/25


# App Launch Detection API

Detect app launch events using the WebToNative JavaScript API. Trigger custom actions and detect app launch when users open your Android or iOS app.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](/javascript-apis/getting-started).
{% endhint %}

This function returns `true` only when the app is launched. On subsequent calls during the same session, it will return `false`. If the app is terminated and relaunched, it will return `true` again. This behavior can be used to execute specific actions every time the app is launched.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.appFirstLoad().then(function(value){
  console.log(value)
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { appFirstLoad } from "webtonative"

appFirstLoad().then((value) => {
  console.log(value)
});
```

{% endtab %}
{% endtabs %}

Response : \
{\
&#x20;  type : "firstCallWhenAppStarted",\
&#x20;  result : true/false\
}

\*Feature was taken live on 23/12/2024


# Download Manager API

Manage file downloads using the WebToNative JavaScript API. Track, control, and monitor download progress in Android and iOS apps. Customise the download manager section according to your app.

```html
<a href="w2n://download-screen">Open Download Screen</a>
```

We offer several query parameters to tailor this screen to your preferences:

* "**title**": Sets the page title (default: "Downloads").
* "**titleBarContentColor**": Defines the color of the title text and back button icon (default: #111111).
* "**titleBarBgColor**": Specifies the background color of the title bar (default: #FFFFFF).

To apply these customizations, include the parameters in the link as shown below:

{% code overflow="wrap" %}

```javascript
w2n://download-screen?title=Downloads&titleBarContentColor=#abcdef&titleBarBgColor=#00000
```

{% endcode %}

The URL mentioned above can be utilised within an anchor tag or any URL field in WebToNative. For instance, you can incorporate this URL in the floating action button URL or bottom navigation item URL.

<div><figure><img src="/files/V1FOmzj2k4vGQ2m0baIj" alt=""><figcaption><p>Android</p></figcaption></figure> <figure><img src="/files/H9Gxw68N50PWVR6SvPC1" alt=""><figcaption><p>iOS</p></figcaption></figure></div>


# Dynamic App Icon API

Change your app icon dynamically using the WebToNative JavaScript API. Personalize app icons for Android and iOS based on user actions.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](/javascript-apis/getting-started).
{% endhint %}

With the code you can change the app icon simply by calling the function.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.updateAppIcon({
    iconName:"icon1" //Name of the new icon
})
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { updateAppIcon } from "webtonative";

updateAppIcon({
    iconName:"icon1" //Name of the new icon
})
```

{% endtab %}
{% endtabs %}

Icon name - Need to pass this if it is null then default icon is set.\
Note:- When creating the app build, the uploaded icons will be named **icon1, icon2, icon3**. To implement them correctly, ensure the function references these names accordingly.

\*Feature was taken live on 19/02/2025


# File Sharing API

Share files securely using the WebToNative JavaScript API. Enable seamless file sharing across Android and iOS apps with native support.

When using navigator.share() API, the user can customize the share behaviour based on the parameters.&#x20;

If you want to share a normal text on any platform.

```
navigator.share({
  type: "url"
  url: "Hello World! You can open this link. 
  https://i.imgur.com/HSLFU3i.jpeg", // text or url to be shared
});
```

To share a file as a doc, image, etc.

```
navigator.share({
  type: "file"
  url: "https://i.imgur.com/HSLFU3i.jpeg",
  extension: "jpg", //extension of the file
  text: "Hello World!"// text to be shared with the file
});
```

Add the following parameters to share the file:

* **type**: “file” or “url”
  * file: Indicate the sharing of the file
  * url: Indicate the sharing of the link
* **extension**(Only for Android): When you are sharing a file, keep in mind that you add a proper extension. Don’t include a full stop while writing the extension of the file. For example, doc, docx, and pdf.
* **url**: Link of the respective file or url to be shared.&#x20;
* **text (**&#x4F;ptiona&#x6C;**)**: A message or description of the file being shared.


# Android Bluetooth API

Connect to Bluetooth devices using the WebToNative JavaScript API.  Invoke functions to scan, pair, and upair to available Bluetooth devices from the app.

{% hint style="info" %}
You'll need to import the JavaScript file into your website before starting from this [link](/javascript-apis/getting-started).
{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}
{% code overflow="wrap" %}

```
const { Bluetooth } = window.WTN

//To start scanning for bluetooth devices
Bluetooth.startBluetoothScan({
    callback: function(data){
        //Contains data which has a list of available bluetooth devices
    }
})


//To connect bluetooth device with specific address
Bluetooth.pairDevice({
    address, //Device address that you want to connect
    timeout, //Time till the app should try to connect before returning not paired status
    callback: function(data){
        //Contains data which has result as PAIRED | NOT_PAIRED
    }
})


//To disconnect from the connected bluetooth device
Bluetooth.unpairDevice({
    address, //Device address that you want to connect
    callback: function(data){
        //Contains data which has result UNPAIRED | ERROR DURING UNPAIRING
    }
})
```

{% endcode %}
{% endtab %}

{% tab title="npm" %}

```
import { Bluetooth } from "webtonative";

//To start scanning for bluetooth devices
Bluetooth.startBluetoothScan({
    callback: function(data){
        //Contains data which has a list of available bluetooth devices
    }
})


//To connect bluetooth device with specific address
Bluetooth.pairDevice({
    address, //Device address that you want to connect
    timeout, //Time till the app should try to connect before returning not paired status
    callback: function(data){
        //Contains data which has result as PAIRED | NOT_PAIRED
    }
})


//To disconnect from the connected bluetooth device
Bluetooth.startBluetoothScan({
    address, //Device address that you want to connect
    callback: function(data){
        //Contains data which has result UNPAIRED | ERROR DURING UNPAIRING
    }
})
```

{% endtab %}
{% endtabs %}


# Android Orientation Handling API

Handle screen orientation changes using the WebToNative JavaScript API. Detect and control portrait and landscape modes in Android apps.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](/javascript-apis/getting-started).
{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

<pre><code>window.WTN.setOrientation({
    orientation, //"protrait | landscape"
<strong>    forceOrientation // true | false
</strong>})
</code></pre>

{% endtab %}

{% tab title="npm" %}

```
import { setOrientation } from "webtonative";

setOrientation({
    orientation, //"protrait | landscape"
    forceOrientation // true | false
})
```

{% endtab %}
{% endtabs %}

orientation (type:string) - values "protrait" or "landscape" - Specify the orientation you want to keep.

forceOrentation (type:boolean) - value true or false - To override the device sensor. If set to true even if the phone is change to other orientation the app will not change, if set to false it will change according to the device sensors.

Feature added in Android on 23/05/25


# Disable Screenshot API

Prevent screenshots and screen recording using the WebToNative JavaScript API. It enhances your app’s security and user privacy by preventing screenshots and screen recordings.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

```
window.WTN.disableScreenshot({
    ssKey:true/false
})
```

{% endtab %}

{% tab title="npm" %}

```
import { disableScreenshot } from "webtonative"

disableScreenshot({
    ssKey:true/false
})
```

{% endtab %}
{% endtabs %}

ssKey (Optional - Boolean parameter) - This is to override the settings set during the AddOn customisation. If screenshot was not disabled and you want to block it for a page then pass this value as  true and vice-versa.

Feature added in iOS on 13/06/25\
Feature added in Android on 19/12/25


# Safe Area API

Get safe area insets using the WebToNative JavaScript API. Retrieve safe area heights to adjust UI components and avoid overlaps with system areas like the notch or home indicator.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](/javascript-apis/getting-started).
{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

```
window.WTN.getSafeArea({
    callback: function(data){
        //Contains data of the safe area settings
    }
});

//Response Data
{
  "type":"getSafeArea",
  "top" : 150,
  "bottom" : 102, 
  "topSafeArea": true/false,
  "bottomSafeArea": true/false
}
```

{% endtab %}

{% tab title="npm" %}

```
import { getSafeArea } from "webtonative"

getSafeArea({
    callback: function(data){
        //Contains data of the safe area settings
    }
});

//Response Data
{
  "type":"getSafeArea",
  "top" : 150,
  "bottom" : 102, 
  "topSafeArea": true/false,
  "bottomSafeArea": true/false
});
```

{% endtab %}
{% endtabs %}

The response shows if the top or bottom safe area is there and the value for it.

#### Response / Return Values

| Field            | Type    | Example         | Platform     | Description                                     |
| ---------------- | ------- | --------------- | ------------ | ----------------------------------------------- |
| `type`           | String  | `"getSafeArea"` | iOS, Android | Identifies the response type                    |
| `top`            | Number  | `150`           | iOS, Android | The top safe area inset (in pixels)             |
| `bottom`         | Number  | `102`           | iOS, Android | The bottom safe area inset (in pixels)          |
| `left`           | Number  | `0`             | Android only | The left safe area inset (in pixels)            |
| `right`          | Number  | `0`             | Android only | The right safe area inset (in pixels)           |
| `topSafeArea`    | Boolean | `true`          | iOS, Android | Indicates whether a top safe area is present    |
| `bottomSafeArea` | Boolean | `true`          | iOS, Android | Indicates whether a bottom safe area is present |
| `leftSafeArea`   | Boolean | `false`         | Android only | Indicates whether a left safe area is present   |
| `rightSafeArea`  | Boolean | `false`         | Android only | Indicates whether a right safe area is present  |

<br>

**Availability:**

* iOS: Introduced June 13, 2025
* Android: Introduced March 9, 2026


# Disable Back Button API

Disable or customize the back button using the WebToNative JavaScript API. Control app navigation behavior on Android and iOS.

Control the default back button of the phone, blocking the user from going back in the app. For iOS, you can now disable the swipe gesture to go back.

{% hint style="info" %}
You'll need to import the JavaScript file into your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

```
window.WTN.customBackHandling({
    enable:true/false
})
```

{% endtab %}

{% tab title="npm" %}

```
import { customBackHandling } from "webtonative"

customBackHandling({
    enable:true/false
})
```

{% endtab %}
{% endtabs %}

enable (Boolean parameter) - Passing this true will disable the back button/swipe gesture.

`customBackHandling` this function on the window will be called where you can handle the back functionality.\
`Example:-`\
`windows.customBackHandling = function(){`\
&#x20;   `//Handle your logic` \
`}`

Feature added in Android on 27/06/25

Feature added in iOS on 04/09/25


# In-App Update API

Enable in-app updates using the WebToNative JavaScript API. Prompt users to update your Android app without leaving the application.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { InAppUpdate } = window.WTN;

InAppUpdate.checkIfAppUpdateAvailable({
    callback: function(data) {
        //your logic
    }
});

InAppUpdate.updateApplication({
    updateType: "immediate", //immediate or flexible
    callback: function(data) {
        
    }
});
```

{% endtab %}

{% tab title="npm" %}

```
import { InAppUpdate } from "webtonative";

InAppUpdate.checkIfAppUpdateAvailable({
    callback: function(data) {
        //your logic
    }
});

InAppUpdate.updateApplication({
    updateType: "immediate", //immediate or flexible
    callback: function(data) {
        
    }
});
```

{% endtab %}
{% endtabs %}

In `checkIfAppUpdateAvailable` it will return two keys isUpdateAvailable (boolean) and latestVersion (string)

For Example:- `{ "isUpdateAvailable": true, "latestVersion": "2.0.1" }`&#x20;

In `updateApplication`  it will return a key updateStatus (string)\
Values:- UPDATE\_CANCELLED or UPDATE\_STARTED

For Example:- `{"updateStatus":"UPDATE_CANCELLED"}`&#x20;

\*Feature added in Android on 02/07/25

### Check If App Update Available

Checks whether an update is available for the app.

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { InAppUpdate } = window.WTN;

InAppUpdate.checkIfAppUpdateAvailable({
    callback: function(data) {
        //your logic
    }
});
```

{% endtab %}

{% tab title="npm" %}

```
import { InAppUpdate } from "webtonative";

InAppUpdate.checkIfAppUpdateAvailable({
    callback: function(data) {
        //your logic
    }
});
```

{% endtab %}
{% endtabs %}

**Callback Response:**

It will return two keys `isUpdateAvailable` (boolean) and `latestVersion` (string).

```
{ "isUpdateAvailable": true, "latestVersion": "2.0.1" }
```

#### Show In-App Update UI

Displays a native in-app update prompt UI to the user. This allows you to trigger the platform's native update dialog and receive the result via a callback.

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { InAppUpdate } = window.WTN;

InAppUpdate.showInAppUpdateUI({
    callback: function(data) {
        //your logic
    }
});
```

{% endtab %}

{% tab title="npm" %}

```
import { InAppUpdate } from "webtonative";

InAppUpdate.showInAppUpdateUI({
    callback: function(data) {
        //your logic
    }
});
```

{% endtab %}
{% endtabs %}

**Callback Response:**

It will return three keys: `type` (string), `isUpdateAvailable` (boolean), and `latestVersion` (string).

| Key                 | Type    | Description                               |
| ------------------- | ------- | ----------------------------------------- |
| `type`              | string  | Always `"showInAppUpdateUI"`              |
| `isUpdateAvailable` | boolean | Whether an update is available            |
| `latestVersion`     | string  | The latest version available on the store |

For Example:

```json
{ "type": "showInAppUpdateUI", "isUpdateAvailable": true, "latestVersion": "2.0.1" }
```

```json
{ "type": "showInAppUpdateUI", "isUpdateAvailable": false, "latestVersion": "1.0.0" }
```

*\*Android and iOS for* checkIfAppUpdateAvailable *and* showInAppUpdateUI *support from 12/03/2026*


# Siri Shortcuts API - iOS Only

Allow your iOS users to trigger app actions using Siri voice commands by adding custom Siri Shortcuts to their device. This function registers a shortcut phrase and links it to a specific URL in app.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

```
const { Siri } = window.WTN

Siri.addToSiri({
  title: "Your Title for Siri Function",
  suggestedPhrase: "Prase for Siri to recognise and take action",
  actionUrl: "Url to redirect"
});


//Example
Siri.addToSiri({
  title: "Siri Shortcut",
  suggestedPhrase: "Open Webtonative",
  actionUrl: "https://www.webtonative.com/"
});
```

{% endtab %}

{% tab title="npm" %}

<pre><code>import { Siri } from "webtonative";
<strong>
</strong>Siri.addToSiri({
  title: "Track Order",
  suggestedPhrase: "Track my order",
  actionUrl: "https://yourdomain.com/orders"
});

//Example
Siri.addToSiri({
  title: "Track Order",
  suggestedPhrase: "Track my order",
  actionUrl: "https://yourdomain.com/orders"
});
</code></pre>

{% endtab %}
{% endtabs %}

This allows you to define a custom Siri shortcut (e.g., “Open Webtonative”) that opens a specific URL inside your app using voice commands like:

> *“Hey Siri, open webtonative”*

<div><figure><img src="/files/bnICoybojWfKUVJVx9gO" alt=""><figcaption></figcaption></figure> <figure><img src="/files/NpeA0IY6tcI22c8TP9GY" alt=""><figcaption></figcaption></figure></div>

| Key               | Type   | Required | Description                                             |
| ----------------- | ------ | -------- | ------------------------------------------------------- |
| `title`           | string | ✅        | The title of the shortcut shown in iOS UI               |
| `suggestedPhrase` | string | ✅        | The voice command you suggest users add to Siri         |
| `actionUrl`       | string | ✅        | The URL that should open when the shortcut is triggered |

Feature added in iOS on 04/08/25


# Native Data Store API

Store and retrieve data securely using the WebToNative JavaScript API. It allows your web app to store, retrieve, and delete key-value pairs in the native layer of Android and iOS apps.

{% tabs %}
{% tab title="Plain Javascript" %}
{% code overflow="wrap" %}

```javascript
const { setAppData, getAppData, deleteAppData, setCloudData, getCloudData, deleteCloudData } = window.WTN.NativeDatastore;

//Store in the app data -> Locally

setAppData({
  keyName: "your key",
  value: "value to store",
  callback: (response) => {
    console.log("Data saved:", response);
  }
});

getAppData({
  keyName: "your key",
  callback: (response) => {
    console.log("Data retrieved:", response);
  }
});

deleteAppData({
  keyName: "your key",
  callback: (response) => {
    console.log("Data deleted:", response);
  }
});

//Store in the cloud -> Cloud storage

setCloudData({
  keyName: "your key",
  value: "value to store",
  callback: (response) => {
    console.log("Cloud data saved:", response);
  }
});

getCloudData({
  keyName: "your key",
  callback: (response) => {
    console.log("Cloud data retrieved:", response);
  }
});

deleteCloudData({
  keyName: "your key",
  callback: (response) => {
    console.log("Cloud data deleted:", response);
  }
});
```

{% endcode %}
{% endtab %}

{% tab title="npm" %}
{% code overflow="wrap" fullWidth="false" %}

```javascript
import { setAppData, getAppData, deleteAppData, setCloudData, getCloudData, deleteCloudData } from "webtonative/NativeDatastore";

//Store in the app data -> Locally

setAppData({
  keyName: "your key",
  value: "value to store",
  callback: (response) => {
    console.log("Data saved:", response);
  }
});

getAppData({
  keyName: "your key",
  callback: (response) => {
    console.log("Data retrieved:", response);
  }
});

deleteAppData({
  keyName: "your key",
  callback: (response) => {
    console.log("Data deleted:", response);
  }
});

//Store in the cloud -> Cloud storage

setCloudData({
  keyName: "your key",
  value: "value to store",
  callback: (response) => {
    console.log("Cloud data saved:", response);
  }
});

getCloudData({
  keyName: "your key",
  callback: (response) => {
    console.log("Cloud data retrieved:", response);
  }
});

deleteCloudData({
  keyName: "your key",
  callback: (response) => {
    console.log("Cloud data deleted:", response);
  }
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

While setting the data supported data types are string, object, array

Feature taken live on 04/09/25


# Notification Functions API

Manage notification actions using the WebToNative JavaScript API. Functions to register the app for push notifications and to clear notifications from the device's notification tray.

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

## Register Notification

Registers the app for push notifications and returns the user's permission status along with the OneSignal player ID and Firebase token.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.registerNotification({
  callback: function (response) {
    console.log(response.permissionStatus);
    console.log(response.oneSignalPlayerId);
    console.log(response.firebaseToken);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { registerNotification } from "webtonative";

registerNotification({
  callback: (response) => {
    console.log(response.permissionStatus);
    console.log(response.oneSignalPlayerId);
    console.log(response.firebaseToken);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                  |
| ---------- | ---------- | -------- | -------------------------------------------- |
| `callback` | `Function` | No       | Callback function invoked with the response. |

**Callback Response:**

| Key                 | Type     | Description                                                         |
| ------------------- | -------- | ------------------------------------------------------------------- |
| `type`              | `String` | Always `"registerNotification"`.                                    |
| `permissionStatus`  | `String` | The notification permission status: `"ALLOWED"` or `"NOT_ALLOWED"`. |
| `oneSignalPlayerId` | `String` | The OneSignal player ID for this device.                            |
| `firebaseToken`     | `String` | The Firebase Cloud Messaging token for this device.                 |

***

## Remove All Notifications

Removes all currently displayed push notifications for the app from the device's notification tray/center.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.removeAllNotifications({
  callback: function (response) {
    console.log(response.isSuccess);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { removeAllNotifications } from "webtonative";

removeAllNotifications({
  callback: (response) => {
    console.log(response.isSuccess);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                  |
| ---------- | ---------- | -------- | -------------------------------------------- |
| `callback` | `Function` | No       | Callback function invoked with the response. |

**Callback Response:**

| Key         | Type               | Description                                                               |
| ----------- | ------------------ | ------------------------------------------------------------------------- |
| `type`      | `String`           | Always `"removeAllNotifications"`.                                        |
| `isSuccess` | `Boolean`          | `true` if all notifications were removed successfully, `false` otherwise. |
| `error`     | `String` or `null` | `null` on success. Error message describing what went wrong on failure.   |

**Example:**

```javascript
window.WTN.removeAllNotifications({
  callback: function (response) {
    if (response.isSuccess) {
      console.log("All notifications removed successfully.");
    } else {
      console.error("Failed to remove notifications:", response.error);
    }
  },
});
```


# Defer Notification

Function to request for notification permission manually

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.registerNotification({
  callback: function(data){
    //data will contain the keys
  }
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { registerNotification } from "webtonative"

registerNotification({
  callback: function(data){
     //data will contain the keys
  }
});
```

{% endtab %}
{% endtabs %}

Data keys

type : (String) - registerNotification

permissionStatus : (String) - ALLOWED || NOT\_ALLOWED

oneSignalPlayerId : (String) - Value if the one signal player id

firebaseToken : (String) - Value of the Firebase token

Feature taken live on 15/09/25


# RevenueCat JavaScript API

RevenueCat functions exposed through the WebToNative for your seamless in-app purchases and subscription management across iOS and Android.

[RevenueCat](https://www.revenuecat.com/) is a subscription and in-app purchase management platform that sits on top of Apple's StoreKit and Google Play Billing. Instead of writing separate purchase, receipt-validation, and entitlement logic for each store, you manage products, pricing, and subscriber state in one dashboard, and RevenueCat keeps both platforms in sync.

WebToNative's RevenueCat plugin integrates the official [RevenueCat Android SDK](https://www.revenuecat.com/docs/getting-started/installation/android) and [iOS SDK](https://www.revenuecat.com/docs/getting-started/installation/ios) natively, and exposes them as a single JavaScript API, so you can trigger native purchase flows and read subscriber status from your website's JavaScript.

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

> **Platform support:** Android and iOS. Native paywalls (`showPaywall`) require iOS 15 or later; all other functions support iOS 14+ and Android.

***

## Key Concepts

If you're new to RevenueCat, these five terms will make the rest of this page much easier to follow:

| Term             | What it means                                                                                                                                                                                                            |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Product**      | A single purchasable item, defined in App Store Connect / Google Play Console (e.g. `premium_monthly`), and imported into RevenueCat. This is the `productId` you pass to `makePurchase`.                                |
| **Entitlement**  | A level of access you define in RevenueCat (e.g. `"pro"`), which one or more Products unlock. Your app should check entitlements — not specific product IDs — to decide what a user can access.                          |
| **Offering**     | A named group of Products you want to present together (e.g. your current pricing screen). This is the `offeringId` you pass to `showPaywall`. Offerings let you change pricing/products remotely without an app update. |
| **Paywall**      | A pre-built purchase screen you design visually in the RevenueCat dashboard and attach to an Offering. `showPaywall` renders this native screen for you, no UI code required.                                            |
| **CustomerInfo** | RevenueCat's record of what a specific user has purchased and which entitlements are currently active. Returned by `getCustomerInfo`, `setUserId`, and `restorePurchase`.                                                |

There are two different ways to sell something with this plugin, pick based on how much UI you want RevenueCat to build for you:

* **`showPaywall`** - shows RevenueCat's own paywall screen (built in their dashboard) for an Offering. Handles product selection, purchase, restore, and cancellation UI for you.
* **`makePurchase`** - skips any RevenueCat UI and goes straight to the native App Store / Play Store purchase sheet for one specific Product ID. Use this if you're building your own pricing screen in your web UI and just need the native checkout.

***

## How It Works

1. **`configure()`** initializes the SDK once per app session, normally right after your app loads.
2. Every other function requires `configure()` to have already succeeded. If you call any of them first, they immediately return a `NOT_CONFIGURED` error.
3. When a user logs into your app, call **`setUserId()`** to attach your own user ID to their RevenueCat subscriber record (instead of RevenueCat's auto-generated anonymous ID).
4. Use **`showPaywall`** or **`makePurchase`** to sell a subscription or one-time product.
5. Use **`getCustomerInfo`** anywhere in your app to check what the current user has access to (e.g. before showing premium content).
6. Use **`restorePurchase`** to let returning users recover purchases made previously on the same App Store / Play Store account.

{% hint style="warning" %}
**`configure()` must run first.** `isInitialized` also works before configuration (it just reports `false`), but `setUserId`, `getCustomerInfo`, `showPaywall`, `makePurchase`, and `restorePurchase` will all fail with `NOT_CONFIGURED` until `configure()` has completed successfully.
{% endhint %}

{% hint style="danger" %}
**The `callback` response is not proof of a valid, ongoing purchase, don't use it as your source of truth.** It only fires while your app happens to be open, for the one event that just occurred on that device. It cannot tell you about a renewal, a billing-retry recovery, an Apple/Google-initiated refund or chargeback, a subscription expiring while the user isn't in your app, or a family-sharing member losing access. Use the callback purely for immediate UI feedback ("Purchase complete!"). Your backend must get its entitlement state from RevenueCat Webhooks instead, see below.
{% endhint %}

***

## Setting Up RevenueCat

### 1. Create Your RevenueCat Project

1. Sign up at [app.revenuecat.com](https://app.revenuecat.com/) and create a Project.
2. Add your app under both the **App Store** and **Play Store** platforms (RevenueCat treats them as two separate "apps" inside one project, each with its own API key).
3. Import your in-app products/subscriptions from App Store Connect and Google Play Console into RevenueCat as **Products**.
4. Group related Products into an **Entitlement** (e.g. `"pro"`), and group the Products you want to sell together into an **Offering**.
5. *(Optional, for `showPaywall`)* Design a **Paywall** in the RevenueCat dashboard and attach it to your Offering.

### 2. Get Your API Keys

RevenueCat issues a **separate public API key for iOS and for Android** within the same project (**Project Settings → API Keys**). Since your JavaScript runs in both apps, detect the platform and pass the matching key:

```javascript
import { platform } from "webtonative";
import { configure } from "webtonative/RevenueCat";

const apiKey = platform === "IOS_APP" ? "appl_YOUR_IOS_KEY" : "goog_YOUR_ANDROID_KEY";

configure({
  apiKey,
  callback: (response) => console.log(response),
});
```

### 3. Enable the Add-on in WebToNative

Open your **WebToNative Dashboard → Add-ons → RevenueCat** and enable it. There's no additional dashboard configuration, your API key, user IDs, offerings, and product IDs are all supplied at runtime from your JavaScript, as shown below.

#### 4. Set Up Webhooks (Required)

This SDK only reports what happened during a live app session, it is not a backend integration. To keep your own database's subscription/entitlement state correct, you need a server that listens for [RevenueCat Webhooks](https://www.revenuecat.com/docs/integrations/webhooks):

1. In the RevenueCat dashboard, go to **Project Settings → Integrations → Webhooks** and add your backend's endpoint URL.
2. Set an **Authorization header value** - RevenueCat sends it with every webhook request so you can verify the request actually came from RevenueCat before trusting it.
3. On your server, handle at least these event types (`event.type` in the webhook payload): `INITIAL_PURCHASE`, `RENEWAL`, `CANCELLATION`, `UNCANCELLATION`, `EXPIRATION`, `BILLING_ISSUE`, `PRODUCT_CHANGE`, `TRANSFER`, and `REFUND_REVERSED`. Update the affected `app_user_id`'s access in your own database on each one.
4. Treat webhooks, not the JS `callback` as the authoritative signal for whether a user currently has access.

{% hint style="info" %}
WebToNative does not provide, proxy, or store these events for you, this webhook endpoint must be built and hosted on your own backend.
{% endhint %}

***

## JavaScript API Reference

### configure

Initializes the RevenueCat SDK for the current app session. Call this once, as early as possible (e.g. on app load), before calling any other function in this API.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.RevenueCat.configure({
  apiKey: "YOUR_REVENUECAT_API_KEY",
  userId: "optional_user_id", // omit to start as an anonymous user
  callback: function (response) {
    console.log(response);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { configure } from "webtonative/RevenueCat";

configure({
  apiKey: "YOUR_REVENUECAT_API_KEY",
  userId: "optional_user_id", // omit to start as an anonymous user
  callback: (response) => {
    console.log(response);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                                                                                                                                                                |
| ---------- | ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `apiKey`   | `String`   | Yes      | Your platform-specific RevenueCat public API key (see [Get Your API Keys](#2.-get-your-api-keys)).                                                                                         |
| `userId`   | `String`   | No       | A stable ID for this user (e.g. your own database user ID). If omitted, RevenueCat generates and persists an anonymous ID on-device. You can attach a real user ID later with `setUserId`. |
| `callback` | `Function` | No       | Function invoked with the result.                                                                                                                                                          |

**Response:**

| Key       | Type      | Description                                                                                                                                                              |
| --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`    | `String`  | Always `"configure"`.                                                                                                                                                    |
| `success` | `Boolean` | `true` if the SDK initialized successfully.                                                                                                                              |
| `error`   | `String`  | Present on failure. On iOS, this is `"API_KEY_MISSING"` if `apiKey` was empty. Android does not currently return an `error` value for this call — only `success: false`. |

***

### isInitialized

Checks whether `configure()` has already been called successfully in this session. Use this to avoid re-configuring, or to decide whether to show a loading state while `configure()` runs.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.RevenueCat.isInitialized({
  callback: function (response) {
    console.log(response.isInitialized);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { isInitialized } from "webtonative/RevenueCat";

isInitialized({
  callback: (response) => {
    console.log(response.isInitialized);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                       |
| ---------- | ---------- | -------- | --------------------------------- |
| `callback` | `Function` | No       | Function invoked with the result. |

**Response:**

| Key             | Type      | Description                                                                                                        |
| --------------- | --------- | ------------------------------------------------------------------------------------------------------------------ |
| `type`          | `String`  | Always `"isInitialized"`.                                                                                          |
| `success`       | `Boolean` | `true` if the check completed (this is `true` even when `isInitialized` is `false`, as long as no error occurred). |
| `isInitialized` | `Boolean` | `true` if the SDK has been configured.                                                                             |
| `error`         | `String`  | Present only if the check itself failed after configuration.                                                       |

***

### setUserId

Attaches your own user ID to the current subscriber, replacing RevenueCat's anonymous ID. Call this right after a user logs into your app, so their purchases and entitlements follow their account across devices.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.RevenueCat.setUserId({
  userId: "your_app_user_id",
  callback: function (response) {
    console.log(response.customerInfo);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { setUserId } from "webtonative/RevenueCat";

setUserId({
  userId: "your_app_user_id",
  callback: (response) => {
    console.log(response.customerInfo);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                 |
| ---------- | ---------- | -------- | ------------------------------------------- |
| `userId`   | `String`   | Yes      | The user ID to identify this subscriber as. |
| `callback` | `Function` | No       | Function invoked with the result.           |

**Response:**

| Key            | Type      | Description                                                                            |
| -------------- | --------- | -------------------------------------------------------------------------------------- |
| `type`         | `String`  | Always `"setUserId"`.                                                                  |
| `success`      | `Boolean` | `true` if the ID was set successfully.                                                 |
| `customerInfo` | `Object`  | The subscriber's [CustomerInfo](#customerinfo-object) after switching to this user ID. |
| `error`        | `String`  | Present on failure, e.g. `"NOT_CONFIGURED"` if called before `configure()`.            |

***

### getCustomerInfo

Fetches the current user's latest purchase and entitlement status, without showing any UI. Call this whenever you need to check what a user has access to, for example, before rendering a premium feature.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.RevenueCat.getCustomerInfo({
  callback: function (response) {
    const isPro = response.customerInfo?.entitlements?.active?.["pro"] != null;
    console.log("Has pro access:", isPro);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { getCustomerInfo } from "webtonative/RevenueCat";

getCustomerInfo({
  callback: (response) => {
    const isPro = response.customerInfo?.entitlements?.active?.["pro"] != null;
    console.log("Has pro access:", isPro);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                       |
| ---------- | ---------- | -------- | --------------------------------- |
| `callback` | `Function` | No       | Function invoked with the result. |

**Response:**

| Key            | Type      | Description                                                                 |
| -------------- | --------- | --------------------------------------------------------------------------- |
| `type`         | `String`  | Always `"getCustomerInfo"`.                                                 |
| `success`      | `Boolean` | `true` if the info was fetched successfully.                                |
| `customerInfo` | `Object`  | The subscriber's [CustomerInfo](#customerinfo-object).                      |
| `error`        | `String`  | Present on failure, e.g. `"NOT_CONFIGURED"` if called before `configure()`. |

***

### showPaywall

Displays the native paywall screen you designed in the RevenueCat dashboard for a given Offering. RevenueCat handles product display, purchase, restore, and cancellation entirely within this screen, your callback just reports the final outcome.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.RevenueCat.showPaywall({
  offeringId: "default",
  callback: function (response) {
    if (response.success) {
      if (response.restore) {
        console.log("Previous purchases restored:", response.customerInfo);
      } else {
        console.log("Purchase completed:", response.transaction);
      }
    } else {
      console.error("Paywall closed without a purchase:", response.error);
    }
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { showPaywall } from "webtonative/RevenueCat";

showPaywall({
  offeringId: "default",
  callback: (response) => {
    if (response.success) {
      if (response.restore) {
        console.log("Previous purchases restored:", response.customerInfo);
      } else {
        console.log("Purchase completed:", response.transaction);
      }
    } else {
      console.error("Paywall closed without a purchase:", response.error);
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key          | Type       | Required | Description                                                                                                      |
| ------------ | ---------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| `offeringId` | `String`   | Yes      | The identifier of the Offering (with an attached Paywall) to display, as configured in the RevenueCat dashboard. |
| `callback`   | `Function` | No       | Function invoked once per outcome — purchase, restore, error, or dismissal. See **Response** below.              |

**Response - purchase completed (`restore` absent):**

| Key           | Type      | Description                                                                      |
| ------------- | --------- | -------------------------------------------------------------------------------- |
| `type`        | `String`  | Always `"showPaywall"`.                                                          |
| `success`     | `Boolean` | `true`.                                                                          |
| `transaction` | `Object`  | See [Transaction object](#transaction-object) below - shape differs by platform. |

**Response - restore completed from within the paywall:**

| Key            | Type      | Description                                                            |
| -------------- | --------- | ---------------------------------------------------------------------- |
| `type`         | `String`  | Always `"showPaywall"`.                                                |
| `success`      | `Boolean` | `true`.                                                                |
| `restore`      | `Boolean` | `true`.                                                                |
| `customerInfo` | `Object`  | The subscriber's [CustomerInfo](#customerinfo-object) after restoring. |

**Response - failure:**

| Key       | Type      | Description                                                               |
| --------- | --------- | ------------------------------------------------------------------------- |
| `success` | `Boolean` | `false`.                                                                  |
| `restore` | `Boolean` | Present and `true` only if the failure happened during a restore attempt. |
| `error`   | `String`  | See error values below.                                                   |

**Errors you may see:**

| Value                        | Platform     | Meaning                                                                                                                 |
| ---------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `NOT_CONFIGURED`             | Android, iOS | `configure()` hasn't succeeded yet.                                                                                     |
| `OFFERING_NOT_FOUND`         | Android      | No Offering matching `offeringId` was found.                                                                            |
| `PURCHASE_CANCELLED_BY_USER` | Android      | User dismissed the paywall without purchasing.                                                                          |
| `PURCHASE_FLOW_CANCELLED`    | iOS          | User dismissed the paywall without purchasing (equivalent to `PURCHASE_CANCELLED_BY_USER` on Android — see hint below). |
| any other message            | Android, iOS | The underlying RevenueCat/store error for a failed purchase or restore.                                                 |

{% hint style="info" %}
**Platform inconsistencies to handle:** The cancellation error string differs by platform, Android sends `"PURCHASE_CANCELLED_BY_USER"`, iOS sends `"PURCHASE_FLOW_CANCELLED"`. Check for both if you want to detect "user simply closed the paywall" versus a real error. Also, on iOS, if `offeringId` doesn't match any existing Offering, the SDK currently does not invoke the callback at all, no error is returned. Apply a client-side timeout if you need to handle an invalid `offeringId` gracefully on iOS; on Android, this same case returns `OFFERING_NOT_FOUND` immediately.
{% endhint %}

***

### makePurchase

Skips any RevenueCat UI and opens the native App Store / Play Store purchase sheet directly for one Product ID. Use this when you're building your own pricing/paywall screen in your website and only need the native checkout step.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.RevenueCat.makePurchase({
  productId: "premium_monthly",
  callback: function (response) {
    if (response.success) {
      console.log("Purchase completed:", response.transaction);
    } else {
      console.error("Purchase not completed:", response.error);
    }
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { makePurchase } from "webtonative/RevenueCat";

makePurchase({
  productId: "premium_monthly",
  callback: (response) => {
    if (response.success) {
      console.log("Purchase completed:", response.transaction);
    } else {
      console.error("Purchase not completed:", response.error);
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key         | Type       | Required | Description                                                                                                               |
| ----------- | ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| `productId` | `String`   | Yes      | The store product identifier to purchase, exactly as it exists in App Store Connect / Google Play Console and RevenueCat. |
| `callback`  | `Function` | No       | Function invoked with the result.                                                                                         |

**Response - success:**

| Key           | Type      | Description                                                                      |
| ------------- | --------- | -------------------------------------------------------------------------------- |
| `type`        | `String`  | Always `"makePurchase"`.                                                         |
| `success`     | `Boolean` | `true`.                                                                          |
| `transaction` | `Object`  | See [Transaction object](#transaction-object) below — shape differs by platform. |

**Response - failure:**

| Key       | Type      | Description             |
| --------- | --------- | ----------------------- |
| `success` | `Boolean` | `false`.                |
| `error`   | `String`  | See error values below. |

**Errors you may see:**

| Value                            | Platform     | Meaning                                          |
| -------------------------------- | ------------ | ------------------------------------------------ |
| `NOT_CONFIGURED`                 | Android, iOS | `configure()` hasn't succeeded yet.              |
| `PRODUCT_<productId>_NOT_FOUND.` | Android      | No store product matching `productId` was found. |
| `PURCHASE_FLOW_CANCELLED`        | iOS          | User cancelled the purchase sheet.               |
| any other message                | Android, iOS | The underlying RevenueCat/store error message.   |

{% hint style="info" %}
**Platform inconsistency:** When the user cancels, iOS returns the fixed value `"PURCHASE_FLOW_CANCELLED"`. Android does **not** currently normalize this, it returns whatever raw error message the store/RevenueCat SDK produced for the cancellation. Don't do a strict string match against `"PURCHASE_FLOW_CANCELLED"` on Android if you need to detect user cancellation reliably.
{% endhint %}

***

### restorePurchase

Restores any purchases already associated with the device's App Store / Play Store account, without showing a checkout screen. Use this behind a "Restore Purchases" button, typically on a settings or paywall screen.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.RevenueCat.restorePurchase({
  callback: function (response) {
    console.log(response.customerInfo);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { restorePurchase } from "webtonative/RevenueCat";

restorePurchase({
  callback: (response) => {
    console.log(response.customerInfo);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                       |
| ---------- | ---------- | -------- | --------------------------------- |
| `callback` | `Function` | No       | Function invoked with the result. |

**Response:**

| Key            | Type      | Description                                                                                                                                                          |
| -------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`         | `String`  | Always `"restorePurchase"`.                                                                                                                                          |
| `success`      | `Boolean` | `true` if the restore completed (this does **not** mean anything was found — a user with no past purchases still gets `success: true` with an empty `customerInfo`). |
| `customerInfo` | `Object`  | The subscriber's [CustomerInfo](#customerinfo-object) after restoring.                                                                                               |
| `error`        | `String`  | Present on failure, e.g. `"NOT_CONFIGURED"` if called before `configure()`.                                                                                          |

***

## Reference Objects

### CustomerInfo object

`customerInfo` is RevenueCat's own subscriber record, passed straight through from the native SDK. The fields you'll use most often:

| Field                                     | Description                                                                                                                                                                |
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `entitlements.active`                     | An object keyed by entitlement identifier (e.g. `"pro"`) — a key is present only if that entitlement is currently active for this user. Use this to gate premium features. |
| `activeSubscriptions`                     | Array of product identifiers the user currently has an active subscription for.                                                                                            |
| `allExpirationDates` / `allPurchaseDates` | Per-product expiration/purchase timestamps.                                                                                                                                |
| `originalAppUserId`                       | The RevenueCat App User ID this record belongs to.                                                                                                                         |
| `requestDate`                             | When this CustomerInfo snapshot was generated.                                                                                                                             |

For the complete, authoritative field list, see RevenueCat's [CustomerInfo reference](https://www.revenuecat.com/docs/customers/customer-info).

### Transaction object

Returned in `transaction` by both `showPaywall` and `makePurchase` on a successful purchase. The shape differs by platform:

**iOS:**

| Key             | Description                                                         |
| --------------- | ------------------------------------------------------------------- |
| `transactionId` | The App Store transaction identifier.                               |
| `productId`     | The purchased product's identifier.                                 |
| `purchaseDate`  | Purchase timestamp, in milliseconds since epoch (as a string).      |
| `storefrontId`  | The App Store storefront (country/region) the purchase was made in. |
| `appUserId`     | The RevenueCat App User ID for this subscriber.                     |

**Android:**

| Key             | Description                                          |
| --------------- | ---------------------------------------------------- |
| `transactionId` | The Google Play purchase token for this transaction. |
| `googleOrderId` | The Google Play order ID.                            |
| `productId`     | The purchased product's identifier.                  |
| `purchaseDate`  | Purchase timestamp, in milliseconds since epoch.     |
| `appUserId`     | The RevenueCat App User ID for this subscriber.      |

***

## Typical Implementation Flow

1. **On app load** - call `configure()` with the platform-specific API key. If the user is already logged into your app, pass their ID as `userId` directly.
2. **On login** (if you didn't have the user ID at configure-time) — call `setUserId()` to attach it.
3. **Gate premium content** - call `getCustomerInfo()` and check `entitlements.active` before showing premium features.
4. **Sell a subscription/product** - call `showPaywall()` if you've designed a RevenueCat paywall, or `makePurchase()` if you built your own pricing UI.
5. **Give returning users a way back in** - call `restorePurchase()` from a "Restore Purchases" button.
6. **Keep your backend in sync independently** - don't rely on step 4's callback for anything beyond immediate UI feedback. Your backend should listen to RevenueCat Webhooks to know when access should actually be granted, renewed, or revoked.

***

## Implementation Checklist

### RevenueCat Dashboard

* [ ] Project created, with both App Store and Play Store apps added

* [ ] Products imported from App Store Connect / Google Play Console

* [ ] Products grouped into at least one Entitlement

* [ ] Products grouped into at least one Offering

* [ ] *(If using `showPaywall`)* A Paywall designed and attached to the Offering

* [ ] Webhook endpoint added under Project Settings → Integrations → Webhooks, with an Authorization header configured ← add to "RevenueCat Dashboard" checklist

#### Your Backend

* [ ] An endpoint that receives RevenueCat Webhooks and verifies the Authorization header before trusting the payload
* [ ] Handles `INITIAL_PURCHASE`, `RENEWAL`, `CANCELLATION`, `EXPIRATION`, and `BILLING_ISSUE` at minimum, updating each `app_user_id`'s access in your own database
* [ ] Treats webhook events — not the JS `callback` — as the source of truth for whether a user currently has access

### WebToNative Dashboard

* [ ] RevenueCat add-on enabled

### Your Website

* [ ] Imported the [WebToNative JavaScript bridge](https://docs.webtonative.com/javascript-apis/getting-started)
* [ ] Called `configure()` on app load with the correct platform-specific API key
* [ ] Called `setUserId()` after login (if not passed at configure-time)
* [ ] Used `getCustomerInfo()` to gate premium features based on entitlements
* [ ] Implemented `showPaywall()` or `makePurchase()` for checkout
* [ ] Added a `restorePurchase()` option for returning users

***

## Frequently Asked Questions

<details>

<summary>What's the difference between `showPaywall` and `makePurchase`?</summary>

`showPaywall` renders a full paywall screen you designed in the RevenueCat dashboard, for a group of products (an Offering) — RevenueCat handles selection, purchase, and restore UI for you. `makePurchase` skips all of that and opens the native purchase sheet directly for one specific product ID, for use with your own custom pricing UI.

</details>

<details>

<summary>Do I need the same API key for iOS and Android?</summary>

No. RevenueCat issues a separate public API key per platform within the same project. Detect the platform in your JavaScript and pass the matching key to `configure()`.

</details>

<details>

<summary>Why did my function call fail with `NOT_CONFIGURED`?</summary>

Every function except `configure()` requires `configure()` to have completed successfully first. Make sure `configure()` runs (and its callback fires with `success: true`) before calling any other RevenueCat function.

</details>

<details>

<summary>Why do cancellation errors look different on Android vs iOS?</summary>

The native SDKs don't normalize this consistently yet. For `showPaywall`, Android sends `"PURCHASE_CANCELLED_BY_USER"` while iOS sends `"PURCHASE_FLOW_CANCELLED"` for the same event. For `makePurchase`, iOS sends the fixed `"PURCHASE_FLOW_CANCELLED"`, while Android passes through the raw underlying error message instead. Check for both/loosely on Android rather than relying on an exact string match.

</details>

<details>

<summary>Does `restorePurchase` show any UI to the user?</summary>

No. It silently checks the App Store / Play Store account already signed in on the device and updates `customerInfo` accordingly. There's no purchase sheet or confirmation dialog — build your own success/failure UI around the callback.

</details>

<details>

<summary>How do I check if a user has an active subscription?</summary>

Call `getCustomerInfo()` and check `response.customerInfo.entitlements.active` for the entitlement identifier you configured in RevenueCat (e.g. `"pro"`) — its presence means the entitlement is currently active, regardless of which specific product unlocked it.

</details>

<details>

<summary>Do I still need webhooks if I already get a <code>callback</code> after a purchase?</summary>

Yes. The `callback` only fires for the exact purchase/restore action that just happened on that device, while your app is open. It never fires for events that happen when your app isn't running, a subscription renewing, a billing retry succeeding, an Apple/Google-initiated refund or chargeback, or a subscription simply expiring. If you grant access based only on the client callback, your backend's records will silently drift out of sync with what the user actually has. Set up RevenueCat Webhooks pointed at your own backend and treat those events as authoritative.

</details>

***


# RevenueCat JavaScript API

RevenueCat functions exposed through the WebToNative for your seamless in-app purchases and subscription management across iOS and Android.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}
{% code overflow="wrap" %}

```javascript
const { configure, isInitialized, setUserId, getCustomerInfo, showPaywall, makePurchase, restorePurchase } = window.WTN.RevenueCat;

//Initialises the RevenueCat SDK on iOS/Android.
configure({
    apiKey: "YOUR_REVENUECAT_API_KEY",
    userId: "optional_user_id", // optional
    callback: (response) => {
        console.log(response);
    }
});

//Check If SDK Is Initialised
isInitialised({
    callback: (response) => {
        console.log(response);
    }
})


//Setting User Id
setUserId({
    userId: "user_id",
    callback: (response) => {
        console.log(response);
    }
})

//Getting customer info
getCustomerInfo({
    callback: (response) => {
        console.log(response);
    }
})

//Showing th paywall
showPaywall({
    offeringId:"revenue_cat_offering_id",
    callback: (response) => {
        console.log(response);
    }
})

//Invoke default In App Purchase
makePurchase({
    productId:"store_product_id",
    callback: (response) => {
        console.log(response);
    }
})

//Restoring a purchase
restorePurchase({
    callback: (response) => {
        console.log(response);
    }
})
```

{% endcode %}
{% endtab %}

{% tab title="npm" %}
{% code overflow="wrap" %}

```
import { configure, isInitialized, setUserId, getCustomerInfo, showPaywall, makePurchase, restorePurchase } from "webtonative/RevenueCat";

//Initialises the RevenueCat SDK on iOS/Android.
configure({
    apiKey: "YOUR_REVENUECAT_API_KEY",
    userId: "optional_user_id", // optional
    callback: (response) => {
        console.log(response);
    }
});

//Check If SDK Is Initialised
isInitialised({
    callback: (response) => {
        console.log(response);
    }
})


//Setting User Id
setUserId({
    userId: "user_id",
    callback: (response) => {
        console.log(response);
    }
})

//Getting customer info
getCustomerInfo({
    callback: (response) => {
        console.log(response);
    }
})

//Showing th paywall
showPaywall({
    offeringId:"revenue_cat_offering_id",
    callback: (response) => {
        console.log(response);
    }
})

//Invoke default In App Purchase
makePurchase({
    productId:"store_product_id",
    callback: (response) => {
        console.log(response);
    }
})

//Restoring a purchase
restorePurchase({
    callback: (response) => {
        console.log(response);
    }
})
```

{% endcode %}
{% endtab %}
{% endtabs %}

Responses for the above fuctions

{% code overflow="wrap" %}

```
// 1. Configure
Success - { "type": "configure", "success": true }
Failure - { "type": "configure", "success": false, "error": "API_KEY_MISSING" }

// 2. Initialisation
Success - { "type": "isInitialized", "success": true, "isInitialized": true }
Failure - { "type": "isInitialized", "success": false, "isInitialized": false, "error": "ERROR_STRING" }

// 3. Setting User Id
Success - { "type": "setUserId", "success": true, "customerInfo": CustomerInfo }
Failure - { "type": "setUserId", "success": false, "error": "Error String" }

// 4. Getting Customer Info
Success - { "success": true, "customerInfo": CustomerInfo RevenueCat obj }
Failure - { "success": false, "error": Error String }

// 5. Showing Paywall
Success - 
// Purchase Success
{
  "type": "showPaywall",
  "success": true,
  // FOR IOS
  "transaction": {
    "transactionId": "STRING",
    "productId": "STRING",
    "purchaseDate": "STRING",
    "appUserId": "STRING",
    "storefrontId": "STRING"
  }
    // FOR ANDROID
  "transaction" :{
	 "productId": "STRING",
	 "purchaseDate": "STRING",
	 "appUserId": "STRING",
	 "transactionId": "STRING",
	 "googleOrderId": "STRING"
  }
}

// Restore Success
{ "type": "showPaywall", "success": true, "restore": true, "customerInfo": CustomerInfo }

Failure - 
//User Cancelled
{ "type": "showPaywall", "success": false, "error": "PURCHASE_FLOW_CANCELLED" }

// Purchase Failed
{ "type": "showPaywall", "success": false, "error": "Error String" }

// Restore Failed
{ "type": "showPaywall", "success": false, "restore": true, "error": "Error String" }
 
// 6. Make Purchase
Success - 
{
  "type": "makePurchase",
  "success": true,
  // For IOS
  "transaction": {
    "transactionId": "STRING",
    "productId": "STRING",
    "purchaseDate": "STRING",
    "storefrontId": "STRING",
    "appUserId": "STRING"
  }
  // FOR ANDROID
  "transaction" :{
	 "productId": "STRING",
	 "purchaseDate": "STRING",
	 "appUserId": "STRING",
	 "purchaseToken": "STRING",
	 "googleOrderId": "STRING"
  }
}

Failure - 
//User Cancelled
{ "type": "makePurchase", "success": false, "error": "PURCHASE_FLOW_CANCELLED" }

/Purchase Failed
{ "type": "makePurchase", "success": false, "error": "Error String" }
```

{% endcode %}


# Native Controls & JS Bridge Functions

Guide for controlling native UI elements and triggering JavaScript functions through the WebtoNative bridge. Enable seamless communication between web content and native apps.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

### 1. Trigger JavaScript Functions via URL Scheme

You can call JavaScript functions inside your WebView directly by using the `w2n://` URL scheme.

```
Syntax:-
w2n://jsFunction:YOUR_JS_CODE

Example:-
w2n://jsFunction:alert("Hello World");

This will execute the JavaScript alert("Hello World") inside the WebView.
```

### 2. Hide Native Components

You can hide native UI components dynamically using the **`hideNativeComponents()`** JavaScript function.

#### **Available Components:**

* `secondary_navigation`
* `admob`
* `floating_button`
* `connect`
* `bottom_navigation`
* `advanced_bottom_navigation`
* `header`

```
Syntax:-
hideNativeComponents(["component1", "component2"]);

Example:-
hideNativeComponents(["bottom_navigation"]);

To hide multiple components:
hideNativeComponents(["bottom_navigation", "header", "admob"]);
```

### 3. Show Native Components

You can show native UI components using the **`showNativeComponents()`** JavaScript function.

#### **Available Components:**

* `secondary_navigation`
* `admob`
* `floating_button`
* `connect`
* `bottom_navigation`
* `advanced_bottom_navigation`
* `header`

```
Syntax:-
showNativeComponents(["component1", "component2"]);

Example:-
showNativeComponents(["header"]);

To show multiple components:
showNativeComponents(["header", "floating_button", "secondary_navigation"]);
```

Feature taken live on 24/11/25

### 3. Setting Navigation Bar color in android

Set the android system navigation bar color.

```
window.WTN.setNavigationBarColor({ color: selectedColor });

//Pass the color you want to set in string format.
```

### 4. Pinch to zoom setting control in Android

Control pinch to zoom feature.

```
window.WTN.pinchToZoom({state:false});

//Pass the state as either true or false to enable or disable respectively.
```

### 5. Invoke Sidebar in Android and iOS

To open side bar in android and iOS call the below url scheme.

```
w2n://open-sidebar
```

### 6. Hiding the splash screen in Android and iOS

When the splash screen's type is set to `JS_TRIGGER` (instead of a timer or page-load trigger), the native app waits for the web page to explicitly call `window.splashScreenJsTrigger()` before hiding the splash screen and navigating forward.

```javascript
window.WTN.Splash.splashScreenJsTrigger();
```


# Passcode JavaScript API

Add passcode protection using the WebToNative JavaScript API. Secure your Android and iOS app with native passcode authentication.

Functions to manage the passcode in the app.

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

## Set Passcode

Creates or updates a user's passcode with an optional reauthentication step.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Passcode.setPasscode({ reauthenticate: true });
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { Passcode } from "webtonative";

Passcode.setPasscode({ reauthenticate: true });
```

{% endtab %}
{% endtabs %}

**Parameter:**

| Key              | Type      | Description                                                                                                                                                                   |
| ---------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reauthenticate` | `Boolean` | If `true`, the user must enter their existing password before setting or changing the passcode. If `false`, the passcode can be set without asking for the existing password. |

When reauthentication is enabled, the function prompts for the current password before proceeding. Otherwise, it goes directly to passcode setup.

***

## Reset Passcode

Resets the user's passcode with optional app data cleanup.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Passcode.resetPasscode({ resetAppData: false });
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { Passcode } from "webtonative";

Passcode.resetPasscode({ resetAppData: false });
```

{% endtab %}
{% endtabs %}

**Parameter:**

| Key            | Type      | Description                                                                                                                                                            |
| -------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `resetAppData` | `Boolean` | If `true`, all application data is cleared along with the passcode. If `false`, only the passcode-related data is cleared and the rest of the app data remains intact. |

Depending on the value of `resetAppData`, this either performs a full app data reset or limits the reset to passcode information only.


# Sendbird JavaScript API

Integrate Sendbird chat using the WebToNative JavaScript API. Enable real-time messaging and communication in Android and iOS apps.

Add real-time in-app messaging and push notifications to your app — powered by Sendbird's native SDKs.

[Sendbird](https://sendbird.com/) is a communication platform that provides pre-built chat UI, real-time messaging, and push notifications. Instead of building a messaging system from scratch — handling message delivery, read receipts, typing indicators, offline queuing, and notification routing — Sendbird handles all of it through its SDKs and cloud infrastructure.

WebToNative's Sendbird plugin integrates the official Sendbird iOS and Android SDKs directly into your app. This gives your users a **native messaging experience** with a full-featured chat UI, group channels, user management, and push notifications via APNs (iOS) and Firebase Cloud Messaging (Android) — all controllable from your web layer through the JavaScript bridge.

### Why use the native Sendbird plugin?

Embedding a web-based chat widget inside a WebView leads to poor performance, unreliable push notifications, and a UX that feels out of place on mobile. The WebToNative Sendbird plugin uses Sendbird's native UI kit, which means your users get a chat experience that looks and performs like a first-class native feature — smooth scrolling, native push delivery, and proper background behavior.

{% hint style="info" %}
**Prerequisites:** Import the WebToNative JavaScript bridge into your website before using any of the functions below. See the [Getting Started](https://docs.webtonative.com/javascript-apis/getting-started) guide.
{% endhint %}

***

## Key Concepts

Before diving into setup, here are a few terms you'll encounter throughout this guide:

**Application ID (`appId`)** — Identifies your Sendbird application. The SDK needs it to initialize. You'll find it in your Sendbird dashboard under your application's overview.

**Group Channel** — A channel where multiple users can chat. Creating one returns a `channelUrl` that you use to open the channel in the native UI.

**Distinct Channel (`isDistinct`)** — When set to `true`, if a group channel with the exact same members already exists, Sendbird reuses it instead of creating a duplicate. Useful for 1-on-1 conversations.

***

## Step 1 — Set Up Your Sendbird Account

### 1.1 Create a Sendbird Application

1. Sign up or log in at the [Sendbird Dashboard](https://dashboard.sendbird.com/).
2. Create a new application (or use an existing one).
3. Copy your **Application ID** from the application overview page — you'll need this for the WebToNative dashboard.

### 1.2 Configure Push Notifications in Sendbird

To deliver push notifications when users receive messages while the app is backgrounded, you need to provide Sendbird with your push credentials.

Navigate to **Settings → Push Notifications** in your Sendbird dashboard, enable push notifications, and add the following credentials:

**For iOS (Apple Push Notification service):**

| Credential                           | Where to find it                                                                                                                 |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| App Bundle ID                        | WebToNative Dashboard → App Info                                                                                                 |
| `.p8` or `.p12` authentication token | [Apple Developer Portal → Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/certificates/list) |
| Key ID                               | Apple Developer Portal → Keys section                                                                                            |
| Team ID                              | Apple Developer Portal → Membership details                                                                                      |

**For Android (Firebase Cloud Messaging):**

| Credential                    | Where to find it                                                                                                          |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Service Account Key (HTTP v1) | [Firebase Console](https://console.firebase.google.com/) → Project Settings → Service Accounts → Generate New Private Key |
| `google-services.json`        | Firebase Console → Project Settings → General → Your Apps → Download                                                      |

{% hint style="info" %}
If you don't have a `google-services.json` file yet, follow the [Firebase Notification Integration Guide](https://docs.webtonative.com/plugin/firebase-notification-integration-ios-setup) to create one.
{% endhint %}

***

## Step 2 — Configure the Plugin in WebToNative

Open your **WebToNative Dashboard → Add-ons → Sendbird** and configure the following settings:

### 2.1 Enable Sendbird

Turn on the **Enable Sendbird** toggle to activate the plugin.

### 2.2 Enter Your App ID

Enter the Sendbird **Application ID** you copied from the Sendbird dashboard in Step 1.

### 2.3 Upload Firebase Service JSON (Android)

Firebase Cloud Messaging requires a `google-services.json` file embedded in your Android build for push notifications to work.

Click **"Click to upload"** and select the `google-services.json` file you downloaded from the Firebase Console.

{% hint style="info" %}
**Note:** The `google-services.json` file is required for push notifications on Android. It is not required for iOS.
{% endhint %}

### 2.4 Disable Notification in App Foreground (Optional)

When enabled, push notifications will **not** appear while the user is actively using the app. This prevents disruptive notification banners during an active chat session.

### 2.5 Ask for Notification Permission on Launch

When enabled, the app will request push notification permission from the user the first time the app is launched.

If you disable this setting, you can request permission later using a **Trigger URL** — a specific URL path that, when visited by the user inside the app, triggers the notification permission prompt. This gives you control over when and where in your app flow the user sees the permission dialog.

### 2.6 Save and Rebuild

After configuring the settings, click **Save & Rebuild** to generate a new build with the Sendbird plugin enabled.

***

## JavaScript API Reference

### Initialize

Initializes the Sendbird SDK with a user ID and connects the user. You can optionally set a nickname and profile image at the same time.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.SendBird.sendbirdInitialize({
  userId: "user123",
  nickname: "John",
  profileurl: "https://example.com/avatar.png",
  callback: function (response) {
    if (response.error) {
      console.error("Init failed:", response.error);
      return;
    }
    console.log("Sendbird initialized for user:", response.userId);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { sendbirdInitialize } from "webtonative/SendBird";

sendbirdInitialize({
  userId: "user123",
  nickname: "John",
  profileurl: "https://example.com/avatar.png",
  callback: (response) => {
    if (response.error) {
      console.error("Init failed:", response.error);
      return;
    }
    console.log("Sendbird initialized for user:", response.userId);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key          | Type       | Required | Description                                                                                       |
| ------------ | ---------- | -------- | ------------------------------------------------------------------------------------------------- |
| `userId`     | `String`   | Yes      | The unique user ID to connect with. If the user doesn't exist in Sendbird, a new user is created. |
| `nickname`   | `String`   | No       | Display name for the user.                                                                        |
| `profileurl` | `String`   | No       | URL of the user's profile image.                                                                  |
| `callback`   | `Function` | No       | Function invoked with the initialization response.                                                |

***

### Is Initialized

Checks whether the Sendbird SDK has been initialized. Useful on app resume or page load to determine if you need to call `sendbirdInitialize` again.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.SendBird.sendbirdIsInitialized({
  callback: function (response) {
    if (response.initialized) {
      console.log("SDK ready, appId:", response.appId);
    } else {
      console.log("SDK not initialized — call sendbirdInitialize()");
    }
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { sendbirdIsInitialized } from "webtonative/SendBird";

sendbirdIsInitialized({
  callback: (response) => {
    if (response.initialized) {
      console.log("SDK ready, appId:", response.appId);
    } else {
      console.log("SDK not initialized — call sendbirdInitialize()");
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Response:**

| Key           | Type      | Description                                                |
| ------------- | --------- | ---------------------------------------------------------- |
| `initialized` | `Boolean` | `true` if the Sendbird SDK has been initialized.           |
| `appId`       | `String`  | The Sendbird Application ID (present only if initialized). |

***

### Is Connected

Checks whether the user is currently connected to the Sendbird server. A user can be initialized but temporarily disconnected (e.g. after calling `sendbirdDisconnect`).

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.SendBird.sendbirdIsConnected({
  callback: function (response) {
    console.log("Connected:", response.connected);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { sendbirdIsConnected } from "webtonative/SendBird";

sendbirdIsConnected({
  callback: (response) => {
    console.log("Connected:", response.connected);
  },
});
```

{% endtab %}
{% endtabs %}

**Response:**

| Key         | Type      | Description                                  |
| ----------- | --------- | -------------------------------------------- |
| `connected` | `Boolean` | `true` if the user is connected to Sendbird. |

***

### Get User ID

Retrieves the user ID of the currently connected user.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.SendBird.sendbirdGetUserId({
  callback: function (response) {
    console.log("Current user:", response.userId);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { sendbirdGetUserId } from "webtonative/SendBird";

sendbirdGetUserId({
  callback: (response) => {
    console.log("Current user:", response.userId);
  },
});
```

{% endtab %}
{% endtabs %}

**Response:**

| Key      | Type     | Description                          |
| -------- | -------- | ------------------------------------ |
| `userId` | `String` | The current user's Sendbird user ID. |

***

### Update User Info

Updates the current user's nickname and/or profile image URL in Sendbird.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.SendBird.sendbirdUpdateUserInfo({
  nickname: "New Display Name",
  profileurl: "https://example.com/new-avatar.png",
  callback: function (response) {
    if (response.success) {
      console.log("User info updated");
    }
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { sendbirdUpdateUserInfo } from "webtonative/SendBird";

sendbirdUpdateUserInfo({
  nickname: "New Display Name",
  profileurl: "https://example.com/new-avatar.png",
  callback: (response) => {
    if (response.success) {
      console.log("User info updated");
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key          | Type       | Required | Description                             |
| ------------ | ---------- | -------- | --------------------------------------- |
| `nickname`   | `String`   | No       | The new display name for the user.      |
| `profileurl` | `String`   | No       | The new profile image URL for the user. |
| `callback`   | `Function` | No       | Function invoked with the response.     |

**Response:**

| Key       | Type      | Description                          |
| --------- | --------- | ------------------------------------ |
| `success` | `Boolean` | `true` if the user info was updated. |

***

### Create Group Channel

Creates a new group channel with the specified users. Returns the `channelUrl` which you can use with `sendbirdShowChannelUI` to open the channel directly.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.SendBird.sendbirdCreateGroupChannel({
  name: "Project Chat",
  userIds: ["user1", "user2", "user3"],
  isDistinct: true,
  callback: function (response) {
    if (response.success) {
      console.log("Channel created:", response.channelUrl);
      // Open the channel immediately
      window.WTN.SendBird.sendbirdShowChannelUI({
        url: response.channelUrl,
      });
    }
  },
});
```

{% endtab %}

{% tab title="ES5+ Module" %}

```javascript
import {
  sendbirdCreateGroupChannel,
  sendbirdShowChannelUI,
} from "webtonative/SendBird";

sendbirdCreateGroupChannel({
  name: "Project Chat",
  userIds: ["user1", "user2", "user3"],
  isDistinct: true,
  callback: (response) => {
    if (response.success) {
      console.log("Channel created:", response.channelUrl);
      sendbirdShowChannelUI({ url: response.channelUrl });
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key          | Type       | Required | Description                                                                                                          |
| ------------ | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `name`       | `String`   | No       | Display name for the channel.                                                                                        |
| `userIds`    | `String[]` | Yes      | Array of user IDs to add to the channel. At least one user ID is required.                                           |
| `isDistinct` | `Boolean`  | No       | If `true`, reuses an existing channel with the exact same members instead of creating a duplicate. Default: `false`. |
| `callback`   | `Function` | No       | Function invoked with the response.                                                                                  |

**Response:**

| Key          | Type      | Description                                    |
| ------------ | --------- | ---------------------------------------------- |
| `success`    | `Boolean` | `true` if the channel was created (or reused). |
| `channelUrl` | `String`  | The URL identifier of the group channel.       |

***

### Show UI

Opens the full Sendbird native chat UI, showing the user's channel list. From here, users can enter channels, create new channels, and navigate back.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.SendBird.sendbirdShowUI({
  callback: function (response) {
    console.log("UI status:", response.status);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { sendbirdShowUI } from "webtonative/SendBird";

sendbirdShowUI({
  callback: (response) => {
    console.log("UI status:", response.status);
  },
});
```

{% endtab %}
{% endtabs %}

**Response:**

| Key       | Type      | Description                              |
| --------- | --------- | ---------------------------------------- |
| `success` | `Boolean` | `true` if the UI was shown successfully. |
| `status`  | `String`  | Status of the UI display operation.      |

***

### Show Channel UI

Opens the Sendbird native chat UI and navigates directly to a specific channel by its URL.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.SendBird.sendbirdShowChannelUI({
  url: "sendbird_group_channel_123",
  callback: function (response) {
    console.log("Opened channel:", response.channelUrl);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { sendbirdShowChannelUI } from "webtonative/SendBird";

sendbirdShowChannelUI({
  url: "sendbird_group_channel_123",
  callback: (response) => {
    console.log("Opened channel:", response.channelUrl);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                              |
| ---------- | ---------- | -------- | ---------------------------------------- |
| `url`      | `String`   | Yes      | The `channelUrl` of the channel to open. |
| `callback` | `Function` | No       | Function invoked with the response.      |

**Response:**

| Key          | Type      | Description                         |
| ------------ | --------- | ----------------------------------- |
| `success`    | `Boolean` | `true` if the channel UI was shown. |
| `status`     | `String`  | Status of the UI display operation. |
| `channelUrl` | `String`  | The URL of the displayed channel.   |

***

### UI Closed Callback

Define this function on your webpage to run custom logic when the user closes the Sendbird native UI (e.g. navigates back from the channel list). This is a global callback, not a parameter.

```javascript
function wtn_sendbird_uiclosed() {
  console.log("Sendbird UI was closed");
  // Navigate back, refresh data, etc.
}
```

***

### Disconnect

Disconnects the current user's session from Sendbird. The user remains logged in — their details stay saved on the device, and the device continues to receive push notifications. Use this for temporary disconnections (e.g. switching tabs, backgrounding).

To reconnect, call `sendbirdInitialize` again.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.SendBird.sendbirdDisconnect({
  callback: function (response) {
    if (response.success) {
      console.log("Disconnected (session preserved)");
    }
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { sendbirdDisconnect } from "webtonative/SendBird";

sendbirdDisconnect({
  callback: (response) => {
    if (response.success) {
      console.log("Disconnected (session preserved)");
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Response:**

| Key       | Type      | Description                              |
| --------- | --------- | ---------------------------------------- |
| `success` | `Boolean` | `true` if the disconnect was successful. |

***

### Logout

Fully logs out the current user from Sendbird. This disconnects the session, unregisters the device's push notification token, and clears all stored Sendbird data from the device. Use this when the user is signing out of your app.

> **Disconnect vs. Logout:** `sendbirdDisconnect` is a soft pause — the user stays logged in and push continues. `sendbirdLogout` is a full cleanup — push is unregistered and all local Sendbird data is cleared.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.SendBird.sendbirdLogout({
  callback: function (response) {
    if (response.success) {
      console.log("Logged out — push unregistered, data cleared");
    }
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { sendbirdLogout } from "webtonative/SendBird";

sendbirdLogout({
  callback: (response) => {
    if (response.success) {
      console.log("Logged out — push unregistered, data cleared");
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Response:**

| Key       | Type      | Description                          |
| --------- | --------- | ------------------------------------ |
| `success` | `Boolean` | `true` if the logout was successful. |

***

## Typical Implementation Flow

Here's how a typical Sendbird integration works end-to-end:

1. **App launch** — Call `sendbirdIsInitialized()` to check if the SDK is already set up.
2. **Initialize** — If not initialized, call `sendbirdInitialize()` with the user's ID, nickname, and profile image. This both initializes the SDK and connects the user.
3. **Show chat** — Call `sendbirdShowUI()` to open the full channel list, or `sendbirdCreateGroupChannel()` followed by `sendbirdShowChannelUI()` to create and open a specific channel.
4. **Handle UI close** — Define `wtn_sendbird_uiclosed()` on your page to run logic when the user exits the Sendbird UI.
5. **User signs out** — Call `sendbirdLogout()` to fully clear the session and unregister push. If the user is just switching views, use `sendbirdDisconnect()` instead.

### Recipe: Create a Channel and Open It

This common pattern creates a group channel (or reuses an existing one if `isDistinct` is `true`) and immediately opens it in the native chat UI:

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.SendBird.sendbirdCreateGroupChannel({
  name: "Support Chat",
  userIds: ["support_agent", "customer_42"],
  isDistinct: true,
  callback: function (response) {
    if (response.success) {
      window.WTN.SendBird.sendbirdShowChannelUI({
        url: response.channelUrl,
      });
    } else {
      console.error("Failed to create channel");
    }
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import {
  sendbirdCreateGroupChannel,
  sendbirdShowChannelUI,
} from "webtonative/SendBird";

sendbirdCreateGroupChannel({
  name: "Support Chat",
  userIds: ["support_agent", "customer_42"],
  isDistinct: true,
  callback: (response) => {
    if (response.success) {
      sendbirdShowChannelUI({ url: response.channelUrl });
    } else {
      console.error("Failed to create channel");
    }
  },
});
```

{% endtab %}
{% endtabs %}

***

## Implementation Checklist

### Sendbird Dashboard

* [ ] Created a Sendbird application and copied the **Application ID**
* [ ] Enabled **Push Notifications** under Settings → Push Notifications
* [ ] Added **APNs credentials** (iOS): `.p8`/`.p12` token, Key ID, Team ID, Bundle ID
* [ ] Added **FCM credentials** (Android): Service Account Key (HTTP v1)

### WebToNative Dashboard

* [ ] Enabled the **Sendbird** add-on
* [ ] Entered the Sendbird **Application ID**
* [ ] Uploaded **`google-services.json`** (required for Android push notifications)
* [ ] Configured **Disable Notification in App Foreground** based on your preference
* [ ] Configured **Ask for Notification Permission on Launch** (or set a Trigger URL for deferred permission)
* [ ] Clicked **Save & Rebuild** to generate a new build

### Your Website

* [ ] Imported the [WebToNative JavaScript bridge](https://docs.webtonative.com/javascript-apis/getting-started)
* [ ] Implemented `sendbirdInitialize()` with user ID on login
* [ ] Implemented `sendbirdShowUI()` or `sendbirdShowChannelUI()` for chat access
* [ ] Defined `wtn_sendbird_uiclosed()` callback for UI close handling
* [ ] Implemented `sendbirdLogout()` in your sign-out flow
* [ ] Tested push notifications on a real device (both iOS and Android)

***

## Frequently Asked Questions

<details>

<summary>What's the difference between <code>disconnect</code> and <code>logout</code>?</summary>

`sendbirdDisconnect` is a soft pause — the user stays logged in, their data is preserved on the device, and push notifications continue to be delivered. Use it for temporary disconnections like backgrounding or switching views. `sendbirdLogout` is a full cleanup — it disconnects the session, unregisters the device's push token, and clears all local Sendbird data. Use it when the user signs out of your app.

</details>

<details>

<summary>Push notifications aren't arriving. What should I check?</summary>

Verify the following in order: (1) Push notifications are enabled in the Sendbird dashboard under Settings → Push Notifications. (2) Your APNs or FCM credentials are correctly added in the Sendbird dashboard. (3) For Android, `google-services.json` is uploaded in the WebToNative dashboard. (4) The user is logged in (not logged out) — `sendbirdLogout` unregisters the push token. (5) You're testing on a real device, not a simulator.

</details>

<details>

<summary>Do I need to call <code>sendbirdInitialize</code> on every page load?</summary>

Not necessarily. Once initialized, the SDK persists across the app session. Use `sendbirdIsInitialized()` on page load to check — if it returns `initialized: true`, the SDK is already connected and you can proceed directly to showing UI or managing channels.

</details>

<details>

<summary>What happens if I call <code>sendbirdCreateGroupChannel</code> with <code>isDistinct: true</code> and the channel already exists?</summary>

Sendbird returns the existing channel's `channelUrl` instead of creating a duplicate. This is the recommended approach for 1-on-1 conversations or any scenario where you want to avoid duplicate channels between the same set of users.

</details>

<details>

<summary>Can I control when the notification permission prompt appears?</summary>

Yes. If you disable **Ask for Notification Permission on Launch** in the WebToNative dashboard, you can set a **Trigger URL** instead. The permission prompt will appear when the user navigates to that specific URL path inside the app. This lets you ask for permission at a contextually appropriate moment (e.g. when the user first opens the chat section).

</details>

<details>

<summary>Can I test Sendbird in a simulator?</summary>

The messaging UI and channel management work in simulators. However, push notifications require a physical device with APNs (iOS) or FCM (Android) support — simulators cannot receive push notifications.

</details>

<details>

<summary>Is <code>google-services.json</code> required for iOS?</summary>

No. The `google-services.json` file is only required for Android push notifications via Firebase Cloud Messaging. iOS push notifications use APNs credentials configured directly in the Sendbird dashboard.

</details>

***

*Feature taken live on 30/03/26*


# Sendbird Notification

Javascript Functions to integrate SendBird chat into your app.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

## Initialize

Initializes SendBird with a user ID and connects the user to the SendBird service.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.SendBird.sendbirdInitialize({
  userId: "user123",
  nickname: "John",
  profileurl: "https://example.com/avatar.png",
  callback: function (response) {
    console.log(response);
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { sendbirdInitialize } from "webtonative/build/SendBird";

sendbirdInitialize({
  userId: "user123",
  nickname: "John",
  profileurl: "https://example.com/avatar.png",
  callback: (response) => {
    console.log(response);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key          | Type       | Required | Description                                  |
| ------------ | ---------- | -------- | -------------------------------------------- |
| `userId`     | `String`   | Yes      | The unique user ID to connect with.          |
| `nickname`   | `String`   | No       | Display name for the user.                   |
| `profileurl` | `String`   | No       | URL of the user's profile image.             |
| `callback`   | `Function` | No       | Callback function invoked with the response. |

***

## Is Initialized

Checks whether SendBird has been initialized.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.SendBird.sendbirdIsInitialized({
  callback: function (response) {
    console.log(response.initialized); // true or false
    console.log(response.appId);
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { sendbirdIsInitialized } from "webtonative/build/SendBird";

sendbirdIsInitialized({
  callback: (response) => {
    console.log(response.initialized); // true or false
    console.log(response.appId);
  },
});
```

{% endtab %}
{% endtabs %}

**Callback Response:**

| Key           | Type      | Description                            |
| ------------- | --------- | -------------------------------------- |
| `initialized` | `Boolean` | Whether SendBird has been initialized. |
| `appId`       | `String`  | The SendBird application ID.           |

***

## Is Connected

Checks whether the user is currently connected to SendBird.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.SendBird.sendbirdIsConnected({
  callback: function (response) {
    console.log(response.connected); // true or false
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { sendbirdIsConnected } from "webtonative/build/SendBird";

sendbirdIsConnected({
  callback: (response) => {
    console.log(response.connected); // true or false
  },
});
```

{% endtab %}
{% endtabs %}

**Callback Response:**

| Key         | Type      | Description                                |
| ----------- | --------- | ------------------------------------------ |
| `connected` | `Boolean` | Whether the user is connected to SendBird. |

***

## Get User ID

Retrieves the currently connected user's ID.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.SendBird.sendbirdGetUserId({
  callback: function (response) {
    console.log(response.userId);
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { sendbirdGetUserId } from "webtonative/build/SendBird";

sendbirdGetUserId({
  callback: (response) => {
    console.log(response.userId);
  },
});
```

{% endtab %}
{% endtabs %}

**Callback Response:**

| Key      | Type     | Description                          |
| -------- | -------- | ------------------------------------ |
| `userId` | `String` | The current user's SendBird user ID. |

***

## Update User Info

Updates the current user's nickname and/or profile image URL.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.SendBird.sendbirdUpdateUserInfo({
  nickname: "New Name",
  profileurl: "https://example.com/new-avatar.png",
  callback: function (response) {
    console.log(response.success);
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { sendbirdUpdateUserInfo } from "webtonative/build/SendBird";

sendbirdUpdateUserInfo({
  nickname: "New Name",
  profileurl: "https://example.com/new-avatar.png",
  callback: (response) => {
    console.log(response.success);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key          | Type       | Required | Description                                  |
| ------------ | ---------- | -------- | -------------------------------------------- |
| `nickname`   | `String`   | No       | The new display name for the user.           |
| `profileurl` | `String`   | No       | The new profile image URL for the user.      |
| `callback`   | `Function` | No       | Callback function invoked with the response. |

**Callback Response:**

| Key       | Type      | Description                        |
| --------- | --------- | ---------------------------------- |
| `success` | `Boolean` | Whether the user info was updated. |

***

## Create Group Channel

Creates a new group channel with the specified users.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.SendBird.sendbirdCreateGroupChannel({
  name: "My Channel",
  userIds: ["user1", "user2", "user3"],
  isDistinct: true,
  callback: function (response) {
    console.log(response.channelUrl);
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { sendbirdCreateGroupChannel } from "webtonative/build/SendBird";

sendbirdCreateGroupChannel({
  name: "My Channel",
  userIds: ["user1", "user2", "user3"],
  isDistinct: true,
  callback: (response) => {
    console.log(response.channelUrl);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key          | Type       | Required | Description                                                                                |
| ------------ | ---------- | -------- | ------------------------------------------------------------------------------------------ |
| `name`       | `String`   | No       | The name of the group channel.                                                             |
| `userIds`    | `String[]` | No       | An array of user IDs to add to the channel.                                                |
| `isDistinct` | `Boolean`  | No       | If `true`, reuses an existing channel with the same members instead of creating a new one. |
| `callback`   | `Function` | No       | Callback function invoked with the response.                                               |

**Callback Response:**

| Key          | Type      | Description                                   |
| ------------ | --------- | --------------------------------------------- |
| `success`    | `Boolean` | Whether the channel was created successfully. |
| `channelUrl` | `String`  | The URL of the created group channel.         |

***

## Show UI

Opens the full SendBird chat UI showing the channel list.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.SendBird.sendbirdShowUI({
  callback: function (response) {
    console.log(response.status);
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { sendbirdShowUI } from "webtonative/build/SendBird";

sendbirdShowUI({
  callback: (response) => {
    console.log(response.status);
  },
});
```

{% endtab %}
{% endtabs %}

**Callback Response:**

| Key       | Type      | Description                            |
| --------- | --------- | -------------------------------------- |
| `success` | `Boolean` | Whether the UI was shown successfully. |
| `status`  | `String`  | Status of the UI display operation.    |

***

## Show Channel UI

Opens the SendBird chat UI for a specific channel.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.SendBird.sendbirdShowChannelUI({
  url: "sendbird_group_channel_123",
  callback: function (response) {
    console.log(response.status);
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { sendbirdShowChannelUI } from "webtonative/build/SendBird";

sendbirdShowChannelUI({
  url: "sendbird_group_channel_123",
  callback: (response) => {
    console.log(response.status);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                  |
| ---------- | ---------- | -------- | -------------------------------------------- |
| `url`      | `String`   | No       | The channel URL to open.                     |
| `callback` | `Function` | No       | Callback function invoked with the response. |

**Callback Response:**

| Key          | Type      | Description                                    |
| ------------ | --------- | ---------------------------------------------- |
| `success`    | `Boolean` | Whether the channel UI was shown successfully. |
| `status`     | `String`  | Status of the UI display operation.            |
| `channelUrl` | `String`  | The URL of the displayed channel.              |

***

## Disconnect

Disconnects the current user from SendBird. The user can reconnect later using `sendbirdInitialize`.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.SendBird.sendbirdDisconnect({
  callback: function (response) {
    console.log(response.success);
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { sendbirdDisconnect } from "webtonative/build/SendBird";

sendbirdDisconnect({
  callback: (response) => {
    console.log(response.success);
  },
});
```

{% endtab %}
{% endtabs %}

**Callback Response:**

| Key       | Type      | Description                            |
| --------- | --------- | -------------------------------------- |
| `success` | `Boolean` | Whether the disconnect was successful. |

***

## Logout

Logs out the current user from SendBird and cleans up all resources including push notification tokens.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.SendBird.sendbirdLogout({
  callback: function (response) {
    console.log(response.success);
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { sendbirdLogout } from "webtonative/build/SendBird";

sendbirdLogout({
  callback: (response) => {
    console.log(response.success);
  },
});
```

{% endtab %}
{% endtabs %}

**Callback Response:**

| Key       | Type      | Description                        |
| --------- | --------- | ---------------------------------- |
| `success` | `Boolean` | Whether the logout was successful. |

Logout differs from disconnect in that it also unregisters the device's push notification token and clears all stored SendBird data. Use `sendbirdDisconnect` for temporary disconnections and `sendbirdLogout` when the user is signing out of your app.

Feature taken live on 30/03/26


# Unified User Session API

Manage unified user sessions using the WebToNative JavaScript API. Synchronize authentication and user data across Android and iOS apps.

Functions to manage user sessions in the app. User data is stored securely on the device (Keychain on iOS, EncryptedSharedPreferences on Android) and can be used to personalize native UI components using template variables like `{{user.name}}`.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

## Login

Creates a new user session with the provided user data. Clears any existing session before creating the new one. Automatically syncs the user ID, email, and phone number with OneSignal for push notifications.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.User.login({
  userId: "user_123",
  name: "John Doe",
  email: "john@example.com",
  phone: "+1234567890",
  avatar: "https://example.com/avatar.png",
  token: "auth_token_here",
  plan: "premium",
  role: "admin",
  group: "team_a",
  language: "en",
  meta: {
    storeId: "store_456",
    companyName: "Acme Inc",
    department: "engineering"
  },
  callback: function (response) {
    console.log(response.success);
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { login } from "webtonative/User";

login({
  userId: "user_123",
  name: "John Doe",
  email: "john@example.com",
  phone: "+1234567890",
  avatar: "https://example.com/avatar.png",
  token: "auth_token_here",
  plan: "premium",
  role: "admin",
  group: "team_a",
  language: "en",
  meta: {
    storeId: "store_456",
    companyName: "Acme Inc",
    department: "engineering"
  },
  callback: (response) => {
    console.log(response.success);
  },
});
```

{% endtab %}
{% endtabs %}

### Pre-defined Fields

These fields are pre-defined because native features are hardcoded to read from them. Each field has a specific purpose in the platform.

| Field      | Type     | Required | Consumed By          | Purpose                                         |
| ---------- | -------- | -------- | -------------------- | ----------------------------------------------- |
| `userId`   | `String` | Yes      | All features         | Primary user identifier across all services.    |
| `name`     | `String` | No       | Side Menu, Native UI | Display name in native UI components.           |
| `email`    | `String` | No       | Push, Analytics      | Push notification targeting, analytics ID.      |
| `phone`    | `String` | No       | Push, Analytics      | SMS push, analytics identification.             |
| `avatar`   | `URL`    | No       | Side Menu, Native UI | Profile image in native UI components.          |
| `token`    | `String` | No       | API Proxy            | Auth token for authenticated feature API calls. |
| `plan`     | `String` | No       | Segmentation         | User plan for targeted rollouts, segmentation.  |
| `role`     | `String` | No       | Features, Access     | User role for content/access control.           |
| `group`    | `String` | No       | Push                 | User group for targeting and segmentation.      |
| `language` | `String` | No       | Features, Content    | Preferred language for localized content.       |

### Meta Object

The `meta` object accepts any key-value pairs for data that doesn't fit the pre-defined fields. It is stored alongside fixed fields and accessible to features via the `{{user.meta.*}}` namespace.

```javascript
meta: {
  storeId: "store_456",
  companyName: "Acme Inc",
  department: "engineering",
  referralCode: "REF123",
  subscriptionTier: "annual",
  // any custom key-value pairs
}
```

| Key        | Type       | Required | Description                                  |
| ---------- | ---------- | -------- | -------------------------------------------- |
| `callback` | `Function` | No       | Callback function invoked with the response. |

**Callback Response:**

| Key       | Type      | Description                                     |
| --------- | --------- | ----------------------------------------------- |
| `type`    | `String`  | Always `"unifiedLogin"`.                        |
| `success` | `Boolean` | `true` if the session was created successfully. |
| `error`   | `String`  | Error message if `success` is `false`.          |

***

## Set User Info

Updates the current user session with new or modified fields. The user must be logged in first — calling this without an active session will return an error. For the `meta` field, new keys are merged into existing meta data (existing keys are preserved unless overwritten).

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.User.setUserInfo({
  name: "Jane Doe",
  email: "jane@example.com",
  avatar: "https://example.com/new-avatar.png",
  plan: "enterprise",
  meta: {
    department: "sales"
  },
  callback: function (response) {
    console.log(response.success);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { setUserInfo } from "webtonative/User";

setUserInfo({
  name: "Jane Doe",
  email: "jane@example.com",
  avatar: "https://example.com/new-avatar.png",
  plan: "enterprise",
  meta: {
    department: "sales"
  },
  callback: (response) => {
    console.log(response.success);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

Any of the [pre-defined fields](#pre-defined-fields) and the `meta` object can be passed to update the session. If `userId`, `email`, or `phone` are updated, they are also synced with OneSignal.

| Key        | Type       | Required | Description                                  |
| ---------- | ---------- | -------- | -------------------------------------------- |
| `callback` | `Function` | No       | Callback function invoked with the response. |

**Callback Response:**

| Key       | Type      | Description                                                       |
| --------- | --------- | ----------------------------------------------------------------- |
| `type`    | `String`  | Always `"setUnifiedUserInfo"`.                                    |
| `success` | `Boolean` | `true` if the user info was updated successfully.                 |
| `error`   | `String`  | Error message if `success` is `false` (e.g., user not logged in). |

***

## Get User Info

Retrieves the current user session data. The `token` field is excluded from the response for security.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.User.getUserInfo({
  callback: function (response) {
    console.log(response.userId);
    console.log(response.name);
    console.log(response.avatar);
    console.log(response.plan);
    console.log(response.meta);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { getUserInfo } from "webtonative/User";

getUserInfo({
  callback: (response) => {
    console.log(response.userId);
    console.log(response.name);
    console.log(response.avatar);
    console.log(response.plan);
    console.log(response.meta);
  },
});
```

{% endtab %}
{% endtabs %}

**Callback Response:**

| Key              | Type     | Description                                          |
| ---------------- | -------- | ---------------------------------------------------- |
| `type`           | `String` | Always `"getUnifiedUserInfo"`.                       |
| `sessionVersion` | `Number` | The session version number.                          |
| `userId`         | `String` | The user's unique identifier.                        |
| `name`           | `String` | The user's display name.                             |
| `email`          | `String` | The user's email address.                            |
| `phone`          | `String` | The user's phone number.                             |
| `avatar`         | `String` | The user's profile image URL.                        |
| `plan`           | `String` | The user's plan.                                     |
| `role`           | `String` | The user's role.                                     |
| `group`          | `String` | The user's group.                                    |
| `language`       | `String` | The user's preferred language.                       |
| `meta`           | `Object` | Custom key-value pairs stored in the session.        |
| `loggedInAt`     | `Number` | Unix timestamp of when the session was created.      |
| `lastUpdated`    | `Number` | Unix timestamp of when the session was last updated. |

The response includes all fields that were stored during `login` and `setUserInfo`, except `token`.

***

## Logout

Clears the current user session and removes all stored user data. Also removes the external user ID from OneSignal and reloads the webview.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.User.logout();
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { logout } from "webtonative/User";

logout();
```

{% endtab %}
{% endtabs %}

This function does not accept any parameters or return a callback. The webview is automatically reloaded after logout.

***

## Using User Data in Native Components

User session data can be referenced in native UI components (bottom navigation labels, side navigation, top app bar, etc.) using template variables:

| Template                                 | Description                                    |
| ---------------------------------------- | ---------------------------------------------- |
| `{{user.name}}`                          | Replaced with the user's name.                 |
| `{{user.email}}`                         | Replaced with the user's email.                |
| `{{user.avatar}}`                        | Replaced with the user's profile image URL.    |
| `{{user.plan}}`                          | Replaced with the user's plan.                 |
| `{{user.role}}`                          | Replaced with the user's role.                 |
| `{{user.meta.storeId}}`                  | Replaced with the `storeId` value from meta.   |
| `{{user.name \| 'Guest'}}`               | Uses `"Guest"` as fallback if name is not set. |
| `{{user.meta.companyName \| 'Unknown'}}` | Uses `"Unknown"` as fallback if not set.       |

Template variables with fallback values use the syntax `{{user.field | 'default'}}`. If the field is not set or empty, the fallback value is used instead.


# Truecaller JavaScript API

Integrate Truecaller login using the WebToNative JavaScript API. Enable fast, secure phone number authentication in Android applications.

Function to integrate Truecaller authentication into your app. The WebToNative Truecaller plugin integrates the Truecaller Android and iOS SDKs natively, giving you a single JavaScript API that works across both platforms while preserving the platform-specific verification model required by Truecaller.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

## How It Works

Truecaller uses two different verification flows depending on the operating system. The WebToNative plugin abstracts the SDK call into a single JavaScript function, but the data returned to your callback — and therefore the work your backend must do — depends on the platform the user is on.

| Platform | Flow                | What the SDK Returns                                                      | Backend Responsibility                                                                |
| -------- | ------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| Android  | OAuth 2.0 with PKCE | `authorizationCode`, `codeVerifier`, `requestNonce`                       | Exchange the authorization code using the PKCE verifier, then fetch the user profile. |
| iOS      | Legacy JWT delegate | Signed JWT (in `authorizationCode`), empty `codeVerifier`, `requestNonce` | Verify the JWT signature, decode the claims, and extract the user profile.            |

Always inspect `data.platform` in the callback response to decide which verification path to run on your backend.

***

## Setting Up Your Truecaller Account

WebToNative leverages Truecaller's developer platform to power phone-number based login within your app. To get started, register at the [Truecaller Developer Portal](https://developer.truecaller.com/).

{% stepper %}
{% step %}

### Log in to the Truecaller Developer Portal

Log in to the [Truecaller Developer Portal](https://developer.truecaller.com/).
{% endstep %}

{% step %}

### Create a new application

Create a new application and select **Mobile** as the application type.
{% endstep %}

{% step %}

### Add platform details

Add the following details for each platform:

**For Android:**

| Field          | Where to Find It                                             |
| -------------- | ------------------------------------------------------------ |
| `Package Name` | WebToNative dashboard → App Info → Package Name.             |
| `SHA-1`        | The release SHA-1 of the signing keystore used for your app. |

**For iOS:**

| Field         | Where to Find It                                           |
| ------------- | ---------------------------------------------------------- |
| `Bundle ID`   | The Bundle ID you created in App Store Connect (iOS only). |
| `App Name`    | The display name of your app as listed on the App Store.   |
| {% endstep %} |                                                            |

{% step %}

### Copy your credentials

Once approved, copy your **Client ID** (Android) and **App Key** (iOS) from the Truecaller dashboard.
{% endstep %}

{% step %}

### Add credentials in WebToNative

Go to your **WebToNative dashboard** → **Add-ons** → **Truecaller** and enter:

| Field               | Description                                               |
| ------------------- | --------------------------------------------------------- |
| `Android Client ID` | Paste the Client ID copied from the Truecaller dashboard. |
| `iOS App Key`       | Paste the App Key copied from the Truecaller dashboard.   |
| `iOS App Link`      | Paste the App Link copied from the Truecaller Dashboard.  |
| {% endstep %}       |                                                           |
| {% endstepper %}    |                                                           |

***

## Truecaller Login

Opens the Truecaller consent screen. On success, returns the credentials your backend needs to fetch the verified user profile.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Truecaller.truecallerLogin({
  callback: function (response) {
    if (response.success) {
      console.log(response.data.platform);
      console.log(response.data.authorizationCode);
      console.log(response.data.codeVerifier);
      console.log(response.data.requestNonce);
    } else {
      console.error(response.error.code);
      console.error(response.error.message);
    }
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { truecallerLogin } from "webtonative/Truecaller";

truecallerLogin({
  callback: (response) => {
    if (response.success) {
      console.log(response.data.platform);
      console.log(response.data.authorizationCode);
      console.log(response.data.codeVerifier);
      console.log(response.data.requestNonce);
    } else {
      console.error(response.error.code);
      console.error(response.error.message);
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                  |
| ---------- | ---------- | -------- | -------------------------------------------- |
| `callback` | `Function` | No       | Callback function invoked with the response. |

**Callback Response:**

| Key       | Type      | Description                                                    |
| --------- | --------- | -------------------------------------------------------------- |
| `type`    | `String`  | Always `"truecallerLogin"`. Use this to filter the callback.   |
| `success` | `Boolean` | `true` if the login flow completed, `false` if it failed.      |
| `data`    | `Object`  | Present when `success` is `true`. See **Data Object** below.   |
| `error`   | `Object`  | Present when `success` is `false`. See **Error Object** below. |

**Data Object** (returned on success):

| Key                 | Type     | Description                                                                                                 |
| ------------------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| `authorizationCode` | `String` | On Android, the OAuth authorization code. On iOS, the signed JWT token issued by Truecaller.                |
| `codeVerifier`      | `String` | On Android, the PKCE verifier required to exchange the authorization code. On iOS, this is an empty string. |
| `requestNonce`      | `String` | A unique nonce generated for this request. Send to your backend to prevent replay attacks.                  |
| `platform`          | `String` | Either `"android"` or `"ios"`. Use this to choose the correct backend verification path.                    |

**Error Object** (returned on failure):

| Key       | Type     | Description                                       |
| --------- | -------- | ------------------------------------------------- |
| `code`    | `String` | Machine-readable error code. See the table below. |
| `message` | `String` | Human-readable error description.                 |

**Error Codes:**

| Error Code                 | Description                        |
| -------------------------- | ---------------------------------- |
| `USER_CANCELLED`           | User cancelled or closed the flow. |
| `TRUECALLER_NOT_INSTALLED` | Truecaller app is unavailable.     |
| `SDK_INTERNAL_ERROR`       | Internal SDK failure.              |

***

## Example Responses

### Android Success Response

```json
{
  "type": "truecallerLogin",
  "success": true,
  "data": {
    "authorizationCode": "tc_oauth_code_abc123",
    "codeVerifier": "pkce_verifier_xyz",
    "requestNonce": "nonce_def456",
    "platform": "android"
  }
}
```

### iOS Success Response

```json
{
  "type": "truecallerLogin",
  "success": true,
  "data": {
    "authorizationCode": "eyJhbGciOiJSUzUxMiJ9...",
    "codeVerifier": "",
    "requestNonce": "nonce_ghi789",
    "platform": "ios"
  }
}
```

### Error Response

```json
{
  "type": "truecallerLogin",
  "success": false,
  "error": {
    "code": "USER_CANCELLED",
    "message": "User cancelled the request"
  }
}
```

***

## iOS Callback Limitation

Unlike Android, iOS does not always emit a cancellation callback. This is a limitation of the current legacy TrueSDK and is **not** something the WebToNative plugin can work around.

No callback is received when:

* The user closes Truecaller manually.
* The user dismisses the login flow.
* The user switches away before the redirect completes.
* The universal-link callback never fires.

### Recommended Handling

Wrap the call in a frontend timeout so your UI never gets stuck waiting on a response that will never come.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
let completed = false;

const timeout = setTimeout(() => {
  if (!completed) {
    console.log({
      type: "truecallerLogin",
      success: false,
      error: {
        code: "USER_CANCELLED",
        message: "User cancelled the request",
      },
    });
  }
}, 10000);

window.WTN.Truecaller.truecallerLogin({
  callback: function (response) {
    completed = true;
    clearTimeout(timeout);
    console.log(response);
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { truecallerLogin } from "webtonative/Truecaller";

let completed = false;

const timeout = setTimeout(() => {
  if (!completed) {
    console.log({
      type: "truecallerLogin",
      success: false,
      error: {
        code: "USER_CANCELLED",
        message: "User cancelled the request",
      },
    });
  }
}, 10000);

truecallerLogin({
  callback: (response) => {
    completed = true;
    clearTimeout(timeout);
    console.log(response);
  },
});
```

{% endtab %}
{% endtabs %}

***

## Backend Verification

The credentials returned by the SDK are not user identities — they are proof that the user completed the Truecaller flow. The verified profile is only available after your backend exchanges or decodes these credentials with Truecaller.

Always branch your backend logic on the `platform` field:

```javascript
if (value.data.platform === "android") {
  // OAuth + PKCE flow
} else if (value.data.platform === "ios") {
  // JWT verification flow
}
```

### Android Verification Flow

```
Authorization Code
        │
        ▼
Exchange with PKCE verifier
        │
        ▼
  Access Token
        │
        ▼
 Fetch User Profile
```

1. POST the `authorizationCode` and `codeVerifier` to Truecaller's token endpoint.
2. Receive an access token in exchange.
3. Call Truecaller's profile API with the access token to fetch the verified user.

### iOS Verification Flow

```
Signed JWT
    │
    ▼
Verify Signature
    │
    ▼
Decode Claims
    │
    ▼
Extract User Profile
```

1. Receive the signed JWT from `data.authorizationCode`.
2. Verify the JWT signature against Truecaller's public keys.
3. Decode the claims to extract the verified phone number and profile fields.

***

## Complete Example

A full implementation that handles both platforms, errors, and iOS cancellations:

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
let completed = false;

const timeout = setTimeout(() => {
  if (!completed) {
    handleResponse({
      type: "truecallerLogin",
      success: false,
      error: {
        code: "USER_CANCELLED",
        message: "User cancelled the request",
      },
    });
  }
}, 10000);

window.WTN.Truecaller.truecallerLogin({
  callback: function (response) {
    completed = true;
    clearTimeout(timeout);
    handleResponse(response);
  },
});

function handleResponse(value) {
  if (value.type !== "truecallerLogin") return;

  if (!value.success) {
    console.error(value.error.code, value.error.message);
    return;
  }

  console.log("Platform:", value.data.platform);
  console.log("Authorization Code:", value.data.authorizationCode);
  console.log("Code Verifier:", value.data.codeVerifier);
  console.log("Request Nonce:", value.data.requestNonce);

  // Send the credentials to your backend for verification
  sendToBackend(value.data);
}
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { truecallerLogin } from "webtonative/Truecaller";

let completed = false;

const timeout = setTimeout(() => {
  if (!completed) {
    handleResponse({
      type: "truecallerLogin",
      success: false,
      error: {
        code: "USER_CANCELLED",
        message: "User cancelled the request",
      },
    });
  }
}, 10000);

truecallerLogin({
  callback: (response) => {
    completed = true;
    clearTimeout(timeout);
    handleResponse(response);
  },
});

const handleResponse = (value) => {
  if (value.type !== "truecallerLogin") return;

  if (!value.success) {
    console.error(value.error.code, value.error.message);
    return;
  }

  console.log("Platform:", value.data.platform);
  console.log("Authorization Code:", value.data.authorizationCode);
  console.log("Code Verifier:", value.data.codeVerifier);
  console.log("Request Nonce:", value.data.requestNonce);

  sendToBackend(value.data);
};
```

{% endtab %}
{% endtabs %}

***

## Best Practices

* **Always verify on the backend.** Treat anything returned to the frontend as untrusted until your server has exchanged or verified it with Truecaller.
* **Never trust frontend profile data.** The phone number and identity are only authoritative after backend verification.
* **Store the Android PKCE verifier securely.** It must accompany the authorization code on the server-side exchange request.
* **Verify the JWT signature on iOS.** Decoding without verification leaves you open to forged tokens.
* **Use the request nonce.** Pass it through to your backend and reject any verification response whose nonce does not match the one you issued — this prevents replay attacks.
* **Branch on `platform`.** Do not assume one flow on the server; the credentials look superficially similar but require different verification.
* **Apply an iOS timeout.** Treat the missing-callback case as a cancellation so your UI never hangs.

***

## Integration Checklist

Use this checklist before shipping to make sure the integration is wired up end-to-end.

**Truecaller portal**

* [ ] Application registered on the [Truecaller Developer Portal](https://developer.truecaller.com/).
* [ ] Android package name and release SHA-1 added.
* [ ] iOS bundle ID and app name added.
* [ ] Android Client ID and iOS App Key copied.

**WebToNative dashboard**

* [ ] Truecaller add-on enabled.
* [ ] Android Client ID entered.
* [ ] iOS App Key entered.
* [ ] Build regenerated after saving credentials.

**Frontend**

* [ ] WebToNative JS file imported on the page that triggers login.
* [ ] `window.WTN.Truecaller.truecallerLogin` called only on `ANDROID_APP` / `IOS_APP` platforms.
* [ ] Callback handles both `success: true` and `success: false` paths.
* [ ] Callback filters by `response.type === "truecallerLogin"`.
* [ ] iOS cancellation timeout implemented.

**Backend**

* [ ] `data.platform` is checked before choosing a verification path.
* [ ] Android: authorization code exchanged with PKCE verifier, then profile fetched.
* [ ] iOS: JWT signature verified against Truecaller's public keys before decoding claims.
* [ ] `requestNonce` validated against the nonce expected for that request.
* [ ] User session issued only after successful backend verification.

***

## FAQs

<details>

<summary>Why does `codeVerifier` come back empty on iOS?</summary>

iOS uses the legacy JWT delegate flow, which does not use PKCE. The field is included in the response so the frontend shape stays identical across platforms, but it is intentionally empty on iOS.

</details>

<details>

<summary>Do I need to call a different function for Android and iOS?</summary>

No. `window.WTN.Truecaller.truecallerLogin` is the single entry point. Branching happens on the backend based on `data.platform`.

</details>

<details>

<summary>What happens if Truecaller is not installed on the device?</summary>

The callback fires with `success: false` and `error.code: "TRUECALLER_NOT_INSTALLED"`. Fall back to your existing phone-number login flow (e.g. OTP) in this case.

</details>

<details>

<summary>Why didn't I receive a callback on iOS when the user closed the prompt?</summary>

This is a known limitation of the legacy TrueSDK on iOS — the SDK does not always emit a cancellation event. Implement the timeout pattern from the **iOS Callback Limitation** section to recover gracefully.

</details>

<details>

<summary>Can I receive the user's phone number directly in the frontend callback?</summary>

No. The frontend only receives credentials that prove the user completed the flow. The verified phone number and profile are returned by Truecaller's backend APIs after your server completes the exchange or JWT verification.

</details>

<details>

<summary>Why is `requestNonce` important?</summary>

It binds a specific verification response to a specific request. Storing the nonce on your server when you initiate the flow, and checking it again when verification completes, prevents an attacker from replaying a previously captured Truecaller response.

</details>

<details>

<summary>Does Truecaller login work on the web?</summary>

No. The plugin only activates inside the Android and iOS WebToNative shells. The call is a no-op in a regular browser.

</details>

***

## Official References

* [Truecaller Developer Portal](https://developer.truecaller.com/)
* [Truecaller SDK Documentation](https://docs.truecaller.com/truecaller-sdk)
* [Android OAuth SDK 3.0 — Setup](https://docs.truecaller.com/truecaller-sdk/android/oauth-sdk-3.0.0/integration-steps/setup)
* [iOS SDK — Integration Guide](https://docs.truecaller.com/truecaller-sdk/ios/integrating-with-your-ios-app)


# Auth0 JavaScript API

Integrate Auth0 authentication using the WebToNative JavaScript API. Enable secure user login and identity management for Android and iOS apps.

Secure, native authentication for your app — powered by Auth0 Universal Login.

[Auth0](https://auth0.com/) is a widely used authentication and authorization platform that handles login, signup, multi-factor authentication, and social login out of the box. Instead of building and maintaining your own authentication system, Auth0 lets you offload all of that complexity to a battle-tested service.

WebToNative's Auth0 plugin integrates the official [Auth0 iOS SDK](https://github.com/auth0/Auth0.swift) and [Auth0 Android SDK](https://github.com/auth0/Auth0.Android) directly into your app. This means your users get a **native login experience** — including Universal Login, biometric authentication (Face ID, Touch ID, Fingerprint), and automatic session management with refresh tokens — all without leaving your app.

### Why use native Auth0 instead of web-based login?

Mobile security best practices (and policies from Google, Apple, and the [IETF](https://datatracker.ietf.org/doc/html/rfc8252)) require that user authentication happen in a secure browser session facilitated by a native app — not inside an embedded WebView. Auth0 Universal Login satisfies this requirement. WebToNative's plugin handles the entire native flow so you don't have to.

{% hint style="info" %}
**Prerequisites:** Import the WebToNative JavaScript bridge into your website before using any of the functions below. See the [Getting Started](https://docs.webtonative.com/javascript-apis/getting-started) guide.
{% endhint %}

***

## Step 1 — Configure Your Auth0 Account

Before enabling the plugin in WebToNative, you need to set up a Native Application in your Auth0 dashboard.

{% hint style="info" %}
**Note:** The steps below demonstrate a typical configuration. Auth0 is highly customizable — consult the [Auth0 Native App Quickstart Guide](https://auth0.com/docs/quickstart/native) and your Auth0 team for production-ready settings.
{% endhint %}

### 1.1 Create a Native Application

1. Log in to the [Auth0 Dashboard](https://manage.auth0.com/).
2. Navigate to **Applications → Create Application**.
3. Select **Native** as the application type and click **Create**.

### 1.2 Configure Callback and Logout URLs

Under **Settings → Application URIs**, add the following to both **Allowed Callback URLs** and **Allowed Logout URLs**:

**Android callback URL format:**

```
YOUR_SCHEME://YOUR_AUTH0_DOMAIN/android/YOUR_PACKAGE_NAME/callback
```

**iOS callback URL format:**

```
YOUR_SCHEME://YOUR_AUTH0_DOMAIN/ios/YOUR_BUNDLE_ID/callback
```

Here's where each placeholder value comes from:

| Placeholder         | Where to find it                                                        |
| ------------------- | ----------------------------------------------------------------------- |
| `YOUR_AUTH0_DOMAIN` | Auth0 Dashboard → Application → Settings → Domain                       |
| `YOUR_PACKAGE_NAME` | WebToNative Dashboard → App Info → Package Name                         |
| `YOUR_BUNDLE_ID`    | The Bundle ID you created in App Store Connect (must match WebToNative) |
| `YOUR_SCHEME`       | The URL scheme you'll enter in WebToNative's Auth0 plugin settings      |

**Example** (using sample values):

| Key             | Value                       |
| --------------- | --------------------------- |
| Auth0 Domain    | `dev-abc123.us.auth0.com`   |
| Android Package | `com.example.android.myapp` |
| iOS Bundle ID   | `com.example.ios.myapp`     |
| Scheme          | `myapp`                     |

This would result in callback URLs:

```
myapp://dev-abc123.us.auth0.com/android/com.example.android.myapp/callback
myapp://dev-abc123.us.auth0.com/ios/com.example.ios.myapp/callback
```

Add these to **both** the Allowed Callback URLs and Allowed Logout URLs fields (comma-separated).

### 1.3 Enable Refresh Tokens

Go to **Advanced Settings → OAuth** and turn on **Allow Offline Access**. This is required for refresh tokens to work, which in turn powers auto-login and biometric re-authentication.

### 1.4 Configure Device Settings

Under **Advanced Settings → Device Settings**, fill in your iOS Team ID and App ID, as well as your Android Package Name and Key Hashes. This ensures Auth0 can verify your app's identity on each platform.

***

## Step 2 — Configure the Plugin in WebToNative

1. Open your **WebToNative Dashboard → Add-ons → Auth0**.
2. Enter the following values:

| Field       | Description                                                        |
| ----------- | ------------------------------------------------------------------ |
| `Domain`    | Your Auth0 tenant domain (e.g. `dev-abc123.us.auth0.com`)          |
| `Client ID` | The Client ID from your Auth0 Application Settings                 |
| `Scheme`    | The URL scheme used in your callback URLs (e.g. `myapp`)           |
| `Audience`  | *(Optional)* Your Auth0 API audience, if you're using a custom API |

3. Configure **Deep Linking** for your Auth0 domain so that callback redirects are routed back to your app after the user completes Universal Login. Without this, the login flow will complete in the browser but the tokens won't be delivered back to your app.
   * **iOS:** Set up Universal Links by hosting a `/.well-known/apple-app-site-association` file on your Auth0 domain (or use the custom URL scheme configured above). See [Deep Linking](https://www.webtonative.com/support/linkhandling/deeplinking) for setup instructions.
   * **Android:** Set up App Links by hosting a `/.well-known/assetlinks.json` file on your Auth0 domain, or rely on the custom URL scheme. See [Deep Linking](https://www.webtonative.com/support/linkhandling/deeplinking) for details.
4. Configure the **URL Scheme Protocol** in your WebToNative dashboard to match the scheme you entered above (e.g. `myapp`). This is the custom scheme Auth0 uses to redirect back to your app (e.g. `myapp://dev-abc123.us.auth0.com/...`). See [URL Scheme Protocol](https://www.webtonative.com/support/linkhandling/urlscheme).
5. Verify that your Auth0 domain is treated as an **external link** in your app's link handling configuration, so that Auth0 login pages open in a secure browser session rather than the app's WebView. See [Internal vs External Linking](https://www.webtonative.com/support/linkhandling/internalvsexternal).

***

## JavaScript API Reference

### Login

Opens the Auth0 Universal Login screen. On success, returns OAuth tokens. Optionally stores credentials with biometric protection for seamless future logins.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.Auth0.login({
  scope: "openid profile email offline_access",
  enableBiometrics: true,
  callback: function (response) {
    if (response.error) {
      console.error("Login failed:", response.error);
      return;
    }
    console.log("Access Token:", response.accessToken);
    console.log("ID Token:", response.idToken);
    console.log("Refresh Token:", response.refreshToken);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { login } from "webtonative/Auth0";

login({
  scope: "openid profile email offline_access",
  enableBiometrics: true,
  callback: (response) => {
    if (response.error) {
      console.error("Login failed:", response.error);
      return;
    }
    console.log("Access Token:", response.accessToken);
    console.log("ID Token:", response.idToken);
    console.log("Refresh Token:", response.refreshToken);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key                | Type       | Required | Description                                                                                      |
| ------------------ | ---------- | -------- | ------------------------------------------------------------------------------------------------ |
| `scope`            | `String`   | No       | OAuth scopes to request. Include `offline_access` to receive a refresh token.                    |
| `enableBiometrics` | `Boolean`  | No       | If `true`, saves credentials to device secure storage with biometric protection (Face ID, etc.). |
| `callback`         | `Function` | No       | Function invoked with the login response.                                                        |

**Response:**

| Key            | Type     | Description                                             |
| -------------- | -------- | ------------------------------------------------------- |
| `accessToken`  | `String` | The OAuth access token.                                 |
| `idToken`      | `String` | The OpenID Connect ID token.                            |
| `refreshToken` | `String` | The refresh token (requires `offline_access` in scope). |
| `scope`        | `String` | The granted scopes.                                     |
| `error`        | `String` | Error message, present only if login failed.            |

***

### Logout

Clears saved credentials from device secure storage and ends the Auth0 session.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.Auth0.logout({
  callback: function (response) {
    if (response.error) {
      console.error("Logout failed:", response.error);
      return;
    }
    console.log("Logged out successfully");
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { logout } from "webtonative/Auth0";

logout({
  callback: (response) => {
    if (response.error) {
      console.error("Logout failed:", response.error);
      return;
    }
    console.log("Logged out successfully");
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                |
| ---------- | ---------- | -------- | ------------------------------------------ |
| `callback` | `Function` | No       | Function invoked with the logout response. |

**Response:**

| Key       | Type      | Description                            |
| --------- | --------- | -------------------------------------- |
| `success` | `Boolean` | `true` if logout was successful.       |
| `error`   | `String`  | Error message, present only if failed. |

***

### Get Status

Checks whether the user has a valid saved session and whether biometric authentication is available on the device. Use this to decide whether to show a login screen or attempt auto-login with biometrics.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.Auth0.getStatus({
  callback: function (response) {
    if (response.hasValidCredentials) {
      // User has a saved session — attempt getCredentials()
    } else {
      // No saved session — show login screen
    }
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { getStatus } from "webtonative/Auth0";

getStatus({
  callback: (response) => {
    if (response.hasValidCredentials) {
      // User has a saved session — attempt getCredentials()
    } else {
      // No saved session — show login screen
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                |
| ---------- | ---------- | -------- | ------------------------------------------ |
| `callback` | `Function` | No       | Function invoked with the status response. |

**Response:**

| Key                   | Type      | Description                                                                    |
| --------------------- | --------- | ------------------------------------------------------------------------------ |
| `hasValidCredentials` | `Boolean` | `true` if the user has saved credentials and the access token has not expired. |
| `biometryAvailable`   | `Boolean` | `true` if Face ID, Touch ID, or Fingerprint authentication is available.       |
| `biometryType`        | `String`  | The type of biometric available: `"faceId"`, `"touchId"`, or `"none"`.         |

***

### Get Credentials

Retrieves saved credentials from device secure storage. If biometrics were enabled during login, the user will be prompted with Face ID or Fingerprint automatically. If the saved access token has expired, it is automatically renewed using the stored refresh token.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.Auth0.getCredentials({
  callback: function (response) {
    if (response.error) {
      console.error("No saved credentials:", response.error);
      return;
    }
    // Use response.accessToken to make authenticated API calls
    console.log("Access Token:", response.accessToken);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { getCredentials } from "webtonative/Auth0";

getCredentials({
  callback: (response) => {
    if (response.error) {
      console.error("No saved credentials:", response.error);
      return;
    }
    console.log("Access Token:", response.accessToken);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                     |
| ---------- | ---------- | -------- | ----------------------------------------------- |
| `callback` | `Function` | No       | Function invoked with the credentials response. |

**Response:**

| Key            | Type     | Description                                       |
| -------------- | -------- | ------------------------------------------------- |
| `accessToken`  | `String` | The OAuth access token (auto-renewed if expired). |
| `idToken`      | `String` | The OpenID Connect ID token.                      |
| `refreshToken` | `String` | The refresh token.                                |
| `error`        | `String` | Error message, present only if retrieval failed.  |

***

### Renew Credentials

Manually renews expired tokens using a refresh token. If no `refreshToken` parameter is provided, the plugin automatically uses the token saved from the last successful login.

> In most cases you don't need to call this directly — `getCredentials()` already handles auto-renewal. Use `renew()` only if you need explicit control over the renewal flow.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.Auth0.renew({
  callback: function (response) {
    if (response.error) {
      console.error("Renewal failed:", response.error);
      return;
    }
    console.log("Renewed Access Token:", response.accessToken);
  },
});
```

{% endtab %}

{% tab title="ES5+ Module" %}

```javascript
import { renew } from "webtonative/Auth0";

renew({
  callback: (response) => {
    if (response.error) {
      console.error("Renewal failed:", response.error);
      return;
    }
    console.log("Renewed Access Token:", response.accessToken);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key            | Type       | Required | Description                                                           |
| -------------- | ---------- | -------- | --------------------------------------------------------------------- |
| `refreshToken` | `String`   | No       | A specific refresh token to use. If omitted, the saved token is used. |
| `callback`     | `Function` | No       | Function invoked with the renewal response.                           |

**Response:**

| Key            | Type     | Description                            |
| -------------- | -------- | -------------------------------------- |
| `accessToken`  | `String` | The renewed OAuth access token.        |
| `idToken`      | `String` | The renewed OpenID Connect ID token.   |
| `refreshToken` | `String` | The renewed refresh token.             |
| `error`        | `String` | Error message, present only if failed. |

***

## Typical Implementation Flow

Here's how a typical authentication flow looks using the Auth0 plugin:

1. **App launch** — Call `getStatus()` to check if the user has saved credentials.
2. **Returning user** — If `hasValidCredentials` is `true`, call `getCredentials()`. The user is prompted with biometrics (if enabled) and receives fresh tokens automatically.
3. **New user / expired session** — If no valid credentials exist, call `login()` with `enableBiometrics: true` and `scope: "openid profile email offline_access"`. Auth0 Universal Login opens natively.
4. **Use tokens** — Send the `accessToken` in your API calls as a Bearer token in the `Authorization` header.
5. **Logout** — Call `logout()` to clear saved credentials and end the session.

***

## Implementation Checklist

### Auth0 Dashboard

* [ ] Created a new **Native** application in Auth0
* [ ] Added **Allowed Callback URLs** and **Allowed Logout URLs** for both Android and iOS
* [ ] Enabled **Allow Offline Access** under Advanced Settings → OAuth
* [ ] Filled in **Device Settings** (iOS Team ID/App ID, Android Package Name/Key Hashes)

### WebToNative Dashboard

* [ ] Entered **Domain**, **Client ID**, and **Scheme** in Add-ons → Auth0
* [ ] Configured [**URL Scheme Protocol**](https://www.webtonative.com/support/linkhandling/urlscheme) matching the scheme set in Auth0 (e.g. `myapp`)
* [ ] Configured [**Deep Linking**](https://www.webtonative.com/support/linkhandling/deeplinking) for your Auth0 domain so callback redirects return to the app
* [ ] Verified Auth0 domain is set as an [external link](https://www.webtonative.com/support/linkhandling/internalvsexternal) so login opens in a secure browser

### Your Website

* [ ] Imported the [WebToNative JavaScript bridge](https://docs.webtonative.com/javascript-apis/getting-started)
* [ ] Implemented `login()` to launch Auth0 Universal Login
* [ ] Implemented `logout()` to give users a way to sign out
* [ ] Used `getStatus()` + `getCredentials()` for seamless returning-user login
* [ ] Hosted `.well-known/assetlinks.json` (Android) and `.well-known/apple-app-site-association` (iOS) for deep linking

***

## Frequently Asked Questions

<details>

<summary>Why use the native Auth0 plugin instead of Auth0 in the WebView?</summary>

Mobile security policies from Google, Apple, and the IETF require authentication to happen in a secure browser session — not inside an embedded WebView. The WebToNative Auth0 plugin uses Auth0's official native SDKs, which satisfy these requirements and provide the best security and user experience.

</details>

<details>

<summary>Can biometrics be tested in simulators?</summary>

No. Face ID, Touch ID, and Fingerprint authentication require a physical device with biometric hardware. Use a real device for testing biometric features.

</details>

<details>

<summary>What scopes should I request?</summary>

At minimum, include `openid profile email`. Add `offline_access` if you want refresh tokens (recommended for biometric re-login and persistent sessions).

</details>

<details>

<summary>What happens if the access token expires?</summary>

If you call `getCredentials()`, expired tokens are automatically renewed using the stored refresh token. You can also call `renew()` manually if you need explicit control.

</details>

<details>

<summary>Do I need to set up deep linking?</summary>

Yes. Auth0 uses redirect-based authentication, which relies on deep links to return control to your app after the login flow completes. You'll need to configure both a [URL Scheme](https://www.webtonative.com/support/linkhandling/urlscheme) and [Deep Linking](https://www.webtonative.com/support/linkhandling/deeplinking) in your WebToNative dashboard. For iOS, this means hosting an `apple-app-site-association` file; for Android, an `assetlinks.json` file on your domain.

</details>

***

*Feature taken live on 09/04/26*


# Auth0

Secure, native authentication for your app — powered by Auth0 Universal Login.

[Auth0](https://auth0.com/) is a widely used authentication and authorization platform that handles login, signup, multi-factor authentication, and social login out of the box. Instead of building and maintaining your own authentication system, Auth0 lets you offload all of that complexity to a battle-tested service.

WebToNative's Auth0 plugin integrates the official [Auth0 iOS SDK](https://github.com/auth0/Auth0.swift) and [Auth0 Android SDK](https://github.com/auth0/Auth0.Android) directly into your app. This means your users get a **native login experience** — including Universal Login, biometric authentication (Face ID, Touch ID, Fingerprint), and automatic session management with refresh tokens — all without leaving your app.

### Why use native Auth0 instead of web-based login?

Mobile security best practices (and policies from Google, Apple, and the [IETF](https://datatracker.ietf.org/doc/html/rfc8252)) require that user authentication happen in a secure browser session facilitated by a native app — not inside an embedded WebView. Auth0 Universal Login satisfies this requirement. WebToNative's plugin handles the entire native flow so you don't have to.

{% hint style="info" %}
**Prerequisites:** Import the WebToNative JavaScript bridge into your website before using any of the functions below. See the [Getting Started](https://docs.webtonative.com/javascript-apis/getting-started) guide.
{% endhint %}

***

## Step 1 — Configure Your Auth0 Account

Before enabling the plugin in WebToNative, you need to set up a Native Application in your Auth0 dashboard.

{% hint style="info" %}
**Note:** The steps below demonstrate a typical configuration. Auth0 is highly customizable — consult the [Auth0 Native App Quickstart Guide](https://auth0.com/docs/quickstart/native) and your Auth0 team for production-ready settings.
{% endhint %}

{% stepper %}
{% step %}

#### Create a Native Application

1. Log in to the [Auth0 Dashboard](https://manage.auth0.com/).
2. Navigate to **Applications → Create Application**.
3. Select **Native** as the application type and click **Create**.
   {% endstep %}

{% step %}

#### Configure Callback and Logout URLs

Under **Settings → Application URIs**, add the following to both **Allowed Callback URLs** and **Allowed Logout URLs**:

**Android callback URL format:**

```
YOUR_SCHEME://YOUR_AUTH0_DOMAIN/android/YOUR_PACKAGE_NAME/callback
```

**iOS callback URL format:**

```
YOUR_SCHEME://YOUR_AUTH0_DOMAIN/ios/YOUR_BUNDLE_ID/callback
```

Here's where each placeholder value comes from:

| Placeholder         | Where to find it                                                        |
| ------------------- | ----------------------------------------------------------------------- |
| `YOUR_AUTH0_DOMAIN` | Auth0 Dashboard → Application → Settings → Domain                       |
| `YOUR_PACKAGE_NAME` | WebToNative Dashboard → App Info → Package Name                         |
| `YOUR_BUNDLE_ID`    | The Bundle ID you created in App Store Connect (must match WebToNative) |
| `YOUR_SCHEME`       | The URL scheme you'll enter in WebToNative's Auth0 plugin settings      |

**Example** (using sample values):

| Key             | Value                       |
| --------------- | --------------------------- |
| Auth0 Domain    | `dev-abc123.us.auth0.com`   |
| Android Package | `com.example.android.myapp` |
| iOS Bundle ID   | `com.example.ios.myapp`     |
| Scheme          | `myapp`                     |

This would result in callback URLs:

```
myapp://dev-abc123.us.auth0.com/android/com.example.android.myapp/callback
myapp://dev-abc123.us.auth0.com/ios/com.example.ios.myapp/callback
```

Add these to **both** the Allowed Callback URLs and Allowed Logout URLs fields (comma-separated).
{% endstep %}

{% step %}

#### Enable Refresh Tokens

Go to **Advanced Settings → OAuth** and turn on **Allow Offline Access**. This is required for refresh tokens to work, which in turn powers auto-login and biometric re-authentication.
{% endstep %}

{% step %}

#### Configure Device Settings

Under **Advanced Settings → Device Settings**, fill in your iOS Team ID and App ID, as well as your Android Package Name and Key Hashes. This ensures Auth0 can verify your app's identity on each platform.
{% endstep %}
{% endstepper %}

***

## Step 2 — Configure the Plugin in WebToNative

{% stepper %}
{% step %}

1. Open your **WebToNative Dashboard → Add-ons → Auth0**.
2. Enter the following values:

   | Field       | Description                                                        |
   | ----------- | ------------------------------------------------------------------ |
   | `Domain`    | Your Auth0 tenant domain (e.g. `dev-abc123.us.auth0.com`)          |
   | `Client ID` | The Client ID from your Auth0 Application Settings                 |
   | `Scheme`    | The URL scheme used in your callback URLs (e.g. `myapp`)           |
   | `Audience`  | *(Optional)* Your Auth0 API audience, if you're using a custom API |

{% endstep %}

{% step %}
3\. Configure **Deep Linking** for your Auth0 domain so that callback redirects are routed back to your app after the user completes Universal Login. Without this, the login flow will complete in the browser but the tokens won't be delivered back to your app.

* **iOS:** Set up Universal Links by hosting a `/.well-known/apple-app-site-association` file on your Auth0 domain (or use the custom URL scheme configured above). See [Deep Linking](https://www.webtonative.com/support/linkhandling/deeplinking) for setup instructions.
* **Android:** Set up App Links by hosting a `/.well-known/assetlinks.json` file on your Auth0 domain, or rely on the custom URL scheme. See [Deep Linking](https://www.webtonative.com/support/linkhandling/deeplinking) for details.
  {% endstep %}

{% step %}
4\. Configure the **URL Scheme Protocol** in your WebToNative dashboard to match the scheme you entered above (e.g. `myapp`). This is the custom scheme Auth0 uses to redirect back to your app (e.g. `myapp://dev-abc123.us.auth0.com/...`). See [URL Scheme Protocol](https://www.webtonative.com/support/linkhandling/urlscheme).
{% endstep %}

{% step %}
5\. Verify that your Auth0 domain is treated as an **external link** in your app's link handling configuration, so that Auth0 login pages open in a secure browser session rather than the app's WebView. See [Internal vs External Linking](https://www.webtonative.com/support/linkhandling/internalvsexternal).
{% endstep %}
{% endstepper %}

***

## JavaScript API Reference

### Login

Opens the Auth0 Universal Login screen. On success, returns OAuth tokens. Optionally stores credentials with biometric protection for seamless future logins.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.Auth0.login({
  scope: "openid profile email offline_access",
  enableBiometrics: true,
  callback: function (response) {
    if (response.error) {
      console.error("Login failed:", response.error);
      return;
    }
    console.log("Access Token:", response.accessToken);
    console.log("ID Token:", response.idToken);
    console.log("Refresh Token:", response.refreshToken);
  },
});
```

{% endtab %}

{% tab title="ES5+ Module" %}

```javascript
import { login } from "webtonative/build/Auth0";

login({
  scope: "openid profile email offline_access",
  enableBiometrics: true,
  callback: (response) => {
    if (response.error) {
      console.error("Login failed:", response.error);
      return;
    }
    console.log("Access Token:", response.accessToken);
    console.log("ID Token:", response.idToken);
    console.log("Refresh Token:", response.refreshToken);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key                | Type       | Required | Description                                                                                      |
| ------------------ | ---------- | -------- | ------------------------------------------------------------------------------------------------ |
| `scope`            | `String`   | No       | OAuth scopes to request. Include `offline_access` to receive a refresh token.                    |
| `enableBiometrics` | `Boolean`  | No       | If `true`, saves credentials to device secure storage with biometric protection (Face ID, etc.). |
| `callback`         | `Function` | No       | Function invoked with the login response.                                                        |

**Response:**

| Key            | Type     | Description                                             |
| -------------- | -------- | ------------------------------------------------------- |
| `accessToken`  | `String` | The OAuth access token.                                 |
| `idToken`      | `String` | The OpenID Connect ID token.                            |
| `refreshToken` | `String` | The refresh token (requires `offline_access` in scope). |
| `scope`        | `String` | The granted scopes.                                     |
| `error`        | `String` | Error message, present only if login failed.            |

***

### Logout

Clears saved credentials from device secure storage and ends the Auth0 session.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.Auth0.logout({
  callback: function (response) {
    if (response.error) {
      console.error("Logout failed:", response.error);
      return;
    }
    console.log("Logged out successfully");
  },
});
```

{% endtab %}

{% tab title="ES5+ Module" %}

```javascript
import { logout } from "webtonative/build/Auth0";

logout({
  callback: (response) => {
    if (response.error) {
      console.error("Logout failed:", response.error);
      return;
    }
    console.log("Logged out successfully");
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                |
| ---------- | ---------- | -------- | ------------------------------------------ |
| `callback` | `Function` | No       | Function invoked with the logout response. |

**Response:**

| Key       | Type      | Description                            |
| --------- | --------- | -------------------------------------- |
| `success` | `Boolean` | `true` if logout was successful.       |
| `error`   | `String`  | Error message, present only if failed. |

***

### Get Status

Checks whether the user has a valid saved session and whether biometric authentication is available on the device. Use this to decide whether to show a login screen or attempt auto-login with biometrics.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.Auth0.getStatus({
  callback: function (response) {
    if (response.hasValidCredentials) {
      // User has a saved session — attempt getCredentials()
    } else {
      // No saved session — show login screen
    }
  },
});
```

{% endtab %}

{% tab title="ES5+ Module" %}

```javascript
import { getStatus } from "webtonative/build/Auth0";

getStatus({
  callback: (response) => {
    if (response.hasValidCredentials) {
      // User has a saved session — attempt getCredentials()
    } else {
      // No saved session — show login screen
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                |
| ---------- | ---------- | -------- | ------------------------------------------ |
| `callback` | `Function` | No       | Function invoked with the status response. |

**Response:**

| Key                   | Type      | Description                                                                    |
| --------------------- | --------- | ------------------------------------------------------------------------------ |
| `hasValidCredentials` | `Boolean` | `true` if the user has saved credentials and the access token has not expired. |
| `biometryAvailable`   | `Boolean` | `true` if Face ID, Touch ID, or Fingerprint authentication is available.       |
| `biometryType`        | `String`  | The type of biometric available: `"faceId"`, `"touchId"`, or `"none"`.         |

***

### Get Credentials

Retrieves saved credentials from device secure storage. If biometrics were enabled during login, the user will be prompted with Face ID or Fingerprint automatically. If the saved access token has expired, it is automatically renewed using the stored refresh token.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.Auth0.getCredentials({
  callback: function (response) {
    if (response.error) {
      console.error("No saved credentials:", response.error);
      return;
    }
    // Use response.accessToken to make authenticated API calls
    console.log("Access Token:", response.accessToken);
  },
});
```

{% endtab %}

{% tab title="ES5+ Module" %}

```javascript
import { getCredentials } from "webtonative/build/Auth0";

getCredentials({
  callback: (response) => {
    if (response.error) {
      console.error("No saved credentials:", response.error);
      return;
    }
    console.log("Access Token:", response.accessToken);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                     |
| ---------- | ---------- | -------- | ----------------------------------------------- |
| `callback` | `Function` | No       | Function invoked with the credentials response. |

**Response:**

| Key            | Type     | Description                                       |
| -------------- | -------- | ------------------------------------------------- |
| `accessToken`  | `String` | The OAuth access token (auto-renewed if expired). |
| `idToken`      | `String` | The OpenID Connect ID token.                      |
| `refreshToken` | `String` | The refresh token.                                |
| `error`        | `String` | Error message, present only if retrieval failed.  |

***

### Renew Credentials

Manually renews expired tokens using a refresh token. If no `refreshToken` parameter is provided, the plugin automatically uses the token saved from the last successful login.

> In most cases you don't need to call this directly — `getCredentials()` already handles auto-renewal. Use `renew()` only if you need explicit control over the renewal flow.

{% tabs %}
{% tab title="Plain JavaScript" %}

```javascript
window.WTN.Auth0.renew({
  callback: function (response) {
    if (response.error) {
      console.error("Renewal failed:", response.error);
      return;
    }
    console.log("Renewed Access Token:", response.accessToken);
  },
});
```

{% endtab %}

{% tab title="ES5+ Module" %}

```javascript
import { renew } from "webtonative/build/Auth0";

renew({
  callback: (response) => {
    if (response.error) {
      console.error("Renewal failed:", response.error);
      return;
    }
    console.log("Renewed Access Token:", response.accessToken);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key            | Type       | Required | Description                                                           |
| -------------- | ---------- | -------- | --------------------------------------------------------------------- |
| `refreshToken` | `String`   | No       | A specific refresh token to use. If omitted, the saved token is used. |
| `callback`     | `Function` | No       | Function invoked with the renewal response.                           |

**Response:**

| Key            | Type     | Description                            |
| -------------- | -------- | -------------------------------------- |
| `accessToken`  | `String` | The renewed OAuth access token.        |
| `idToken`      | `String` | The renewed OpenID Connect ID token.   |
| `refreshToken` | `String` | The renewed refresh token.             |
| `error`        | `String` | Error message, present only if failed. |

***

## Typical Implementation Flow

Here's how a typical authentication flow looks using the Auth0 plugin:

{% stepper %}
{% step %}

#### App launch

Call `getStatus()` to check if the user has saved credentials.
{% endstep %}

{% step %}

#### Returning user

If `hasValidCredentials` is `true`, call `getCredentials()`. The user is prompted with biometrics (if enabled) and receives fresh tokens automatically.
{% endstep %}

{% step %}

#### New user / expired session

If no valid credentials exist, call `login()` with `enableBiometrics: true` and `scope: "openid profile email offline_access"`. Auth0 Universal Login opens natively.
{% endstep %}

{% step %}

#### Use tokens

Send the `accessToken` in your API calls as a Bearer token in the `Authorization` header.
{% endstep %}

{% step %}

#### Logout

Call `logout()` to clear saved credentials and end the session.
{% endstep %}
{% endstepper %}

***

## Implementation Checklist

### Auth0 Dashboard

* [ ] Created a new **Native** application in Auth0
* [ ] Added **Allowed Callback URLs** and **Allowed Logout URLs** for both Android and iOS
* [ ] Enabled **Allow Offline Access** under Advanced Settings → OAuth
* [ ] Filled in **Device Settings** (iOS Team ID/App ID, Android Package Name/Key Hashes)

### WebToNative Dashboard

* [ ] Entered **Domain**, **Client ID**, and **Scheme** in Add-ons → Auth0
* [ ] Configured [**URL Scheme Protocol**](https://www.webtonative.com/support/linkhandling/urlscheme) matching the scheme set in Auth0 (e.g. `myapp`)
* [ ] Configured [**Deep Linking**](https://www.webtonative.com/support/linkhandling/deeplinking) for your Auth0 domain so callback redirects return to the app
* [ ] Verified Auth0 domain is set as an [external link](https://www.webtonative.com/support/linkhandling/internalvsexternal) so login opens in a secure browser

### Your Website

* [ ] Imported the [WebToNative JavaScript bridge](https://docs.webtonative.com/javascript-apis/getting-started)
* [ ] Implemented `login()` to launch Auth0 Universal Login
* [ ] Implemented `logout()` to give users a way to sign out
* [ ] Used `getStatus()` + `getCredentials()` for seamless returning-user login
* [ ] Hosted `.well-known/assetlinks.json` (Android) and `.well-known/apple-app-site-association` (iOS) for deep linking

***

## Frequently Asked Questions

<details>

<summary>Why use the native Auth0 plugin instead of Auth0 in the WebView?</summary>

Mobile security policies from Google, Apple, and the IETF require authentication to happen in a secure browser session — not inside an embedded WebView. The WebToNative Auth0 plugin uses Auth0's official native SDKs, which satisfy these requirements and provide the best security and user experience.

</details>

<details>

<summary>Can biometrics be tested in simulators?</summary>

No. Face ID, Touch ID, and Fingerprint authentication require a physical device with biometric hardware. Use a real device for testing biometric features.

</details>

<details>

<summary>What scopes should I request?</summary>

At minimum, include `openid profile email`. Add `offline_access` if you want refresh tokens (recommended for biometric re-login and persistent sessions).

</details>

<details>

<summary>What happens if the access token expires?</summary>

If you call `getCredentials()`, expired tokens are automatically renewed using the stored refresh token. You can also call `renew()` manually if you need explicit control.

</details>

<details>

<summary>Do I need to set up deep linking?</summary>

Yes. Auth0 uses redirect-based authentication, which relies on deep links to return control to your app after the login flow completes. You'll need to configure both a [URL Scheme](https://www.webtonative.com/support/linkhandling/urlscheme) and [Deep Linking](https://www.webtonative.com/support/linkhandling/deeplinking) in your WebToNative dashboard. For iOS, this means hosting an `apple-app-site-association` file; for Android, an `assetlinks.json` file on your domain.

</details>


# Device Phone Number API

Retrieve the device phone number using the WebToNative JavaScript API. Access native phone information for Android applications securely.

Retrieves a phone number linked to the user's Google account on the device. On Android, this uses Google's Phone Number Hint, which shows a system prompt allowing the user to pick from phone numbers associated with their signed-in Google account(s).

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

{% hint style="info" %}
**Platform support:** Android only.
{% endhint %}

## Get Device Phone Number

Triggers the Google Phone Number Hint prompt on Android and returns the selected number through the callback.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.getDevicePhoneNumber({
  callback: function (response) {
    console.log(response.phoneNumber);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { getDevicePhoneNumber } from "webtonative";

getDevicePhoneNumber({
  callback: (response) => {
    console.log(response.phoneNumber);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                  |
| ---------- | ---------- | -------- | -------------------------------------------- |
| `callback` | `Function` | No       | Callback function invoked with the response. |

**Callback Response:**

| Key           | Type               | Description                                                                                  |
| ------------- | ------------------ | -------------------------------------------------------------------------------------------- |
| `type`        | `String`           | Always `"getDevicePhoneNumber"`.                                                             |
| `success`     | `Boolean`          | `true` if a phone number was selected and retrieved successfully, `false` otherwise.         |
| `phoneNumber` | `String` or `null` | The phone number selected by the user from the Google account hint. `null` on failure.       |
| `error`       | `String` or `null` | `null` on success. Error message describing what went wrong (e.g., user dismissed the hint). |
| `error_code`  | `String` or `null` | `null` on success. A short error code identifying the failure case.                          |

**Example:**

```javascript
window.WTN.getDevicePhoneNumber({
  callback: function (response) {
    if (response.success && response.phoneNumber) {
      console.log("Selected phone number:", response.phoneNumber);
    } else {
      console.error(
        "Failed to get phone number:",
        response.error_code,
        response.error
      );
    }
  },
});
```

**Notes:**

* Works on **Android only**. iOS will not invoke the callback.
* Phone numbers come from the user's **Google account(s) signed in on the device**, not from the SIM. If the user has no phone number linked to their Google account, the hint prompt may show no options.
* The user must explicitly tap a number in the Google hint sheet — if they dismiss the sheet, `success` will be `false`.
* Requires Google Play services on the device.


# NFC JavaScript API

Integrate Near Field Communication using the WebToNative JavaScript API. Read, write, and interact with NFC tags in Android applications.

Functions to interact with NFC (Near Field Communication) tags from your website. You can check whether NFC is available on the device, scan an NFC tag to read its contents (and optionally open the encoded URL in the app), and write data to an NFC tag.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

{% hint style="info" %}
**Platform support:** Android and iOS. iOS requires devices with NFC reading capability (iPhone 7 and later) and the appropriate entitlements configured on the app.
{% endhint %}

## Status

Checks the current NFC capability and state of the device — whether NFC hardware is present, whether it is enabled, and whether the app is permitted to use it.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.NFC.status({
  callback: function (response) {
    console.log(response.status);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { status } from "webtonative/NFC";

status({
  callback: (response) => {
    console.log(response.status);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                             |
| ---------- | ---------- | -------- | ------------------------------------------------------- |
| `callback` | `Function` | No       | Callback function invoked with the NFC status response. |

**Callback Response:**

| Key      | Type     | Description                                                                |
| -------- | -------- | -------------------------------------------------------------------------- |
| `type`   | `String` | Always `"nfcGetStatus"`.                                                   |
| `status` | `String` | Current NFC state. One of `"ENABLED"`, `"DISABLED"`, or `"NOT_SUPPORTED"`. |

**Status values:**

| Value           | Description                                                             |
| --------------- | ----------------------------------------------------------------------- |
| `ENABLED`       | The device has NFC hardware and it is turned on. Ready for scan/write.  |
| `DISABLED`      | The device has NFC hardware but it is currently turned off in settings. |
| `NOT_SUPPORTED` | The device does not have NFC hardware.                                  |

## Read

Starts an NFC scan session. The device waits for an NFC tag to be tapped and returns the tag's contents through the callback. On Android, a system scan dialog is shown (with the configurable `message`); on iOS, the system NFC scan sheet is presented.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.NFC.read({
  message: "Hold your device near the NFC tag",
  openUrl: false,
  continuous: false,
  callback: function (response) {
    console.log(response.content);
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { read } from "webtonative/NFC";

read({
  message: "Hold your device near the NFC tag",
  openUrl: false,
  continuous: false,
  callback: (response) => {
    console.log(response.content);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key          | Type       | Required | Description                                                                                                                                         |
| ------------ | ---------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `message`    | `String`   | No       | The instruction text shown to the user in the system NFC scan dialog.                                                                               |
| `openUrl`    | `Boolean`  | No       | If `true` and the scanned tag contains an `http`/`https` URL, the app automatically loads it in the WebView. Defaults to `false`.                   |
| `continuous` | `Boolean`  | No       | If `true`, the callback is kept registered after the first scan so subsequent scans also fire the callback. Defaults to `false` (single-shot scan). |
| `callback`   | `Function` | No       | Callback function invoked with the scan response.                                                                                                   |

**Callback Response:**

| Key       | Type      | Description                                                                                                                |
| --------- | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `type`    | `String`  | Always `"nfcScanTag"`.                                                                                                     |
| `success` | `Boolean` | `true` if the tag was read successfully, `false` if the scan was cancelled or failed.                                      |
| `content` | `String`  | The payload read from the tag (URL string, plain text, or other NDEF record content).                                      |
| `opened`  | `Boolean` | Present and `true` only when `openUrl` was requested, the scan succeeded, and the URL was loaded into the WebView.         |
| `error`   | `String`  | Present when `success` is `false`. A short code describing why the scan failed (e.g., user cancellation, unsupported tag). |

**Example:**

```javascript
window.WTN.NFC.read({
  message: "Scan a product tag",
  openUrl: true,
  callback: function (response) {
    if (response.success) {
      console.log("Tag content:", response.content);
      if (response.opened) {
        console.log("URL was auto-opened in the WebView.");
      }
    } else {
      console.error("NFC scan failed:", response.error);
    }
  },
});
```

## Write

Writes an NDEF message to an NFC tag. The device starts a write session and waits for the user to tap a writable tag against it. Use this to encode a URL, plain text, or a custom MIME payload onto an empty/rewritable NFC tag.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.NFC.write({
  type: "url",
  content: "https://example.com",
  message: "Hold your device near a writable NFC tag",
  callback: function (response) {
    console.log(response.success);
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { write } from "webtonative/NFC";

write({
  type: "url",
  content: "https://example.com",
  message: "Hold your device near a writable NFC tag",
  callback: (response) => {
    console.log(response.success);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                                                                                                                                                  |
| ---------- | ---------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`     | `String`   | Yes      | The type of NDEF record to write. One of `"url"` or `"string"`.                                                                                                              |
| `content`  | `String`   | Yes      | The value to write onto the tag. For `type: "url"`, any valid URI scheme is accepted (`http`, `https`, `tel`, `sms`, `mailto`, `geo`, etc.). For `type: "string"`, any text. |
| `message`  | `String`   | No       | The instruction text shown to the user in the system NFC write dialog.                                                                                                       |
| `callback` | `Function` | No       | Callback function invoked with the write response.                                                                                                                           |

**Supported `type` values:**

| Value    | Description                                                              |
| -------- | ------------------------------------------------------------------------ |
| `url`    | Writes an NDEF URI record. `content` should be a valid URI (any scheme). |
| `string` | Writes an NDEF text record. `content` is plain text.                     |

**Callback Response:**

| Key       | Type      | Description                                                                                         |
| --------- | --------- | --------------------------------------------------------------------------------------------------- |
| `type`    | `String`  | Always `"nfcWriteTag"`.                                                                             |
| `success` | `Boolean` | `true` if the tag was written successfully, `false` otherwise.                                      |
| `cancel`  | `Boolean` | `true` if the user cancelled the write session before a tag was tapped. `false` otherwise.          |
| `error`   | `String`  | Present when `success` is `false`. A short error code describing the failure (see the table below). |

**Possible `error` values:**

| Code                | Description                                                                                                |
| ------------------- | ---------------------------------------------------------------------------------------------------------- |
| `INVALID_URL`       | `type` was `url` but `content` was blank or did not contain a valid URI scheme.                            |
| `INVALID_MIME_TYPE` | A MIME-type write was requested but no `mimeType` was provided.                                            |
| `WRITE_FAILED`      | The tag could not be written — e.g., the tag is read-only, locked, or the NDEF payload could not be built. |

**Example:**

```javascript
window.WTN.NFC.write({
  type: "url",
  content: "https://example.com/product/42",
  message: "Tap a blank NFC tag to encode the product link",
  callback: function (response) {
    if (response.success) {
      console.log("Tag written successfully");
    } else if (response.cancel) {
      console.log("User cancelled the write");
    } else {
      console.error("Write failed:", response.error);
    }
  },
});
```

**Notes:**

* Always call `status` before attempting to `read` or `write` so you can show a meaningful message to the user when NFC is `DISABLED` or `NOT_SUPPORTED`.
* On iOS, NFC sessions are time-limited by the system; if no tag is tapped within the timeout, the session will end and the callback will fire with `success: false`.
* `openUrl` in `read` only auto-loads `http` and `https` URLs into the WebView. Other schemes (e.g., `tel:`, `mailto:`) are returned in `content` but are not opened automatically.
* For `write`, the tag must be writable and have enough capacity for the NDEF payload. Locked or read-only tags will fail with `WRITE_FAILED`.


# Age Safety API

Implement age verification using the WebToNative JavaScript API. Restrict access and deliver age-appropriate experiences in Android and iOS apps.

Functions to support age assurance and regulatory compliance in your app. You can request the user's age signals / declared age range from the platform's native age-verification service, and (on iOS) notify the system about a significant change to your app's content.

These APIs wrap **Google Play's Age Signals API** on Android and Apple's **Declared Age Range** framework on iOS. They are intended for apps that need to gate content or features by age in regulated regions.

{% hint style="info" %}
You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

{% hint style="warning" %}
**Platform support:** `getAgeSignals` works on Android and iOS. `notifySignificantChange` is **iOS only**. The native age-range/permission APIs on iOS require **iOS 26.2 or later**.

**Store services required:** Both functions only run when the app build has store services enabled. If store services are disabled, the call is ignored and no callback fires.
{% endhint %}

## getAgeSignals

Requests the platform's age signals for the current user against a configured age gate.

* **Android** — calls the Google Play Age Signals API and returns the user's verification status and (when available) an age range.
* **iOS** — calls `AgeRangeService.requestAgeRange`, which may present a system sheet, and returns the declared age range. Requires iOS 26.2+.

The response shape differs between platforms (see the per-platform tables below), so check `success` first and then read the fields relevant to the running platform.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.AgeSafety.getAgeSignals({
  ageGates: 18,
  callback: function (response) {
    console.log(response);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { getAgeSignals } from "webtonative/AgeSafety";

getAgeSignals({
  ageGates: 18,
  callback: (response) => {
    console.log(response);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key        | Type       | Required | Description                                                                                                                      |
| ---------- | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `ageGates` | `Number`   | No       | The primary age threshold to check against, e.g. `18`. On iOS this drives the age band returned by the system. Defaults to `18`. |
| `callback` | `Function` | No       | Callback function invoked with the age-signals response.                                                                         |

{% hint style="info" %}
**iOS note:** The underlying iOS API supports up to three age gates (creating up to four age bands, each spanning at least 2 years). The JavaScript API currently sends a single primary `ageGates` value.
{% endhint %}

### Callback Response — common fields

| Key       | Type      | Description                                                                             |
| --------- | --------- | --------------------------------------------------------------------------------------- |
| `type`    | `String`  | Always `"getAgeSignals"`.                                                               |
| `success` | `Boolean` | `true` if the age signals were retrieved successfully, `false` otherwise.               |
| `error`   | `String`  | Present when `success` is `false`. A human-readable message describing what went wrong. |

### Callback Response — Android

Returned by Google Play's Age Signals API.

| Key                      | Type     | Description                                                                    |
| ------------------------ | -------- | ------------------------------------------------------------------------------ |
| `userStatus`             | `String` | The user's age-verification status as reported by Play.                        |
| `ageLower`               | `Number` | Present when available. The lower bound of the user's age range.               |
| `ageUpper`               | `Number` | Present when available. The upper bound of the user's age range.               |
| `installId`              | `String` | Present when available. An install identifier associated with the signals.     |
| `mostRecentApprovalDate` | `String` | Present when available. The date of the most recent age approval, as a string. |

### Callback Response — iOS

Returned by Apple's Declared Age Range framework (iOS 26.2+).

| Key                        | Type       | Description                                                                                                                             |
| -------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `isEligibleForAgeFeatures` | `Boolean`  | `true` if the user is in a regulated region where age assurance is required. `false` means age sharing is voluntary.                    |
| `declined`                 | `Boolean`  | `true` if the user declined to share their age range (or no sharing occurred in a non-regulated region).                                |
| `lowerBound`               | `Number`   | Present when sharing. The lower bound of the declared age range. Absent (`nil`) means the user is **below** the lowest age gate.        |
| `upperBound`               | `Number`   | Present when sharing. The upper bound of the declared age range. Absent (`nil`) means the user is **at or above** the highest age gate. |
| `activeParentalControls`   | `String[]` | Active parental controls in effect, e.g. `["communicationLimits"]`. Empty array when none.                                              |
| `isSupervised`             | `Boolean`  | `true` if any parental controls are active (i.e. `activeParentalControls` is non-empty).                                                |
| `ageRangeDeclaration`      | `String`   | How the age was established. See the table below. Present only when sharing.                                                            |
| `errorCode`                | `String`   | Present on certain failures. One of `"invalidRequest"` or `"notAvailable"` (see error table).                                           |

**`ageRangeDeclaration` values (iOS):**

| Value                          | Description                                   |
| ------------------------------ | --------------------------------------------- |
| `selfDeclared`                 | Age was declared by the user themselves.      |
| `guardianDeclared`             | Age was declared by a guardian.               |
| `paymentChecked`               | Age was verified via a payment method.        |
| `governmentIDChecked`          | Age was verified via a government ID.         |
| `checkedByOtherMethod`         | Age was verified by another method.           |
| `guardianPaymentChecked`       | Guardian's age verified via a payment method. |
| `guardianGovernmentIDChecked`  | Guardian's age verified via a government ID.  |
| `guardianCheckedByOtherMethod` | Guardian's age verified by another method.    |
| `unknown`                      | An unrecognized declaration type.             |

**iOS `errorCode` values:**

| Code             | Description                                                                                             |
| ---------------- | ------------------------------------------------------------------------------------------------------- |
| `invalidRequest` | The age gates were invalid — each band must be ≥ 2 years, with a maximum of 3 gates in ascending order. |
| `notAvailable`   | The age range service is not available on this device (e.g. no iCloud account, parental restrictions).  |

**Example:**

```javascript
window.WTN.AgeSafety.getAgeSignals({
  ageGates: 18,
  callback: function (response) {
    if (!response.success) {
      console.error("Age signals failed:", response.errorCode, response.error);
      return;
    }

    // iOS
    if (typeof response.isEligibleForAgeFeatures !== "undefined") {
      if (response.declined) {
        console.log("User declined to share their age range");
      } else {
        console.log("Age range:", response.lowerBound, "-", response.upperBound);
        console.log("Supervised:", response.isSupervised);
      }
    }

    // Android
    if (response.userStatus) {
      console.log("User status:", response.userStatus);
      console.log("Age range:", response.ageLower, "-", response.ageUpper);
    }
  },
});
```

## notifySignificantChange

Notifies the system that the app has undergone a significant change to its content or experience (for example, a major content update relevant to age assurance). On iOS this presents a PermissionKit prompt via `AskCenter` so the user / guardian can be re-asked.

{% hint style="warning" %}
**Platform support:** iOS only, requires iOS 26.2+. On other platforms the call does nothing.
{% endhint %}

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.AgeSafety.notifySignificantChange({
  topicString: "Major content update for the 2026 season",
  callback: function (response) {
    console.log(response.success);
  },
});
```

{% endtab %}

{% tab title="ES5+" %}

```javascript
import { notifySignificantChange } from "webtonative/AgeSafety";

notifySignificantChange({
  topicString: "Major content update for the 2026 season",
  callback: (response) => {
    console.log(response.success);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key           | Type       | Required | Description                                                 |
| ------------- | ---------- | -------- | ----------------------------------------------------------- |
| `topicString` | `String`   | Yes      | A description of the significant change. Must be non-empty. |
| `callback`    | `Function` | No       | Callback function invoked with the response.                |

**Callback Response:**

| Key                  | Type      | Description                                                                                                                                                                                                                     |
| -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`               | `String`  | Always `"notifySignificantChange"`.                                                                                                                                                                                             |
| `success`            | `Boolean` | `true` if the notification was submitted successfully, `false` otherwise.                                                                                                                                                       |
| `error`              | `String`  | Present when `success` is `false`. A human-readable message describing the failure.                                                                                                                                             |
| `regionNotSupported` | `Boolean` | Present and `true` when the failure is because PermissionKit / `AskCenter` is not available in the user's region (currently limited to EU/EEA). Use this to show a tailored message rather than treating it as a generic error. |
| `_simulator`         | `Boolean` | Present and `true` only on the iOS Simulator, where the PermissionKit UI cannot be presented (stubbed response).                                                                                                                |
| `_note`              | `String`  | Present only on the iOS Simulator. Explains that the response is a simulator stub.                                                                                                                                              |

**Example:**

```javascript
window.WTN.AgeSafety.notifySignificantChange({
  topicString: "Major content update for the 2026 season",
  callback: function (response) {
    if (response.success) {
      console.log("Significant change submitted");
    } else if (response.regionNotSupported) {
      console.log("This feature isn't available in your region yet");
    } else {
      console.error("Failed to notify:", response.error);
    }
  },
});
```

## Notes

* Both functions require **store services** to be enabled on the build; otherwise the call is silently ignored.
* `getAgeSignals` returns different fields on Android vs iOS. Always branch on the field you need (`userStatus` for Android, `isEligibleForAgeFeatures` / `declined` for iOS) rather than assuming a single shape.
* On iOS, `lowerBound`/`upperBound` being absent is meaningful: a missing `lowerBound` means the user is **below** the lowest age gate, and a missing `upperBound` means they are **at or above** the highest age gate.
* The iOS age-range and significant-change APIs require **iOS 26.2 or later**. On older iOS versions the callback fires with `success: false` and an explanatory `error`.
* On the **iOS Simulator**, `notifySignificantChange` cannot present the PermissionKit UI and returns a stub response with `_simulator: true`. Test the full flow on a real device.
* `notifySignificantChange` (and the underlying PermissionKit `AskCenter`) is currently restricted to certain regions (EU/EEA). Outside those regions it fails with `regionNotSupported: true`.
* **Debug only (Android):** in debug builds, `getAgeSignals` honors an optional `testScenario` value (e.g. `"SUPERVISED"`, `"VERIFIED"`) that drives a fake signals manager for testing. This is ignored in release builds, which always use the real Play Age Signals API.


# Permissions Handling API

Manage device permissions using the WebToNative JavaScript API. Request, check, and handle permissions for Android and iOS apps.

Functions to manage device permissions and hardware/service states from your website. You can check the current status of one or more permissions without prompting, request a single permission (showing the native system dialog when needed), and open the system settings screen so the user can re-enable a permission manually.

{% hint style="info" %}
You'll need to import the JavaScript file into your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).
{% endhint %}

{% hint style="info" %}
**Platform support:** Android and iOS. All three functions work identically on both platforms unless a platform note states otherwise.
{% endhint %}

***

## Status Values

Every permission-related callback returns one of the following status strings.

| Status                | Description                                                                                                                                                |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ALLOWED`             | Permission is granted. The feature can be used.                                                                                                            |
| `NOT_ALLOWED`         | Permission is not granted but a dialog can still be shown it has never been asked, was denied once (and can be asked again), or the device service is off. |
| `PERMANENTLY_BLOCKED` | The user selected "Don't ask again" (Android) or denied the permission from Settings (iOS). No dialog can be shown the user must open Settings manually.   |
| `RESTRICTED`          | A system-level policy (parental controls, MDM) prevents the permission. The user cannot change it. iOS only.                                               |
| `UNKNOWN_STATUS`      | The status cannot be determined on this platform or device.                                                                                                |

***

## Supported Permissions

These values are accepted by both `request` and `check`.

| Value             | Description                                        | iOS | Android |
| ----------------- | -------------------------------------------------- | --- | ------- |
| `camera`          | Camera access                                      | ✅   | ✅       |
| `location`        | Location access (while using the app)              | ✅   | ✅       |
| `location_always` | Location access at all times, including background | ✅   | ✅       |
| `notification`    | Push notification delivery                         | ✅   | ✅       |
| `record_audio`    | Microphone access                                  | ✅   | ✅       |
| `contact`         | Read device contacts                               | ✅   | ✅       |
| `bluetooth`       | Bluetooth access                                   | ✅   | ✅       |

{% hint style="info" %}
**Contact** and **Bluetooth** are only active when the corresponding native module is enabled in your app configuration. If the module is disabled, the callback returns `NOT_ALLOWED` immediately without showing a dialog.
{% endhint %}

***

## Supported Device States

These values represent hardware or service toggles, not grantable permissions. They are accepted **only by `check`** (and by `open`) they **cannot** be passed to `request`.

| Value             | Description                                       | iOS | Android |
| ----------------- | ------------------------------------------------- | --- | ------- |
| `enableBluetooth` | Whether the Bluetooth radio is currently on       | ✅   | ✅       |
| `enableNfc`       | Whether NFC is currently enabled                  | ✅   | ✅       |
| `enableLocation`  | Whether Location Services are enabled system-wide | ✅   | ✅       |

{% hint style="info" %}
iOS cannot check the Bluetooth power state synchronously `enableBluetooth` returns `UNKNOWN_STATUS` on iOS.
{% endhint %}

***

## Check

Checks the current status of one or more permissions or device states **without showing any dialog**. Results for all requested items are returned together in a single callback.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Permission.check({
  permissions: ["camera", "notification", "enableLocation"],
  callback: function (response) {
    console.log(response.permissionStatus);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { check } from "webtonative/Permission";

check({
  permissions: ["camera", "notification", "enableLocation"],
  callback: (response) => {
    console.log(response.permissionStatus);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key           | Type       | Required | Description                                                       |
| ------------- | ---------- | -------- | ----------------------------------------------------------------- |
| `permissions` | `String[]` | Yes      | One or more permission or device-state values to check.           |
| `callback`    | `Function` | No       | Callback function invoked with the status of all requested items. |

**Callback Response:**

| Key                | Type     | Description                                                                                                           |
| ------------------ | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `type`             | `String` | Always `"checkPermission"`.                                                                                           |
| `permissionStatus` | `Object` | A key-value map where each key is a permission value you passed in and each value is one of the status strings above. |

**Example response:**

```json
{
  "type": "checkPermission",
  "permissionStatus": {
    "camera": "ALLOWED",
    "notification": "PERMANENTLY_BLOCKED",
    "enableLocation": "NOT_ALLOWED"
  }
}
```

**Notes:**

* On Android, a permission that has never been requested returns `NOT_ALLOWED` (not `PERMANENTLY_BLOCKED`). `PERMANENTLY_BLOCKED` is only returned once the user has been shown a dialog at least once and selected "Don't ask again".

***

## Request

Requests a **single** permission from the user. Shows the native system dialog if the permission has not been granted yet. If it is already granted the callback fires immediately with `ALLOWED`; if it is permanently blocked the callback fires immediately with `PERMANENTLY_BLOCKED` no dialog is shown.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Permission.request({
  permission: "camera",
  callback: function (response) {
    console.log(response.status);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { request } from "webtonative/Permission";

request({
  permission: "camera",
  callback: (response) => {
    console.log(response.status);
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key          | Type       | Required | Description                                                                                           |
| ------------ | ---------- | -------- | ----------------------------------------------------------------------------------------------------- |
| `permission` | `String`   | Yes      | A single permission value to request. Device-state values (`enableBluetooth`, etc.) are not accepted. |
| `callback`   | `Function` | No       | Callback function invoked with the outcome of the request.                                            |

**Callback Response:**

| Key      | Type     | Description                                                      |
| -------- | -------- | ---------------------------------------------------------------- |
| `type`   | `String` | Always `"requestPermission"`.                                    |
| `status` | `String` | One of the status strings above, reflecting the request outcome. |

**Example response:**

```json
{
  "type": "requestPermission",
  "status": "ALLOWED"
}
```

**Notes:**

* Only one permission can be requested per call.
* On iOS, `notification` permission can only be requested once. After it is denied, subsequent calls return `PERMANENTLY_BLOCKED` immediately.

***

## Open

Opens the system settings screen for a specific permission or device state. Use this when `check` or `request` returns `PERMANENTLY_BLOCKED` and you want to guide the user to re-enable the permission manually. Optionally show a native confirmation dialog before navigating to Settings.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
// Open settings directly
window.WTN.Permission.open({
  permission: "camera",
});

// Show a confirmation dialog first
window.WTN.Permission.open({
  permission: "camera",
  alertDialogStyle: {
    title: "Camera Permission Required",
    message: "Please enable camera access in Settings to continue.",
    positiveText: "Open Settings",
    negativeText: "Cancel",
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { open } from "webtonative/Permission";

// Open settings directly
open({
  permission: "camera",
});

// Show a confirmation dialog first
open({
  permission: "camera",
  alertDialogStyle: {
    title: "Camera Permission Required",
    message: "Please enable camera access in Settings to continue.",
    positiveText: "Open Settings",
    negativeText: "Cancel",
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key                             | Type     | Required | Description                                                                                                                 |
| ------------------------------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `permission`                    | `String` | Yes      | The permission or device-state value whose settings screen to open. Accepts all permission and device-state values.         |
| `alertDialogStyle`              | `Object` | No       | When provided, a native confirmation dialog is shown before navigating to Settings. If omitted, Settings opens immediately. |
| `alertDialogStyle.title`        | `String` | No       | Dialog title. Defaults to a permission-name-based title if omitted.                                                         |
| `alertDialogStyle.message`      | `String` | No       | Dialog body text. A generic message is used if omitted.                                                                     |
| `alertDialogStyle.positiveText` | `String` | No       | Text for the confirm button. Defaults to `"Settings"`.                                                                      |
| `alertDialogStyle.negativeText` | `String` | No       | Text for the cancel button. Defaults to `"Cancel"`.                                                                         |

***

**Notes:**

* Device-state values (`enableBluetooth`, `enableNfc`, `enableLocation`) work with `check` and `open` only never with `request`.


# Stripe Tap To Pay API

Accept in-person card payments directly on a customer's phone no extra hardware required. The WebToNative Stripe plugin integrates Stripe Terminal's native Tap to Pay SDKs for Android and iOS

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

> **Platform support:** Android and iOS. iOS Tap to Pay requires a physical iPhone (XS or later) on a supported iOS version and Apple's explicit approval see [Limitations](#limitations-read-before-purchasing) below.

***

## Limitations read before purchasing

Stripe Tap to Pay is a paid add-on with hard requirements outside WebToNative's control. Please review this table before purchasing.

| Limitation                           | Tag                | Details                                                                                                                                                                                 |
| ------------------------------------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Apple approval required**          | `Hard requirement` | Tap to Pay on iPhone requires Apple's explicit entitlement approval before it works on real devices in production. Purchasing this add-on does not guarantee Apple will grant approval. |
| **Backend required**                 | `Hard requirement` | The SDK does not provide backend APIs. You must build and host the Connection Token API and PaymentIntent API on your own server.                                                       |
| **Stripe account required**          | `Hard requirement` | You must use your own Stripe account. WebToNative does not provide or manage Stripe accounts, Stripe Dashboard configuration, or merchant onboarding.                                   |
| **Country restrictions**             | `Verify first`     | Stripe Terminal and Tap to Pay are only available in Stripe-supported countries. Check [stripe.com](https://stripe.com/global) for the current list before purchasing.                  |
| **No refund processing**             |                    | The SDK does not process refunds. Refunds must be handled by your backend using Stripe's refund APIs.                                                                                   |
| **No webhook replacement**           |                    | The SDK does not replace Stripe Webhooks. Your backend should process Stripe Webhooks independently for reliable payment status updates.                                                |
| **Real device required for testing** |                    | Final testing must be done on a supported physical device. Simulator and emulator testing has limited support  use `isSimulated: true` for development only.                            |
| **Internet connection required**     |                    | Most payment operations require an active internet connection. Limited offline support depends on Stripe Terminal's own offline capabilities and your implementation.                   |

***

## How It Works

1. Your website calls `makeTapToPay()` with a Stripe Terminal **connection token**, an amount/currency (or an existing PaymentIntent's `clientSecret`), and a Stripe Terminal **Location ID**.
2. The native SDK initializes Stripe Terminal, discovers a Tap to Pay reader on the device, and connects to it.
3. The SDK creates (or retrieves) a PaymentIntent, then prompts the customer to tap their card, phone, or wallet on the back of the device.
4. Stripe confirms the payment, and the result is returned to your `callback`.

{% hint style="info" %}
**Platform difference:** On iOS, the SDK only supports **retrieving** an existing PaymentIntent you must create the PaymentIntent on your backend first and pass its `clientSecret`. On Android, `clientSecret` is optional: if you omit it, the SDK creates the PaymentIntent itself from `amount` and `currency`. To keep behaviour identical across platforms, always create the PaymentIntent on your backend and pass `clientSecret`.
{% endhint %}

***

## Setting Up Stripe Tap to Pay

### 1. Set Up Your Stripe Account

1. Create or use an existing account at [stripe.com](https://stripe.com/) and enable [Stripe Terminal](https://dashboard.stripe.com/terminal).
2. Create a [Location](https://stripe.com/docs/terminal/fleet/locations) in the Stripe Dashboard (or via the API) and note its Location ID (`tml_...`) this is your `stripeLocationId`.
3. Apply for the [Tap to Pay on iPhone entitlement](https://developer.apple.com/apple-pay/tap-to-pay-on-iphone/) directly with Apple if you plan to support iOS. This is a manual approval process handled entirely by Apple, WebToNative cannot request or expedite it on your behalf.

### 2. Build Your Backend Endpoints

The SDK does not talk to Stripe's servers on its own for these two steps your server must:

* **Connection Token endpoint** - a POST endpoint that creates a [Stripe Terminal connection token](https://stripe.com/docs/terminal/fleet/locations#create) and returns it as JSON: `{ "secret": "<connection_token>" }`. Your website fetches this and passes the token as `connectionToken`.
* **PaymentIntent endpoint** *(required for iOS, recommended for Android)* - a POST endpoint that [creates a PaymentIntent](https://stripe.com/docs/api/payment_intents/create) with `payment_method_types: ["card_present"]` and `capture_method` set to `automatic` or `manual`, and returns its `client_secret`. Your website passes this as `clientSecret`.
* Handle **capture** (for `manual` capture method) and **refunds** using Stripe's standard APIs, the SDK does not do this for you.
* Process **Stripe Webhooks** independently to keep your own order/payment records in sync the SDK's callback is not a substitute for webhooks.

### 3. Enable the Add-on in WebToNative

1. Open your **WebToNative Dashboard → Add-ons → Stripe Tap to Pay** and purchase/enable it.
2. Unlike some other add-ons, there is no dashboard configuration screen for Stripe, your Stripe keys, connection tokens, and location IDs are all supplied at runtime from your JavaScript, as shown below.

### 4. Platform Permissions

WebToNative's Stripe plugin already declares the required permissions and usage-description strings for both platforms. You still need to make sure your app can obtain them at runtime:

**Android** - the plugin adds `ACCESS_FINE_LOCATION`, `BLUETOOTH_CONNECT`, `BLUETOOTH_SCAN`, and `NFC` to your manifest. Tap to Pay will fail with `LOCATION_PERMISSION_NOT_GRANTED` or `GPS_NOT_ENABLED` if these aren't granted/enabled, prompt the user for location and nearby-devices (Bluetooth) permissions, and ensure GPS is turned on, before calling `makeTapToPay`.

**iOS** - the plugin adds `NSBluetoothAlwaysUsageDescription` and `NSBluetoothPeripheralUsageDescription` to your `Info.plist`. The device also needs NFC reading capability, and location services must be enabled and authorized, otherwise the callback returns `NO_NFC_SUPPORT_ON_DEVICE`, `BLUETOOTH_PERMISSION_NOT_GRANTED`, `GPS_NOT_ENABLED`, or `LOCATION_PERMISSION_NOT_GRANTED`.

***

## JavaScript API Reference

### makeTapToPay

Discovers a Tap to Pay reader on the device, connects to it, and collects a payment.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Stripe.makeTapToPay({
  connectionToken: "YOUR_STRIPE_TERMINAL_CONNECTION_TOKEN",
  stripeLocationId: "tml_xxxxxxxxxxxx",
  clientSecret: "pi_xxxxxxxx_secret_xxxxxxxx",
  amount: 1999,
  currency: "usd",
  captureMethod: "automatic",
  isSimulated: false,
  callback: function (response) {
    if (response.paymentStatus === "SUCCESS") {
      console.log("Payment ID:", response.paymentId);
    } else {
      console.error("Payment failed:", response.failureReason);
    }
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { makeTapToPay } from "webtonative/Stripe";

makeTapToPay({
  connectionToken: "YOUR_STRIPE_TERMINAL_CONNECTION_TOKEN",
  stripeLocationId: "tml_xxxxxxxxxxxx",
  clientSecret: "pi_xxxxxxxx_secret_xxxxxxxx",
  amount: 1999,
  currency: "usd",
  captureMethod: "automatic",
  isSimulated: false,
  callback: (response) => {
    if (response.paymentStatus === "SUCCESS") {
      console.log("Payment ID:", response.paymentId);
    } else {
      console.error("Payment failed:", response.failureReason);
    }
  },
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key                | Type       | Required                             | Description                                                                                                                                                                                                                                                                         |
| ------------------ | ---------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connectionToken`  | `String`   | Yes                                  | A Stripe Terminal connection token, fetched from your backend's connection-token endpoint before calling `makeTapToPay`.                                                                                                                                                            |
| `stripeLocationId` | `String`   | Yes                                  | The Stripe Terminal Location ID (`tml_...`) to associate the reader with.                                                                                                                                                                                                           |
| `clientSecret`     | `String`   | Required on iOS, optional on Android | The `client_secret` of a PaymentIntent created on your backend. If provided, the SDK retrieves and confirms that PaymentIntent. On Android only, if omitted, the SDK creates a new PaymentIntent from `amount`/`currency` instead.                                                  |
| `amount`           | `Number`   | Yes, unless `clientSecret` is set    | Payment amount in the smallest currency unit (e.g. `1999` = $19.99 USD).                                                                                                                                                                                                            |
| `currency`         | `String`   | Yes, unless `clientSecret` is set    | Three-letter ISO currency code (e.g. `"usd"`).                                                                                                                                                                                                                                      |
| `captureMethod`    | `String`   | No - default `"automatic"`           | `"automatic"` or `"manual"`. Ignored when `clientSecret` is provided, since the capture method is already set on the existing PaymentIntent.                                                                                                                                        |
| `isSimulated`      | `Boolean`  | No - default `false`                 | Use Stripe's simulated reader for development. Must be `false` for real transactions on a physical device.                                                                                                                                                                          |
| `apiUrl`           | `String`   | No                                   | URL of a backend endpoint that returns a Stripe Terminal connection token as `{ "secret": "..." }`. Currently only honored on iOS as a fallback when `connectionToken` isn't supplied, Android always requires `connectionToken`. Prefer always passing `connectionToken` directly. |
| `callback`         | `Function` | No                                   | Function invoked with the payment result. See **Response** below.                                                                                                                                                                                                                   |

**Response:**

| Key             | Type     | Description                                                                                 |
| --------------- | -------- | ------------------------------------------------------------------------------------------- |
| `type`          | `String` | Always `"makeTapToPayStripePayment"`.                                                       |
| `paymentStatus` | `String` | `"SUCCESS"` or `"FAILED"`.                                                                  |
| `token`         | `String` | The connection token used for this payment.                                                 |
| `paymentId`     | `String` | The Stripe PaymentIntent ID (e.g. `"pi_..."`), present when `paymentStatus` is `"SUCCESS"`. |
| `paymentIntent` | `String` | A stringified representation of the full Stripe PaymentIntent object, present on success.   |
| `failureReason` | `String` | Present only when `paymentStatus` is `"FAILED"`. See **Failure Reasons** below.             |

**Failure Reasons:**

| Value                                     | Platform     | Meaning                                                                                                                         |
| ----------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `JSON_UNDEFINED`                          | Android      | The options passed to `makeTapToPay` could not be parsed.                                                                       |
| `GPS_NOT_ENABLED`                         | Android, iOS | Device location services are turned off.                                                                                        |
| `LOCATION_PERMISSION_NOT_GRANTED`         | Android, iOS | The app does not have location permission.                                                                                      |
| `BLUETOOTH_PERMISSION_NOT_GRANTED`        | iOS          | Bluetooth permission was denied.                                                                                                |
| `NO_NFC_SUPPORT_ON_DEVICE`                | iOS          | The device doesn't support NFC and `isSimulated` was `false`.                                                                   |
| `LOCATION_ID_MISSING`                     | iOS          | `stripeLocationId` was missing.                                                                                                 |
| `TOKEN_ISSUE`                             | Android      | `connectionToken` was empty or invalid.                                                                                         |
| `API_CONNECTION_FAILED`                   | Android      | The Stripe Terminal SDK could not be initialized with the given token.                                                          |
| `INVALID_TOKEN`                           | Android      | Stripe rejected the connection token.                                                                                           |
| `NO_NEARBY_READER_DEVICE_FOUND`           | Android      | Reader discovery completed without finding a Tap to Pay reader.                                                                 |
| `READER_FAILURE - <code>` / other message | Android, iOS | Reader connection, card collection, or payment confirmation failed the value contains the underlying Stripe Terminal SDK error. |

***

## Typical Implementation Flow

1. **Create a PaymentIntent** call your backend to create a PaymentIntent for the order total, and get back its `client_secret`.
2. **Fetch a connection token** call your backend's connection-token endpoint to get a fresh Stripe Terminal `connectionToken`.
3. **Collect payment** call `makeTapToPay()` with the `connectionToken`, `clientSecret`, and `stripeLocationId`, and prompt the customer to tap their card.
4. **Handle the result** on `"SUCCESS"`, store `paymentId` against the order. On `"FAILED"`, inspect `failureReason` and show the customer an appropriate retry message.
5. **Reconcile via webhooks** use Stripe Webhooks on your backend as the source of truth for payment status, independent of the app callback.

***

## Implementation Checklist

### Stripe Dashboard

* [ ] Stripe Terminal enabled on your Stripe account
* [ ] A Location created, with its Location ID noted
* [ ] (iOS) Tap to Pay on iPhone entitlement requested from Apple

### Your Backend

* [ ] Connection Token endpoint returning `{ "secret": "..." }`
* [ ] PaymentIntent creation endpoint returning `client_secret`
* [ ] Capture and refund handling via Stripe's APIs
* [ ] Stripe Webhooks configured for reliable payment status updates

### WebToNative Dashboard

* [ ] Stripe Tap to Pay add-on purchased and enabled

### Your Website

* [ ] Imported the [WebToNative JavaScript bridge](https://docs.webtonative.com/javascript-apis/getting-started)
* [ ] Implemented `makeTapToPay()` with a fresh `connectionToken` and `clientSecret` per transaction
* [ ] Handled both `"SUCCESS"` and `"FAILED"` in the callback

***

## Frequently Asked Questions

<details>

<summary>Do I need my own Stripe account?</summary>

Yes. WebToNative does not provide, manage, or proxy a Stripe account for you, you supply your own Stripe keys, connection tokens, and PaymentIntents from your own backend.

</details>

<details>

<summary>Will this work in production on iPhone without any extra steps?</summary>

No. Tap to Pay on iPhone requires Apple's explicit entitlement approval for your app. Purchasing this WebToNative add-on does not guarantee or expedite that approval, you must apply directly with Apple.

</details>

<details>

<summary>Can I test without a physical reader-capable device?</summary>

Partially. Pass `isSimulated: true` to use Stripe's simulated reader during development. Final testing before release must be done on a real, supported device, simulator/emulator support is limited.

</details>

<details>

<summary>Does the SDK handle refunds or webhooks?</summary>

No. Refunds must be processed by your backend using Stripe's refund APIs, and you should independently process Stripe Webhooks on your backend for reliable payment status, the JavaScript `callback` is not a substitute for either.

</details>

<details>

<summary>Why does iOS require `clientSecret` but Android doesn't?</summary>

The current iOS implementation only supports retrieving an existing PaymentIntent by `client_secret`; it does not create one from `amount`/`currency` on-device. Android can create a PaymentIntent directly from `amount`/`currency` if `clientSecret` is omitted. To keep your integration consistent across both platforms, always create the PaymentIntent on your backend and pass `clientSecret`.

</details>

<details>

<summary>What happens if the customer's card is declined?</summary>

The callback fires with `paymentStatus: "FAILED"` and a `failureReason` describing the underlying Stripe Terminal error (for example, a decline reason from Stripe's API). Show the customer a retry option.

</details>

***


# Android TV D-Pad Functions

WebToNative's Smart TV Support add-on forwards every remote button press into your website as a JavaScript function call, so your site can react to it

When your app runs on an Android TV device, users navigate with a remote control (D-Pad) instead of touch.

Unlike the other bridges in these docs, this is not something your website calls it's a contract in the other direction: **your website defines a global function, and the native app calls it** whenever the user presses a remote key.

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

> **Platform support:** Android only. This add-on has no iOS/tvOS equivalent `handleKeyEvent` is never called on iOS.

***

## How It Works

1. Enable **Smart TV Support** in the WebToNative dashboard (see [Setup](#setting-up-smart-tv-support) below).
2. On your website, define a global `window.handleKeyEvent(key)` function.
3. When the app is running on a device the SDK detects as Android TV, every remote button press is checked against `typeof handleKeyEvent === 'function'`. If your function exists, it's called with a string identifying the key (see [Key Values](#key-values) below).

{% hint style="danger" %}
**Your website cannot block or "consume" a key press.** The native side does not look at anything `handleKeyEvent` returns. For the navigation keys (`UP`, `DOWN`, `LEFT`, `RIGHT`, `CENTER`, `HOME`, `MENU`, `ENTER`, `INFO`), the native app **always** additionally re-dispatches the raw key event into the WebView right after calling `handleKeyEvent`, to move DOM focus between focusable elements. You can react to these keys (e.g. play a sound, update some UI state) but you cannot prevent the underlying focus movement from also happening. The media transport keys (`PLAY`, `PAUSE`, `STOP`, `NEXT`, `PREVIOUS`) are the exception, see the note below.
{% endhint %}

***

## Setting Up Smart TV Support

1. Go to your **WebToNative dashboard** → **Add-ons** → **Smart TV Support** and enable it.
2. No credentials are required — once enabled, the behaviour described on this page is active automatically whenever the app is running on a device detected as Android TV.

{% hint style="info" %}
Detection is automatic: the SDK checks the device's `UiModeManager` for TV mode and for the Android TV "leanback" feature. You don't need to detect Android TV yourself in JavaScript before defining `handleKeyEvent` just define it, and it will simply never be called on a phone/tablet.
{% endhint %}

***

## Defining `handleKeyEvent`

Define this function anywhere on your page, before the user starts interacting with the remote (e.g. on page load):

```javascript
window.handleKeyEvent = function (key) {
  switch (key) {
    case "UP":
    case "DOWN":
    case "LEFT":
    case "RIGHT":
      // Optional: react to directional movement (e.g. play a focus sound).
      // The app also moves DOM focus for you automatically — you don't need
      // to move focus yourself for these four keys.
      break;
    case "CENTER":
    case "ENTER":
      // The user pressed select/OK on the currently focused element.
      document.activeElement?.click?.();
      break;
    case "HOME":
    case "MENU":
    case "INFO":
      // React to these as your UI needs.
      break;
    case "PLAY":
      videoElement.play();
      break;
    case "PAUSE":
      videoElement.pause();
      break;
    case "STOP":
      videoElement.pause();
      videoElement.currentTime = 0;
      break;
    case "NEXT":
      playNextInPlaylist();
      break;
    case "PREVIOUS":
      playPreviousInPlaylist();
      break;
  }
};
```

**Parameters passed to your function:**

| Key   | Type     | Description                                                     |
| ----- | -------- | --------------------------------------------------------------- |
| `key` | `String` | The remote button pressed. See [Key Values](#key-values) below. |

Your function has no meaningful return value — nothing reads it.

***

## Key Values

| Value      | Remote Button                    | Native fallback behavior                                                                                                                                                          |
| ---------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `UP`       | D-Pad Up                         | Always also moves DOM focus upward, in addition to calling `handleKeyEvent`.                                                                                                      |
| `DOWN`     | D-Pad Down                       | Always also moves DOM focus downward, in addition to calling `handleKeyEvent`.                                                                                                    |
| `LEFT`     | D-Pad Left                       | Always also moves DOM focus left, in addition to calling `handleKeyEvent`.                                                                                                        |
| `RIGHT`    | D-Pad Right                      | Always also moves DOM focus right, in addition to calling `handleKeyEvent`.                                                                                                       |
| `CENTER`   | D-Pad Center / Select            | Also re-dispatched to the WebView for native focus handling, see the warning above.                                                                                               |
| `HOME`     | Home                             | Also re-dispatched to the WebView.                                                                                                                                                |
| `MENU`     | Menu                             | Also re-dispatched to the WebView.                                                                                                                                                |
| `ENTER`    | Enter                            | Also re-dispatched to the WebView.                                                                                                                                                |
| `INFO`     | Info                             | Also re-dispatched to the WebView.                                                                                                                                                |
| `PLAY`     | Media Play/Pause (while paused)  | **Not** re-dispatched your website is fully responsible for handling it. If `handleKeyEvent` isn't defined, the user sees a "Media Player not working" toast and nothing happens. |
| `PAUSE`    | Media Play/Pause (while playing) | Same as `PLAY` not re-dispatched, fully your responsibility.                                                                                                                      |
| `STOP`     | Media Stop                       | Same as `PLAY` not re-dispatched, fully your responsibility.                                                                                                                      |
| `NEXT`     | Media Next Track                 | Not re-dispatched. No fallback toast if undefined it's simply a no-op.                                                                                                            |
| `PREVIOUS` | Media Previous Track             | Not re-dispatched. No fallback toast if undefined it's simply a no-op.                                                                                                            |

{% hint style="warning" %}
**`PLAY`, `PAUSE`, and `STOP` are only forwarded to `handleKeyEvent` if the separate Custom Media Player add-on is disabled.** If you also have WebToNative's Custom Media Player add-on enabled, those three keys are intercepted by the native custom player instead, and your `handleKeyEvent` never receives them at all. Disable Custom Media Player if you need to handle transport controls yourself in JavaScript.
{% endhint %}

### Keys That Never Reach `handleKeyEvent`

| Remote Button          | What happens instead                                                                          |
| ---------------------- | --------------------------------------------------------------------------------------------- |
| Back                   | Handled entirely natively as the app's back-navigation action. Never forwarded to JavaScript. |
| Voice/Assistant button | Natively launches Google Assistant. Never forwarded to JavaScript.                            |

***

## Implementation Checklist

### WebToNative Dashboard

* [ ] Smart TV Support add-on enabled
* [ ] Custom Media Player add-on left disabled if you want `PLAY` / `PAUSE` / `STOP` delivered to `handleKeyEvent`

### Your Website

* [ ] Imported the [WebToNative JavaScript bridge](https://docs.webtonative.com/javascript-apis/getting-started)
* [ ] Defined `window.handleKeyEvent(key)` before the user can interact with the remote
* [ ] Handled `PLAY`, `PAUSE`, `STOP`, `NEXT`, `PREVIOUS` explicitly these have no native fallback
* [ ] Not relying on `handleKeyEvent` to block/prevent navigation for `UP`/`DOWN`/`LEFT`/`RIGHT`/`CENTER`/`HOME`/`MENU`/`ENTER`/`INFO` the native focus movement always happens regardless
* [ ] Defined `window.volumeEventCallback(event)` separately, if you also need to react to volume button presses

***

## Frequently Asked Questions

<details>

<summary>Why is `handleKeyEvent` never called on my device?</summary>

Two things are required: the Smart TV Support add-on must be enabled in the WebToNative dashboard, and the device must be detected as Android TV (TV UI mode or the "leanback" feature). On a regular phone or tablet or on iOS `handleKeyEvent` is never called.

</details>

<details>

<summary>Can I prevent the D-Pad from moving focus to the next element?</summary>

No, not for the navigation keys (`UP`, `DOWN`, `LEFT`, `RIGHT`, `CENTER`, `HOME`, `MENU`, `ENTER`, `INFO`). The native app calls `handleKeyEvent` (if defined) and then unconditionally re-dispatches the key event for native focus handling there's no way to intercept or cancel that from JavaScript today.

</details>

<details>

<summary>Why did the user see a "Media Player not working" message?</summary>

That toast only appears for the media transport keys (`PLAY`, `PAUSE`, `STOP`) when `window.handleKeyEvent` isn't defined at all. Define the function and handle those key values to remove it.

</details>

<details>

<summary>I pressed Play/Pause/Stop but nothing happened in my JavaScript function.</summary>

Check whether the Custom Media Player add-on is also enabled if it is, those three keys are consumed by the native custom player before they ever reach `handleKeyEvent`.

</details>

<details>

<summary>Does pressing Back on the remote trigger `handleKeyEvent`?</summary>

No. Back is handled entirely by the app's native back-navigation logic and is never forwarded to JavaScript, regardless of whether `handleKeyEvent` is defined.

</details>

<details>

<summary>Is there an equivalent for tvOS / Apple TV?</summary>

No. This feature is Android TV-only. There is currently no Siri Remote / tvOS equivalent in WebToNative.

</details>


# Beacon Apis

Functions to monitor nearby iBeacons from your website.

The WebToNative Beacon plugin watches for one or more beacons by UUID (optionally scoped to a major/minor pair), and notifies your backend over a webhook — plus, optionally, shows a local push notification — whenever a user's device enters or exits range.

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

> **Platform support:** Android and iOS. iOS requires the user to grant **"Always Allow"** location access — background/region monitoring is not possible with "While Using the App" only. Android only needs foreground location access, since monitoring runs inside a foreground service.

***

## Setting Up Beacon

1. Go to your **WebToNative dashboard** → **Add-ons** → **Beacon** and enable it.
2. That's the only dashboard step — unlike Auth0 or Meta Ads, Beacon has no credentials to enter. The beacon(s) to watch, the webhook URL, and the notification content are all supplied at runtime from JavaScript via `initBeaconData` (below), not from the dashboard.

{% hint style="warning" %}
**Calling `initBeaconData` before the add-on is enabled in the dashboard does nothing — no callback fires.** Enable the Beacon add-on first, then call `initBeaconData` once your page knows which beacon(s) to watch.
{% endhint %}

***

## JavaScript API Reference

### initBeaconData

Starts (or restarts) beacon monitoring with the given list of beacons. Calling this again while monitoring is already active replaces the previous beacon list — there's no separate "add a beacon" call.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Beacon.initBeaconData({
  beaconData: {
    beaconConfig: [
      {
        uuid: "E2C56DB5-DFFB-48D2-B060-D0F5A71096E0",
        major: 1,
        minor: 1,
        webhookUrl: "https://your-backend.example.com/beacon-webhook",
        settings: {
          showNotificationOnEntry: true,
          showNotificationOnExit: true,
          notificationInterval: 5,
          notificationContentSource: "PRE_DEFINED",
          defaultNotificationEnterData: {
            title: "Welcome!",
            body: "You're near our store.",
            image: "",
            deepLink: "https://example.com/promo",
          },
          defaultNotificationExitData: {
            title: "See you soon!",
            body: "",
            image: "",
            deepLink: "",
          },
        },
      },
    ],
    userInfo: { userId: "u_123", userName: "Jane", userEmail: "jane@example.com" },
  },
  callback: function (response) {
    console.log(response.isSuccess, response.response);
  },
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { initBeaconData } from "webtonative/Beacon";

initBeaconData({
  beaconData: {
    beaconConfig: [
      {
        uuid: "E2C56DB5-DFFB-48D2-B060-D0F5A71096E0",
        major: 1,
        minor: 1,
        webhookUrl: "https://your-backend.example.com/beacon-webhook",
        settings: { showNotificationOnEntry: true, showNotificationOnExit: true },
      },
    ],
    userInfo: { userId: "u_123", userName: "Jane", userEmail: "jane@example.com" },
  },
  callback: (response) => console.log(response),
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key                       | Type       | Required | Description                                                                                                                                                 |
| ------------------------- | ---------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `beaconData.beaconConfig` | `Array`    | Yes      | List of beacons to monitor. See the table below for each entry's fields.                                                                                    |
| `beaconData.userInfo`     | `Object`   | No       | Arbitrary identifying info (`userId`, `userName`, `userEmail`, or any keys you want) included in the webhook payload on enter/exit.                         |
| `callback`                | `Function` | No       | Called once, immediately, with the result of the permission/setup check — not called again per enter/exit event (those go to `webhookUrl`, not JavaScript). |

**`beaconConfig[]` entry fields:**

| Key                                                           | Type      | Required                   | Description                                                                                                                                                                                        |
| ------------------------------------------------------------- | --------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `uuid`                                                        | `String`  | Yes                        | The beacon's proximity UUID.                                                                                                                                                                       |
| `major`                                                       | `Number`  | **Android: No · iOS: Yes** | Beacon major value. On Android, omit it (or the whole beacon entry loses major/minor scoping) to match **any** major for this UUID — iOS has no such wildcard and requires an explicit value.      |
| `minor`                                                       | `Number`  | **Android: No · iOS: Yes** | Beacon minor value. Same wildcard behavior as `major` — Android-only.                                                                                                                              |
| `webhookUrl`                                                  | `String`  | No                         | Your server endpoint. WebToNative POSTs a JSON payload here on every enter/exit — see [Webhook Payload](#webhook-payload-not-a-javascript-callback). Defaults to not sending a webhook if omitted. |
| `settings.showNotificationOnEntry` / `showNotificationOnExit` | `Boolean` | No                         | Show a local push notification when the device enters/exits range. Both default to `false`.                                                                                                        |
| `settings.notificationInterval`                               | `Number`  | No                         | Minutes to wait before showing the same enter/exit notification again, to avoid spamming the user as they linger near a beacon's edge. Defaults to `0` (no throttling).                            |
| `settings.notificationContentSource`                          | `String`  | No                         | `"PRE_DEFINED"` (use `defaultNotificationEnterData`/`ExitData` below) or `"API_FETCHED"` (your own backend decides the content via the webhook). Defaults to `"PRE_DEFINED"`.                      |
| `settings.defaultNotificationEnterData` / `ExitData`          | `Object`  | No                         | `{ title, body, image, deepLink }` shown in the local notification. If omitted, Android still shows a blank notification; iOS shows none at all — see the note below.                              |

{% hint style="warning" %}
**A single malformed beacon entry can silently disable monitoring for every other entry in the array on iOS.** If any entry in `beaconConfig[]` is missing `uuid`, `major`, or `minor`, iOS aborts parsing the whole array — none of your beacons get monitored, not just the bad one. Android skips only the malformed entry and keeps monitoring the rest. Always send complete entries (with explicit `major`/`minor` on iOS) to avoid this.
{% endhint %}

**Callback Response:**

| Key         | Type      | Description                                                                                                                                                                                                                                                                     |
| ----------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`      | `String`  | Always `"initBeaconData"`.                                                                                                                                                                                                                                                      |
| `isSuccess` | `Boolean` | `true` if monitoring started. `false` if a permission or hardware check failed — see `response`.                                                                                                                                                                                |
| `response`  | `String`  | `"BEACON_INITIALIZED"` on success. On failure, one of the status codes below — **Android may return several joined with `" \| "`** if multiple things are blocking at once (e.g. `"LOCATION_NOT_ALWAYS_ALLOWED \| BLUETOOTH_NOT_ENABLED"`); **iOS always returns exactly one.** |

**Possible failure `response` values:**

| Value                                      | Platform | Meaning                                                                                                      |
| ------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------ |
| `NOTIFICATION_NOT_ALLOWED`                 | Android  | Notification permission not granted yet.                                                                     |
| `NOTIFICATION_PERMANENTLY_BLOCKED`         | Android  | Notification permission permanently denied.                                                                  |
| `LOCATION_NOT_ALWAYS_ALLOWED`              | Both     | Location isn't granted at all (Android), or only "While Using the App" is granted instead of "Always" (iOS). |
| `LOCATION_PERMANENTLY_BLOCKED`             | Both     | Location permission permanently denied.                                                                      |
| `BLUETOOTH_PERMISSION_NOT_ALLOWED`         | Android  | Bluetooth permission not granted.                                                                            |
| `BLUETOOTH_PERMISSION_PERMANENTLY_BLOCKED` | Android  | Bluetooth permission permanently denied.                                                                     |
| `BLUETOOTH_NOT_ENABLED`                    | Android  | Bluetooth is turned off on the device.                                                                       |
| `LOCATION_NOT_SUPPORTED`                   | iOS      | Beacon monitoring is disabled for this build.                                                                |
| `NOTIFICATION_PERMISSION_DENIED`           | iOS      | User denied the notification permission prompt.                                                              |

Any denied-but-not-yet-permanent permission is automatically re-prompted by the native app; you don't need to request it yourself before calling `initBeaconData`.

***

## Webhook Payload (not a JavaScript callback)

Region enter/exit events are **not** delivered back to your JavaScript — they're POSTed directly from the native app to the `webhookUrl` you supplied for that beacon:

```json
{
  "status": "CONNECTED",
  "beaconInfo": { "beaconUUID": "E2C56DB5-DFFB-48D2-B060-D0F5A71096E0", "beaconMajor": 1, "beaconMinor": 1 },
  "userInfo": { "userId": "u_123", "userName": "Jane", "userEmail": "jane@example.com" },
  "FCMToken": "...",
  "OneSignalPlayerId": "...",
  "deviceInfo": { "platform": "ANDROID_APP", "...": "..." }
}
```

`status` is `"CONNECTED"` on region entry and `"DISCONNECTED"` on region exit.

{% hint style="info" %}
**`userInfo` is a JSON object in the webhook body on Android, but a JSON-encoded&#x20;*****string*****&#x20;on iOS.** If your backend parses `userInfo` as an object, add a check for the iOS case (`typeof body.userInfo === "string"`) and `JSON.parse` it before use.
{% endhint %}

***

## Foreground Notifications (iOS only)

If a beacon notification would fire while your app is in the foreground, and the app has "disable notifications in foreground" turned on, iOS forwards the notification's data to JavaScript instead of showing a system banner, by calling a global function you can define:

```javascript
window.wtnGetForegroundNotificationData = function (data) {
  console.log(data.title, data.body, data.userInfo.deepLink, data.type); // data.type === "beacon"
};
```

Android has no equivalent — beacon notifications on Android always show as a normal system notification regardless of whether the app is foregrounded.

***

## Frequently Asked Questions

<details>

<summary>How do I stop monitoring a beacon?</summary>

There's no dedicated "stop" function on either platform today. Calling `initBeaconData` again replaces the previous beacon list, but to fully stop monitoring you'd currently need to reinstall or restart the relevant native flow — this is a known gap, not a configuration option.

</details>

<details>

<summary>Why does `initBeaconData` fail with `LOCATION_NOT_ALWAYS_ALLOWED` even though the user granted location access?</summary>

On iOS, beacon monitoring is a background capability and specifically requires the **"Always"** location authorization level — "While Using the App" is not enough and will produce this exact status. On Android, this status instead means foreground location wasn't granted at all.

</details>

<details>

<summary>Can I scope monitoring to a specific major/minor on both platforms?</summary>

Yes, but the "match any" wildcard only exists on Android — omit `major`/`minor` there to match any value for that UUID. iOS requires both fields on every entry; there is no wildcard equivalent.

</details>


# Pdf Viewer Apis

Functions to open PDF files in a native, in-app viewer instead of downloading them or handing them off to an external app.

The WebToNative PDF Viewer plugin renders the file natively — search, thumbnails, password-protected files, download, and share are all handled in-app.

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

> **Platform support:** Android and iOS.

***

## Setting Up PDF Viewer

1. Go to your **WebToNative dashboard** → **Add-ons** → **PDF Viewer** and enable it.
2. Configure the toolbar/behavior options you want under the same add-on page:

| Dashboard field       | Maps to             | Default                             | Description                                                                                                                                                                                                      |
| --------------------- | ------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Toolbar Title         | `toolbarTitle`      | *(uses the `title` passed from JS)* | Overrides the `title` argument if set to a non-blank value.                                                                                                                                                      |
| Show Download         | `showDownload`      | Off                                 | Shows a download-to-device button in the toolbar.                                                                                                                                                                |
| Show Share            | `showShare`         | Off                                 | Shows a share-sheet button in the toolbar.                                                                                                                                                                       |
| Show Search           | `showSearch`        | Off                                 | Shows an in-document search button.                                                                                                                                                                              |
| Show Thumbnails       | `showThumbnails`    | Off                                 | Shows a page-thumbnail grid button.                                                                                                                                                                              |
| Show Page Indicator   | `showPageIndicator` | **Off on Android, On on iOS** ⚠     | Shows a page-number pill at the bottom. See the platform-difference hint below.                                                                                                                                  |
| Auto-Detect PDF Links | `autoDetectLinks`   | Off                                 | If a link the user taps inside your site resolves to a `.pdf` URL, opens it in the native viewer instead of navigating the WebView. See [Auto-Detect Matching](#auto-detect-matching-differs-by-platform) below. |
| URL Patterns          | `urlPatterns`       | *(empty)*                           | Only used when Auto-Detect is on — see below.                                                                                                                                                                    |
| Error Message         | `errorMessage`      | *(native default message)*          | Custom text shown if a PDF fails to load.                                                                                                                                                                        |
| Error Button Text     | `errorButtonName`   | `"Retry"`                           | Label for the retry button on the error screen.                                                                                                                                                                  |

{% hint style="warning" %}
**`showPageIndicator` defaults differently per platform when left unset.** Android hides the page indicator by default; iOS shows it by default. If you need identical behavior on both platforms, set this explicitly in the dashboard rather than relying on the default.
{% endhint %}

***

## JavaScript API Reference

### openPDF

Opens a PDF from a URL in the native in-app viewer.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.openPDF({
  url: "https://example.com/files/manual.pdf",
  title: "Product Manual",
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { openPDF } from "webtonative";

openPDF({
  url: "https://example.com/files/manual.pdf",
  title: "Product Manual",
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key     | Type     | Required | Description                                                                                                                  |
| ------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `url`   | `String` | Yes      | The URL of the PDF to open. Throws a JavaScript error immediately (before reaching native code) if blank.                    |
| `title` | `String` | No       | Shown in the viewer's toolbar, unless the dashboard's Toolbar Title field is set to a non-blank value, which takes priority. |

{% hint style="danger" %}
**`openPDF` has no `callback` parameter.** All viewer configuration (search, thumbnails, password handling, download, share, auto-detect) comes from the dashboard settings above, not from additional arguments to this call — there is nothing else to pass here beyond `url` and `title`.
{% endhint %}

***

## Auto-Detect Matching Differs By Platform

If you enable **Auto-Detect PDF Links**, the matching rule against `urlPatterns` is not identical on both platforms:

* **Android** opens the viewer for any URL simply ending in `.pdf`. `urlPatterns` is only consulted as a fallback for URLs that *don't* end in `.pdf` (substring match).
* **iOS** requires the URL to *both* end in `.pdf` **and** match at least one entry in `urlPatterns` as a prefix. A bare `.pdf` URL with Auto-Detect on and no `urlPatterns` configured will open automatically on Android but **not** on iOS.

If you need consistent behavior, always fill in `urlPatterns` explicitly rather than relying on the "any `.pdf` URL" fallback that only exists on Android.

***

## Frequently Asked Questions

<details>

<summary>Why does the page-number indicator show on iOS but not Android with the same config?</summary>

`showPageIndicator` defaults to off on Android and on iOS when left unset in the dashboard. Set it explicitly if you need matching behavior across platforms.

</details>

<details>

<summary>Can I pass search/thumbnails/download settings directly in the `openPDF()` call?</summary>

No — `openPDF` only accepts `url` and `title`. Everything else is configured once, for the whole app, via the dashboard's PDF Viewer add-on settings.

</details>

<details>

<summary>What happens if a PDF requires a password?</summary>

The viewer shows a native password-entry screen and retries opening the file once a password is submitted. There is currently no JavaScript event for "password required" — the user is prompted entirely natively.

</details>


# Debugger

Opens a full-screen, in-app console log viewer over your website — showing console.log / warn / error / info output, uncaught JS errors, and unhandled promise rejections as they happen.

Use it to diagnose issues on a real user's device (e.g. walking someone through a support call, or catching a production bug you can't reproduce locally) without wiring up remote DevTools or asking the user to send you logs manually.

{% hint style="info" %}
This doesn't go through the `window.WTN` JavaScript bridge like other WebToNative features — there's no import to add and no `npm` equivalent. It works by navigating to a special URL that the app intercepts before the page actually navigates away. See [How It Works](#how-it-works) below.
{% endhint %}

> **Platform support:** Android and iOS.

***

## Opening the Debugger

Trigger it by navigating to `w2n://console-screen` — the app catches this before it leaves your site and opens the console viewer as an overlay on top of whatever screen is currently showing.

```javascript
window.location.href = "w2n://console-screen";
```

Or wire it directly to a link or button, with no JavaScript needed:

```html
<a href="w2n://console-screen">Open Debugger</a>
```

### How It Works

`w2n://console-screen` is a custom URL scheme, not a page URL. Both apps intercept navigation to it at the WebView level and cancel the actual navigation, so your page never unloads — the console viewer just slides in on top of it. That also means it works on any page, even one where the WebToNative JavaScript file hasn't been imported, since nothing needs to be loaded on your page for the interception to happen.

***

## What It Shows

* **Console output** — every `console.log` / `info` / `warn` / `error` call, plus uncaught errors and unhandled promise rejections, grouped by the page URL they came from.
* **Native bridge traffic** — calls made through `window.WTN.*` and their responses, so you can see what your site sent to the app and what it got back, alongside the console output.
* A search bar to filter entries by URL, filter tabs to narrow by type/direction, and a clear-all action to wipe the current log list.

Close it with the **X** in the top bar, or the device back gesture/button — either returns you to the page exactly as it was underneath.

***

## Common Patterns

### Hidden Support/Debug Trigger

Since anyone who can navigate your site to `w2n://console-screen` can open this, most sites tuck the trigger somewhere a regular user won't stumble into it — a hidden button on a settings/support page, gated behind a tap sequence, or behind a flag you control from your own backend:

```javascript
function openDebugConsole() {
  window.location.href = "w2n://console-screen";
}

// e.g. only reachable from a support page you control
document.getElementById("support-debug-button")?.addEventListener("click", openDebugConsole);
```

***

## Frequently Asked Questions

<details>

<summary>Do I need to import the WebToNative JavaScript file to use this?</summary>

No — this doesn't use the `window.WTN` bridge at all, so it works even on a page where that script hasn't loaded. See [How It Works](#how-it-works).

</details>

<details>

<summary>What happens if I navigate to `w2n://console-screen` in a normal browser, outside the WebToNative app?</summary>

Nothing useful — browsers don't recognize the `w2n://` scheme, so the navigation just fails silently. Only rely on this from links/buttons that are reached from inside your WebToNative app.

</details>

<details>

<summary>Can I close the debugger from JavaScript once it's open?</summary>

No — there's no call to dismiss it programmatically. The user closes it with the on-screen **X** or the device back action.

</details>

<details>

<summary>Does opening it trigger `beforeunload` or change my page's URL?</summary>

No — the app cancels the navigation before it actually happens, so your page never unloads and the URL bar (if any) doesn't change. The console viewer opens as an overlay on top of the current page.

</details>


# Custom Sound Feature

Play your own uploaded sounds on demand from JavaScript, pair them with haptic feedback, and reuse them for push notifications across Android and iOS.

Lets your app play a **custom sound** — uploaded once via the **WebToNative Dashboard** — instead of relying only on the device's default sound. This page covers the shared prerequisite (uploading the file) and how the feature fits together; each way of actually playing the sound has its own dedicated page:

| Page                                                                          | What it covers                                                                                                                                                                                                                          |
| ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Sound.play](broken://pages/aef35d04cc88cca740f664b6e5e4943c32bbd1c1)         | Play the uploaded sound immediately, triggered directly from your JavaScript.                                                                                                                                                           |
| [Haptics.trigger](broken://pages/89f43d502aa7e1fe7c9de6cd1403e6abd450b543)    | Play the uploaded sound alongside a haptic vibration effect, in one call.                                                                                                                                                               |
| [Notification Sound](broken://pages/ffd7c158fb69bb714964306c45c5d0bcc8fa05b7) | Have a **remote push notification** play the uploaded sound automatically when it arrives (via [OneSignal](broken://pages/8edb3d376c88b810fb53d342c2b09f573456b9f0) or [FCM](broken://pages/0f00cc955f4330b7803163fd1c6625444c06e04e)). |

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

> **Platform support:** Android and iOS.

***

## How It Works

One uploaded sound file can be used in all three places above, and all three reference it **by the same name**:

1. **`Sound.play(soundName)`** — plays it immediately from JavaScript (e.g. a chat "message sent" ping, a game effect). See [Sound.play](broken://pages/aef35d04cc88cca740f664b6e5e4943c32bbd1c1).
2. **`Haptics.trigger({ effect, soundName })`** — plays it alongside a haptic vibration effect, in one call. See [Haptic Feedback](broken://pages/89f43d502aa7e1fe7c9de6cd1403e6abd450b543).
3. **A remote push notification** — OneSignal or FCM tells the OS to play it automatically when a push arrives. See [Notification Sound](broken://pages/ffd7c158fb69bb714964306c45c5d0bcc8fa05b7).

The sound file itself is never sent from your JavaScript or from a push payload — only its **name** is. It must already be uploaded to your app via the Dashboard (below) before you reference it from any of the three places above.

***

## Uploading the Sound File (Dashboard)

Go to your **WebToNative Dashboard → Add-ons → Notification → OS Notification Sound** and upload the sound file under the platform's sound setting. Android and iOS each have their **own separate upload field** — uploading for one platform does not make the file available on the other, so upload the same audio to both fields if you want consistent behavior across platforms.

* **Reference name:** the name you pass to `Sound.play` / `soundName` is derived from the uploaded file's name, minus its extension.
* **Format:** only `.mp3` and `.wav` files can be uploaded — these are the only supported formats on both platforms.
* **Extension:** `.mp3` is the default — if you call `Sound.play("your_sound_name")` **without an extension**, `.mp3` is assumed automatically, so you don't need to pass anything if you uploaded an `.mp3` file. If you uploaded a `.wav` file, you must pass the extension explicitly — `Sound.play("your_sound_name.wav")` — or the lookup will assume `.mp3` and fail to find it.

> Changes take effect on your app's next build — if you're testing on a device already running an older build, rebuild/reinstall after uploading a new or changed sound file.

***

## Frequently Asked Questions

<details>

<summary>Do I need to enable an add-on on the WebToNative Dashboard to use this?</summary>

Yes — you need to add the **OS Notification Sound** add-on from WebToNative. Once it's added, you can upload your sound file under **OS Notification Sound**, in the **Notification** section of **Add-ons**, for each platform you want it on. The JavaScript bridge works as soon as it's imported as usual.

</details>

<details>

<summary>Can I use the same sound file for `Sound.play`, `Haptics.trigger`, and a push notification?</summary>

Yes — upload it once per platform via the Dashboard, and reference it by the same name from all three. For the push notification side, the platform-specific dashboard/API fields have their own naming conventions (extension required on iOS, omitted on Android) — see [Notification Sound](broken://pages/ffd7c158fb69bb714964306c45c5d0bcc8fa05b7).

</details>

<details>

<summary>What audio formats are supported?</summary>

Only `.mp3` and `.wav` — these are the only formats you can upload, on either platform. `.mp3` is assumed by default if you don't pass an extension in `Sound.play`; for `.wav`, pass the extension explicitly.

</details>

<details>

<summary>Why did I get `PERMANENTLY_BLOCKED` or no sound after asking for notification permission?</summary>

That's unrelated to this page — `Sound.play`/`Haptics.trigger` play immediately from JavaScript and don't require notification permission at all. Notification permission only affects whether the OS shows/plays a *remote push* automatically; see [Permission](broken://pages/dc06f6a344d650fd8c86c6907f98a575f7e8862b) for permission handling, and [Notification Sound](broken://pages/ffd7c158fb69bb714964306c45c5d0bcc8fa05b7) for push-specific sound setup.

</details>


# Sound through Javascript Function

Plays a custom sound immediately, which is triggered directly from your JavaScript.

Plays a custom sound — uploaded once via the WebToNative Dashboard — immediately, triggered directly from your JavaScript. Use it for anything that should feel like a native alert sound: a chat "message sent" ping, a game effect, an order confirmation.

> Before calling this, upload the sound file via the Dashboard — see [Uploading the Sound File](broken://pages/a79c4542382c36e04c0f4f96d71228e854c27913#uploading-the-sound-file-dashboard) on the [OS Notification Sound](broken://pages/a79c4542382c36e04c0f4f96d71228e854c27913) page. `Sound.play` only plays a sound that's already been uploaded; it does not accept a file directly.

> **Platform support:** Android and iOS.

> Looking to have a **remote push notification** play this sound automatically instead? See [Notification Sound](broken://pages/8a9aac3abd127b5cf76c02602b3c8580ead563dd).

***

## JavaScript API Reference

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Sound.play("your_sound_name");
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { play } from "webtonative/Sound";

play("your_sound_name");
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key         | Type     | Required | Description                                                                                                                                                                                                                           |
| ----------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `soundName` | `String` | Yes      | Name of the uploaded sound file, with or without extension (see [Uploading the Sound File](broken://pages/a79c4542382c36e04c0f4f96d71228e854c27913#uploading-the-sound-file-dashboard)). If no extension is given, `.mp3` is assumed. |

This function does not take a `callback` — it is fire-and-forget, with no response to read. If the named sound isn't found or fails to play, it fails **silently** on both platforms — see [Troubleshooting](#troubleshooting) below.

***

## Common Patterns

### Playing a Sound Only on Notification-Relevant Actions

Reserve `Sound.play` for actions that should feel like a native alert — a message received in an open chat, an item added to cart — rather than every UI tap:

```javascript
import { play } from "webtonative/Sound";

function onChatMessageReceived() {
  play("chat_ping");
}

function onOrderConfirmed() {
  play("order_confirmed");
}
```

***

## Troubleshooting

* **Nothing plays, no error in the console:** this is expected if the sound isn't found — both platforms fail silently with no callback to JavaScript. Double-check that the file was uploaded to the correct platform's field on the Dashboard, that you've rebuilt/reinstalled since uploading, and that the name (and extension, if you passed one) matches exactly — see [Uploading the Sound File](broken://pages/a79c4542382c36e04c0f4f96d71228e854c27913#uploading-the-sound-file-dashboard).


# Haptic Sound Apis

Plays a custom sound alongside a haptic vibration effect, in one call.

Plays a custom sound — uploaded once via the WebToNative Dashboard — alongside a haptic vibration effect, in one call. Use this when an action should feel like a native alert *and* buzz at the same time (e.g. an order confirmation, an error state).

> This is the sound-specific side of `Haptics.trigger`. For plain haptic feedback without a sound — the full `effect` reference and `isHapticSupported` — see [Haptic Feedback](broken://pages/b6fa796e1b136fc7d88b5047fbb85590f1ffc502).

> Before calling this, upload the sound file via the Dashboard — see [Uploading the Sound File](broken://pages/a79c4542382c36e04c0f4f96d71228e854c27913#uploading-the-sound-file-dashboard) on the [OS Notification Sound](broken://pages/a79c4542382c36e04c0f4f96d71228e854c27913) page. Just want the sound alone, with no vibration? See [Sound.play](broken://pages/2d515e3fb3ebe549ba811c09f086c55f872cacd9).

> **Platform support:** Android and iOS.

***

## JavaScript API Reference

`Haptics.trigger` accepts an optional `soundName` alongside `effect`, so one call triggers both.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Haptics.trigger({
  effect: "impactMedium",
  soundName: "your_sound_name",
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { trigger } from "webtonative/Haptics";

trigger({
  effect: "impactMedium",
  soundName: "your_sound_name",
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key         | Type     | Required | Description                                                                                                                                                                                                                   |
| ----------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `effect`    | `String` | No       | The vibration pattern to play. See [supported values](broken://pages/b6fa796e1b136fc7d88b5047fbb85590f1ffc502#trigger) on the Haptic Feedback page.                                                                           |
| `soundName` | `String` | No       | Name of an uploaded sound file to play alongside the haptic effect, using the exact same lookup rules as [`Sound.play`](broken://pages/2d515e3fb3ebe549ba811c09f086c55f872cacd9) — omit it to trigger the haptic effect only. |

> Internally this plays the sound through the same path as `Sound.play` — there's no behavioral difference between calling `Sound.play("x")` directly versus passing `soundName: "x"` here, other than the haptic effect firing at the same time.

***

## Common Patterns

### Sound Alone vs. Sound + Haptic

Reserve the combined call for actions that warrant both signals — use `Sound.play` alone for higher-frequency events where a vibration on every occurrence would be excessive:

```javascript
import { play } from "webtonative/Sound";
import { trigger } from "webtonative/Haptics";

function onChatMessageReceived() {
  // Sound only — this happens often, a haptic on every message would be excessive
  play("chat_ping");
}

function onOrderConfirmed() {
  // Sound + haptic together — a rarer, higher-significance event
  trigger({
    effect: "notificationSuccess",
    soundName: "order_confirmed",
  });
}
```

***

## Troubleshooting

* **Sound doesn't play (haptic effect still fires):** the sound lookup fails silently, same as [`Sound.play`](broken://pages/2d515e3fb3ebe549ba811c09f086c55f872cacd9#troubleshooting) — double-check the file was uploaded to the correct platform's field on the Dashboard, that you've rebuilt/reinstalled since uploading, and that the name (and extension, if you passed one) matches exactly.


# Notification Sound

Have a remote push notification play your custom sound automatically when it arrives

As opposed to [`Sound.play`](broken://pages/5534a5bf4395b1bad8e325ab9826a2109d3c399c)/[`Haptics.trigger`](broken://pages/9ee054bb5947f859bb772318d1490af3b84bd743), which play it on demand from your own JavaScript. This page covers the mechanics both push providers share; the actual sending step is provider-specific:

| Page                                                                                                                    | Use when...                                          |
| ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| [Custom Notification Sound via OneSignal](broken://pages/41598d1a0d3d8fe2e541dc13fbb32f25f73d5560)                      | You send pushes through the **OneSignal Dashboard**. |
| [Custom Notification Sound via Firebase Cloud Messaging (FCM)](broken://pages/3a78b56c939b3b89da342987f226e0771aff18fb) | Your backend calls the **FCM API** directly.         |

> Before using either page below, upload the sound file via the Dashboard — see [Uploading the Sound File](broken://pages/8c1b185b3b39abf677e563aa47c56c51eff246ae#uploading-the-sound-file-dashboard) on the [OS Notification Sound](broken://pages/8c1b185b3b39abf677e563aa47c56c51eff246ae) page. The provider only tells the OS *which* uploaded sound to play — it does not deliver the sound file itself.

> **Platform support:** Android and iOS.

***

## Shared Mechanics (Both Providers)

These OS-level rules apply no matter which service actually sends the push — they're what OneSignal/FCM are ultimately configuring underneath:

* **iOS**: the sound is set by filename **with its extension** (e.g. `custom_notify.wav`), matching exactly what you uploaded for iOS. This maps to the native APNs payload's `sound` field.
* **Android**: the sound is tied to a **Notification Channel**, referenced by the sound's resource name **without extension** (e.g. `custom_notify`). A channel's sound **cannot be changed after it's created** on a user's device — this is an Android OS restriction, so changing a channel's sound requires creating a new channel ID rather than editing the old one.

If you send push notifications through a provider other than OneSignal or FCM in the future, these same two rules still apply — only the provider-specific dashboard/API field names differ.

***

## Choosing a Provider

* Already sending through **OneSignal**? Use the [OneSignal](broken://pages/41598d1a0d3d8fe2e541dc13fbb32f25f73d5560) page — it's dashboard-only, no backend code required.
* Calling the **FCM API** directly from your own backend (not through OneSignal)? Use the [FCM](broken://pages/3a78b56c939b3b89da342987f226e0771aff18fb) page.


# Onesignal Custom Notification Sound

This page covers how to make a push notification play a custom sound when it's sent through the OneSignal Dashboard, instead of the device's default notification sound.

> This is the OneSignal-specific half of [Notification Sound](broken://pages/f84a5aa6d3b3b3327f3d92eadc7c663aa8c9270d). Before using this page, your app must already have the sound file uploaded via the Dashboard — see [Uploading the Sound File](broken://pages/fb0dcafe03edf3b8d88092c7bd4042dc3c182711#uploading-the-sound-file-dashboard) on the [OS Notification Sound](broken://pages/fb0dcafe03edf3b8d88092c7bd4042dc3c182711) page. OneSignal only tells the OS *which* uploaded sound to play, it does not deliver the sound file itself.

> **Platform support:** Android and iOS, both from the OneSignal Dashboard — no JavaScript call is involved on this page. The WebToNative JS OneSignal module (`webtonative/OneSignal`) covers player ID/tags/triggers, not sound; sound is set entirely from OneSignal's own composer and settings screens.

***

## How It Works

* **iOS**: the Dashboard's Sound field takes the sound's filename **with its extension** (e.g. `custom_notify.wav`), matching exactly what you uploaded for iOS.
* **Android**: the Dashboard's Sound field takes the sound's resource name **without extension** (e.g. `custom_notify`, for a file uploaded as `custom_notify.mp3`/`.wav`), and the sound is tied to whichever **Notification Channel** you select alongside it.
* The sound file itself is never uploaded to OneSignal directly — it must already exist in the app (uploaded per [OS Notification Sound](broken://pages/fb0dcafe03edf3b8d88092c7bd4042dc3c182711)). These OneSignal composer/settings fields just reference it by name.

***

## Setting Sound on a Single Notification

1. Log in to the [OneSignal Dashboard](https://dashboard.onesignal.com/).
2. Select the correct **App** from the app switcher.
3. Go to **Messages → New Push**.
4. Compose the notification (title, body, audience) as usual.
5. Expand **Delivery → Advanced Options** (or **Platform-specific settings**, depending on your dashboard version).
6. Under **iOS Settings**:
   * Locate the **Sound** field.
   * Enter the exact sound filename with extension (e.g. `custom_notify.wav`).
   * Leave it blank to use the system default sound.
7. Under **Android Settings**:
   * Locate the **Sound** field.
   * Enter the resource name without extension (e.g. `custom_notify`).
   * Locate the **Notification Channel** dropdown and select (or create) the channel that has this sound configured — see [Notification Channels (Android)](#notification-channels-android) below.
8. Send or schedule the notification.

***

## Notification Channels (Android)

Android notification sounds are tied to a **Notification Channel**, and a channel's sound **cannot be changed after it's created** on a user's device — this is an Android OS restriction, not a OneSignal limitation.

1. In the Dashboard, go to **Settings → Notification Channels** (or the Android platform settings panel).
2. If you need to change the sound for an existing channel, **create a new channel** (e.g. `custom_channel_v2`) instead of editing the old one — reusing an existing channel ID with a new sound will not take effect on devices that already have that channel installed.
3. Assign the desired sound resource to the new channel.
4. Reference this channel from the composer ([Setting Sound on a Single Notification](#setting-sound-on-a-single-notification), step 7) or set it as the default in app settings.

***

## Verification Checklist

* [ ] The sound file is already uploaded per [OS Notification Sound](broken://pages/fb0dcafe03edf3b8d88092c7bd4042dc3c182711#uploading-the-sound-file-dashboard), before configuring anything here.
* [ ] iOS Sound field includes the file extension; Android Sound field does not.
* [ ] Android: correct Notification Channel selected/created for the sound.
* [ ] Sent a test push from the Dashboard to a real device to confirm the sound plays (simulators are unreliable for custom sounds on iOS).
* [ ] Left the Sound field blank where the system default is intended, to avoid accidental overrides.

***

## Frequently Asked Questions

<details>

<summary>I set the sound but the default notification sound plays instead — why?</summary>

The most common cause is the filename being entered incorrectly for the platform — remember iOS needs the extension (`custom_notify.wav`) and Android does not (`custom_notify`). Double-check it matches exactly what you uploaded per [OS Notification Sound](broken://pages/fb0dcafe03edf3b8d88092c7bd4042dc3c182711).

</details>

<details>

<summary>My new sound works for fresh installs but not for existing users — why?</summary>

On Android, a Notification Channel's sound is locked in the first time it's created on a device and can't be changed afterward. If you changed the sound on an existing channel, existing users keep hearing the old sound. Create a new channel ID with the new sound and reference that instead — see [Notification Channels (Android)](#notification-channels-android).

</details>

***

## Common Pitfalls

| Symptom                                                       | Likely Cause                                                                                           |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Custom sound never plays, default plays instead               | Filename entered incorrectly (extension included/excluded incorrectly for the platform)                |
| Works for new installs, not for existing users after a change | Android channel sound is locked in — create a new channel and select it instead of editing the old one |


# Firebase Custom Notification Sound

This page covers how to make a push notification play a custom sound when you send it directly through the Firebase Cloud Messaging HTTP v1 API

> This is the FCM-specific half of [Notification Sound](broken://pages/ce4d05eac85a6edb168fcecde6545491c4d3c748). Before sending anything below, your app must already have the sound file uploaded via the Dashboard — see [Uploading the Sound File](broken://pages/96fa8998679f408165c2b9f584f47fba14a737a4#uploading-the-sound-file-dashboard) on the [OS Notification Sound](broken://pages/96fa8998679f408165c2b9f584f47fba14a737a4) page. FCM only tells the OS *which* uploaded sound to play, it does not deliver the sound file itself.

> If you send pushes through the **OneSignal Dashboard** instead of calling FCM directly, use [Custom Notification Sound via OneSignal](broken://pages/e13ece9e42da83dfa9ebfabd5e7e1e1a63bf9477) instead — this page is for apps/backends that call the FCM API directly.

***

## Endpoint

```
POST https://fcm.googleapis.com/v1/projects/{project_id}/messages:send
Authorization: Bearer {OAuth2_access_token}
Content-Type: application/json
```

***

## How Sound Works Per Platform

* **iOS**: the native APNs payload has a real `sound` field (`apns.payload.aps.sound`). Set it to the sound's filename **with extension** (e.g. `custom_notify.wav`/`.caf`), matching what you uploaded for iOS. Use `"default"` for the system sound.
* **Android**: FCM has **no native sound field**. The sound actually played is whichever sound is attached to the **Notification Channel** the notification is delivered on — so for Android, "setting the sound" really means routing the notification to the right channel, either via the native `android.notification.channel_id` field, or by sending the sound name through `data` and having your app code build the notification on that channel itself. Both approaches are shown below.

***

## Payload Structure

```jsonc
{
  "message": {
    "token": "eH3kP9vQxT2:APA91bF7sN4dR8mLzYcW1oJpX6qKvA0uZtG3nBhMwEsRfCiVdT5jHkOlPmNq",
    "android": {
      "priority": "high",
      "notification": {
        "channel_id": "high" // Android: routes to a channel that already has the custom sound attached — see "Android: Channel-Based Sound" below
      }
    },
    "apns": {
      "headers": {
        "apns-priority": "10"
      },
      "payload": {
        "aps": {
          "alert": {
            "title": "Your order has shipped",
            "body": "Order #48213 is on its way and will arrive by Thursday."
          },
          "sound": "custom_notify.wav", // iOS: native APNs sound, played by the OS when the app is backgrounded/killed
          "mutable-content": 1
        }
      }
    },
    "data": {
      "title": "Your order has shipped",
      "body": "Order #48213 is on its way and will arrive by Thursday.",
      "sound": "custom_notify", // Android: sound key your app code reads if you're building the notification yourself instead of using android.notification.channel_id
      "channel_id": "high"
    }
  }
}
```

***

## Field Reference

### `message` (object, required)

Root wrapper for the entire notification request. FCM requires exactly one target field inside it — here it's `token`.

| Field   | Type   | Description                                                                                                        |
| ------- | ------ | ------------------------------------------------------------------------------------------------------------------ |
| `token` | string | The FCM registration token of the target device. Identifies the single device/app instance to receive the message. |

***

### `android.notification.channel_id` — Channel-Based Sound (Recommended for Android)

| Field        | Type   | Description                                                                                                                                                                                                                                                                       |
| ------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `channel_id` | string | The ID of an Android Notification Channel that **already exists on the device** with your custom sound attached (created via `NotificationManager.createNotificationChannel()` in your Android app code). FCM applies this channel automatically — no app code needed to read it. |

If the channel doesn't already exist on the device, Android falls back to a default channel and plays the default sound.

***

### `data.sound` / `data.channel_id` — App-Handled Sound (Alternative for Android)

Use this instead of `android.notification.channel_id` only if your app already builds notifications manually in code (custom rendering, rich media, etc.) rather than relying on FCM's auto-displayed notification.

| Field        | Type   | Description                                                                                                                                                                                                                                                                 |
| ------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sound`      | string | Sound resource identifier your own app code reads to pick a sound or channel when constructing the notification. **Not applied automatically** by FCM/Android — your app must read `data.sound` itself.                                                                     |
| `channel_id` | string | Target Android Notification Channel ID. Like `sound`, this sits in the custom `data` block, so it is *not* auto-applied — your app code must read this value and pass it explicitly (e.g. `NotificationCompat.Builder(context, channelId)`) when building the notification. |

> **All `data` values must be strings.** FCM rejects numbers/booleans in `data` — send `"1"`/`"true"` instead.

***

### `apns.payload.aps.sound` — iOS Sound

Native Apple Push payload (`aps` dictionary), passed through unmodified by FCM to APNs.

| Field             | Type      | Description                                                                                                                                                                                                                                                                                              |
| ----------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sound`           | string    | Name of a custom sound file uploaded for the app (e.g. `custom_notify.caf`/`.wav`), matching what's uploaded per [OS Notification Sound](broken://pages/96fa8998679f408165c2b9f584f47fba14a737a4). Played natively by APNs when the app is backgrounded or killed. Use `"default"` for the system sound. |
| `mutable-content` | int (0/1) | When `1`, allows a Notification Service Extension to intercept and modify the notification before display. Not required just for a custom sound — only needed if you're also modifying content (e.g. downloading media).                                                                                 |

***

### `apns.headers.apns-priority`

| Field           | Type   | Description                                                                                                                                                         |
| --------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apns-priority` | string | `"10"` = send immediately — **required** for a notification with a custom sound to actually play it; `"5"` delivers silently in the background with no sound/alert. |

***

## Sample cURL Request

```bash
curl -X POST "https://fcm.googleapis.com/v1/projects/YOUR_PROJECT_ID/messages:send" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \
  -d '{
    "message": {
      "token": "eH3kP9vQxT2:APA91bF7sN4dR8mLzYcW1oJpX6qKvA0uZtG3nBhMwEsRfCiVdT5jHkOlPmNq",
      "android": {
        "priority": "high",
        "notification": { "channel_id": "high" }
      },
      "apns": {
        "headers": { "apns-priority": "10" },
        "payload": {
          "aps": {
            "alert": { "title": "Your order has shipped", "body": "Order #48213 is on its way and will arrive by Thursday." },
            "sound": "custom_notify.wav",
            "mutable-content": 1
          }
        }
      },
      "data": {
        "title": "Your order has shipped",
        "body": "Order #48213 is on its way and will arrive by Thursday.",
        "sound": "custom_notify",
        "channel_id": "high"
      }
    }
  }'
```

***

## Implementation Checklist

* [ ] Sound file uploaded for the Android/iOS app per [OS Notification Sound](broken://pages/96fa8998679f408165c2b9f584f47fba14a737a4#uploading-the-sound-file-dashboard)
* [ ] Android: a Notification Channel created on-device (via app code) with the custom sound attached, and its ID passed as `android.notification.channel_id`
* [ ] iOS: `apns.payload.aps.sound` set to the exact uploaded filename, with extension
* [ ] `apns.headers.apns-priority` set to `"10"` (a custom sound will not play at priority `"5"`)
* [ ] Sent a real test push to a physical device on each platform to confirm the sound plays (simulators are unreliable for custom sounds on iOS)

***

## Frequently Asked Questions

<details>

<summary>Why does my custom sound not play on Android even though I set `data.sound`?</summary>

`data` is a custom payload — Android/FCM never reads it automatically. Either use `android.notification.channel_id` pointing at a channel that already has your sound attached (so FCM applies it for you), or read `data.sound`/`data.channel_id` yourself in app code when constructing the notification.

</details>

<details>

<summary>Why does my custom sound not play on iOS?</summary>

Check two things: `apns.payload.aps.sound` must exactly match the uploaded filename including its extension, and `apns-priority` must be `"10"` — at `"5"` iOS delivers the notification silently with no sound regardless of what's in `sound`.

</details>

<details>

<summary>I changed the Android channel's sound but existing users still hear the old one — why?</summary>

This is an Android OS restriction, not an FCM one — a Notification Channel's sound is fixed the first time it's created on a device and can't be changed afterward. Create a new channel ID with the new sound and point `channel_id` at that instead of editing the old channel.

</details>

<details>

<summary>Should I send through FCM directly or through OneSignal?</summary>

See [Choosing a Provider](broken://pages/ce4d05eac85a6edb168fcecde6545491c4d3c748#choosing-a-provider) on the Notification Sound page — in short, use OneSignal if you already send pushes through its Dashboard, and this FCM page only if your backend calls the FCM API directly.

</details>

***

## Official Documentation References

* [FCM HTTP v1 API — Send Messages](https://firebase.google.com/docs/cloud-messaging/send-message) — overview of building and sending messages via the v1 API.
* [`projects.messages.send` REST Reference](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages/send) — full request/response schema for the `messages:send` endpoint.
* [`ApnsConfig` Reference](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#apnsconfig) — schema for the `apns` block (`headers`, `payload`).
* [Apple: Generating a Remote Notification (`aps` payload)](https://developer.apple.com/documentation/usernotifications/generating-a-remote-notification) — canonical spec for `alert`, `sound`, `mutable-content`, and other `aps` keys.
* [Create and Manage Notification Channels (Android)](https://developer.android.com/develop/ui/views/notifications/channels) — `NotificationManager.createNotificationChannel()` and channel importance/sound behavior.


# Custom Sound

Lets your app play a custom sound

Lets your app play a **custom sound,** uploaded once via the **WebToNative Dashboard,** on demand from your website's JavaScript, and reuses that same sound as an [in-app haptic accompaniment](broken://pages/6be13a7605a380f9c31315f94affb54eafce58ba). This is a *local* sound trigger: your JavaScript decides when it plays. It is separate from (but works alongside) a *remote push notification* playing a custom sound automatically when it arrives — see [Custom Notification Sound via OneSignal](broken://pages/9b19cdb713d85c83700bbb9c5e5f612164e64ef8) and [Custom Notification Sound via Firebase Cloud Messaging (FCM)](broken://pages/42012545f97d4ac7ca7397fb831af4e404d76c3a) for that.

> You'll need to import the javascript file in your website before starting from this [link](https://docs.webtonative.com/javascript-apis/getting-started).

> **Platform support:** Android and iOS.

***

## How It Works

One uploaded sound file can be used in three different places, and all three reference it **by the same name**:

1. **`Sound.play(soundName)`** — plays it immediately, triggered directly from your JavaScript (e.g. a chat "message sent" ping, a game effect).
2. **`Haptics.trigger({ effect, soundName })`** — plays it alongside a haptic vibration effect, in one call. See [Haptic Feedback](broken://pages/6be13a7605a380f9c31315f94affb54eafce58ba).
3. **A remote push notification** — OneSignal or FCM tells the OS to play it automatically when a push arrives. This requires a file with the *exact same name* to already be uploaded per this page — see [OneSignal](broken://pages/9b19cdb713d85c83700bbb9c5e5f612164e64ef8) / [FCM](broken://pages/42012545f97d4ac7ca7397fb831af4e404d76c3a) for the sending side.

The sound file itself is never sent from your JavaScript or from a push payload — only its **name** is. It must already be uploaded to your app via the Dashboard before you reference it from any of the three places above.

***

## 1. Uploading the Sound File (Dashboard)

Go to your **WebToNative Dashboard → Add-ons → Notification → OS Notification Sound** and upload the sound file under the platform's sound setting. Android and iOS each have their **own separate upload field** — uploading for one platform does not make the file available on the other, so upload the same audio to both fields if you want consistent behavior across platforms.

* **Reference name:** the name you pass to `Sound.play` / `soundName` is derived from the uploaded file's name, minus its extension.
* **Format:** only `.mp3` and `.wav` files can be uploaded — these are the only supported formats on both platforms.
* **Extension:** `.mp3` is the default — if you call `Sound.play("your_sound_name")` **without an extension**, `.mp3` is assumed automatically, so you don't need to pass anything if you uploaded an `.mp3` file. If you uploaded a `.wav` file, you must pass the extension explicitly — `Sound.play("your_sound_name.wav")` — or the lookup will assume `.mp3` and fail to find it.

> Changes take effect on your app's next build — if you're testing on a device already running an older build, rebuild/reinstall after uploading a new or changed sound file.

***

## JavaScript API Reference

### Sound.play

Plays an uploaded sound immediately.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Sound.play("your_sound_name");
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { play } from "webtonative/Sound";

play("your_sound_name");
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key         | Type     | Required | Description                                                                                                                                                                       |
| ----------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `soundName` | `String` | Yes      | Name of the uploaded sound file, with or without extension (see [Uploading the Sound File](#1.-uploading-the-sound-file-dashboard)). If no extension is given, `.mp3` is assumed. |

This function does not take a `callback` — it is fire-and-forget, with no response to read. If the named sound isn't found or fails to play, it fails **silently** on both platforms — see [Troubleshooting](#troubleshooting) below.

***

### Haptics.trigger with a sound

`Haptics.trigger` also accepts an optional `soundName`, so one call can trigger a haptic effect and an uploaded sound together. Full parameter/effect reference lives on the [Haptic Feedback](broken://pages/6be13a7605a380f9c31315f94affb54eafce58ba) page — this is the sound-specific addition to that same function.

{% tabs %}
{% tab title="Plain Javascript" %}

```javascript
window.WTN.Haptics.trigger({
  effect: "impactMedium",
  soundName: "your_sound_name",
});
```

{% endtab %}

{% tab title="npm" %}

```javascript
import { trigger } from "webtonative/Haptics";

trigger({
  effect: "impactMedium",
  soundName: "your_sound_name",
});
```

{% endtab %}
{% endtabs %}

**Parameters:**

| Key         | Type     | Required | Description                                                                                                                                                                            |
| ----------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `effect`    | `String` | No       | The vibration pattern to play. See [Haptic Feedback](broken://pages/6be13a7605a380f9c31315f94affb54eafce58ba#trigger) for supported values.                                            |
| `soundName` | `String` | No       | Name of an uploaded sound file to play alongside the haptic effect, using the exact same lookup rules as [`Sound.play`](#sound.play) above. Omit it to trigger the haptic effect only. |

> Internally this plays the sound through the same path as `Sound.play` — there's no behavioral difference between calling `Sound.play("x")` directly versus passing `soundName: "x"` here, other than the haptic effect firing at the same time.

***

## Common Patterns

### Playing a Sound Only on Notification-Relevant Actions

Reserve `Sound.play` for actions that should feel like a native alert (message received in an open chat, item added to cart) rather than every UI tap — pair it with a haptic effect for actions that also warrant one:

```javascript
import { play } from "webtonative/Sound";
import { trigger } from "webtonative/Haptics";

function onChatMessageReceived() {
  // Sound only — this happens often, a haptic on every message would be excessive
  play("chat_ping");
}

function onOrderConfirmed() {
  // Sound + haptic together — a rarer, higher-significance event
  trigger({
    effect: "notificationSuccess",
    soundName: "order_confirmed",
  });
}
```

***

## Troubleshooting

* **Nothing plays, no error in the console:** this is expected if the sound isn't found — both platforms fail silently with no callback to JavaScript. Double-check that the file was uploaded to the correct platform's field on the Dashboard, that you've rebuilt/reinstalled since uploading, and that the name (and extension, if you passed one) matches exactly — see [Uploading the Sound File](#1.-uploading-the-sound-file-dashboard).

***

## Frequently Asked Questions

<details>

<summary>Do I need to enable an add-on on the WebToNative Dashboard to use this?</summary>

Yes — you need to add the **OS Notification Sound** add-on from WebToNative. Once it's added, you can upload your sound file under **OS Notification Sound**, in the **Notification** section of **Add-ons**, for each platform you want it on. The JavaScript bridge works as soon as it's imported as usual.

</details>

<details>

<summary>Can I use the same sound file for `Sound.play`, `Haptics.trigger`, and a push notification?</summary>

Yes — upload it once per platform via the Dashboard, and reference it by the same name from all three. For the push notification side, the platform-specific dashboard/API fields have their own naming conventions (extension required on iOS, omitted on Android) — see [OneSignal](broken://pages/9b19cdb713d85c83700bbb9c5e5f612164e64ef8) and [FCM](broken://pages/42012545f97d4ac7ca7397fb831af4e404d76c3a).

</details>

<details>

<summary>What audio formats are supported?</summary>

Only `.mp3` and `.wav` — these are the only formats you can upload, on either platform. `.mp3` is assumed by default if you don't pass an extension in `Sound.play`; for `.wav`, pass the extension explicitly.

</details>

<details>

<summary>Why did I get `PERMANENTLY_BLOCKED` or no sound after asking for notification permission?</summary>

That's unrelated to this page — `Sound.play`/`Haptics.trigger` play immediately from JavaScript and don't require notification permission at all. Notification permission only affects whether the OS shows/plays a *remote push* automatically; see [Permission](broken://pages/9eb1e42fb58a4004b0fdf989642d482da32dbded) for permission handling, and [OneSignal](broken://pages/9b19cdb713d85c83700bbb9c5e5f612164e64ef8)/[FCM](broken://pages/42012545f97d4ac7ca7397fb831af4e404d76c3a) for push-specific sound setup.

</details>


# OneSignal Sound

This page covers how to make a push notification play a **custom sound** when it's sent through the **OneSignal Dashboard**, instead of the device's default notification sound.

> This is the OneSignal-specific half of custom sound support. Before using this page, your app must already have the sound file uploaded via the Dashboard and working with [`Sound.play`](broken://pages/da4194201edb57e44576fd614f2e90a08b109e95) — OneSignal only tells the OS *which* uploaded sound to play, it does not deliver the sound file itself. See [OS Notification Sound](broken://pages/da4194201edb57e44576fd614f2e90a08b109e95) for how to upload the file for your Android/iOS app.

> **Platform support:** Android and iOS, both from the OneSignal Dashboard — no JavaScript call is involved on this page. The WebToNative JS OneSignal module (`webtonative/OneSignal`) covers player ID/tags/triggers, not sound; sound is set entirely from OneSignal's own composer and settings screens.

***

## How It Works

* **iOS**: the Dashboard's Sound field takes the sound's filename **with its extension** (e.g. `custom_notify.wav`), matching exactly what you uploaded for iOS.
* **Android**: the Dashboard's Sound field takes the sound's resource name **without extension** (e.g. `custom_notify`, for a file uploaded as `custom_notify.mp3`/`.wav`), and the sound is tied to whichever **Notification Channel** you select alongside it.
* The sound file itself is never uploaded to OneSignal directly — it must already exist in the app (uploaded per [OS Notification Sound](broken://pages/da4194201edb57e44576fd614f2e90a08b109e95)). These OneSignal composer/settings fields just reference it by name.

***

## Setting Sound on a Single Notification

1. Log in to the [OneSignal Dashboard](https://dashboard.onesignal.com/).
2. Select the correct **App** from the app switcher.
3. Go to **Messages → New Push**.
4. Compose the notification (title, body, audience) as usual.
5. Expand **Delivery → Advanced Options** (or **Platform-specific settings**, depending on your dashboard version).
6. Under **iOS Settings**:
   * Locate the **Sound** field.
   * Enter the exact sound filename with extension (e.g. `custom_notify.wav`).
   * Leave it blank to use the system default sound.
7. Under **Android Settings**:
   * Locate the **Sound** field.
   * Enter the resource name without extension (e.g. `custom_notify`).
   * Locate the **Notification Channel** dropdown and select (or create) the channel that has this sound configured — see [Notification Channels (Android)](#notification-channels-android) below.
8. Send or schedule the notification.

***

## Notification Channels (Android)

Android notification sounds are tied to a **Notification Channel**, and a channel's sound **cannot be changed after it's created** on a user's device — this is an Android OS restriction, not a OneSignal limitation.

1. In the Dashboard, go to **Settings → Notification Channels** (or the Android platform settings panel).
2. If you need to change the sound for an existing channel, **create a new channel** (e.g. `custom_channel_v2`) instead of editing the old one — reusing an existing channel ID with a new sound will not take effect on devices that already have that channel installed.
3. Assign the desired sound resource to the new channel.
4. Reference this channel from the composer ([Setting Sound on a Single Notification](#setting-sound-on-a-single-notification), step 7) or set it as the default in app settings.

***

## Verification Checklist

* [ ] The sound file is already uploaded per [OS Notification Sound](broken://pages/da4194201edb57e44576fd614f2e90a08b109e95), before configuring anything here.
* [ ] iOS Sound field includes the file extension; Android Sound field does not.
* [ ] Android: correct Notification Channel selected/created for the sound.
* [ ] Sent a test push from the Dashboard to a real device to confirm the sound plays (simulators are unreliable for custom sounds on iOS).
* [ ] Left the Sound field blank where the system default is intended, to avoid accidental overrides.

***

## Frequently Asked Questions

<details>

<summary>I set the sound but the default notification sound plays instead — why?</summary>

The most common cause is the filename being entered incorrectly for the platform — remember iOS needs the extension (`custom_notify.wav`) and Android does not (`custom_notify`). Double-check it matches exactly what you uploaded per [OS Notification Sound](broken://pages/da4194201edb57e44576fd614f2e90a08b109e95).

</details>

<details>

<summary>My new sound works for fresh installs but not for existing users — why?</summary>

On Android, a Notification Channel's sound is locked in the first time it's created on a device and can't be changed afterward. If you changed the sound on an existing channel, existing users keep hearing the old sound. Create a new channel ID with the new sound and reference that instead — see [Notification Channels (Android)](#notification-channels-android).

</details>

***

## Common Pitfalls

| Symptom                                                       | Likely Cause                                                                                           |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Custom sound never plays, default plays instead               | Filename entered incorrectly (extension included/excluded incorrectly for the platform)                |
| Works for new installs, not for existing users after a change | Android channel sound is locked in — create a new channel and select it instead of editing the old one |


# FCM Sound

This page covers how to make a push notification play a **custom sound** when you send it directly through the **Firebase Cloud Messaging HTTP v1 API**, as an alternative to sending through OneSignal.

> This is the FCM-specific half of custom sound support. Before sending anything below, your app must already have the sound file uploaded via the Dashboard and working with [`Sound.play`](broken://pages/09988be90eb079f645463201974d762252eb3a17) — FCM only tells the OS *which* uploaded sound to play, it does not deliver the sound file itself. See [OS Notification Sound](broken://pages/09988be90eb079f645463201974d762252eb3a17) for how to upload the file for your Android/iOS app.

> If you send pushes through the **OneSignal Dashboard** instead of calling FCM directly, use [Custom Notification Sound via OneSignal](broken://pages/779a0ef0befe98707a901247ecef9ccf836170c0) instead — this page is for apps/backends that call the FCM API directly.

***

## Endpoint

```
POST https://fcm.googleapis.com/v1/projects/{project_id}/messages:send
Authorization: Bearer {OAuth2_access_token}
Content-Type: application/json
```

***

## How Sound Works Per Platform

* **iOS**: the native APNs payload has a real `sound` field (`apns.payload.aps.sound`). Set it to the sound's filename **with extension** (e.g. `custom_notify.wav`/`.caf`), matching what you uploaded for iOS. Use `"default"` for the system sound.
* **Android**: FCM has **no native sound field**. The sound actually played is whichever sound is attached to the **Notification Channel** the notification is delivered on — so for Android, "setting the sound" really means routing the notification to the right channel, either via the native `android.notification.channel_id` field, or by sending the sound name through `data` and having your app code build the notification on that channel itself. Both approaches are shown below.

***

## Payload Structure

```jsonc
{
  "message": {
    "token": "eH3kP9vQxT2:APA91bF7sN4dR8mLzYcW1oJpX6qKvA0uZtG3nBhMwEsRfCiVdT5jHkOlPmNq",
    "android": {
      "priority": "high",
      "notification": {
        "channel_id": "high" // Android: routes to a channel that already has the custom sound attached — see "Android: Channel-Based Sound" below
      }
    },
    "apns": {
      "headers": {
        "apns-priority": "10"
      },
      "payload": {
        "aps": {
          "alert": {
            "title": "Your order has shipped",
            "body": "Order #48213 is on its way and will arrive by Thursday."
          },
          "sound": "custom_notify.wav", // iOS: native APNs sound, played by the OS when the app is backgrounded/killed
          "mutable-content": 1
        }
      }
    },
    "data": {
      "title": "Your order has shipped",
      "body": "Order #48213 is on its way and will arrive by Thursday.",
      "sound": "custom_notify", // Android: sound key your app code reads if you're building the notification yourself instead of using android.notification.channel_id
      "channel_id": "high"
    }
  }
}
```

***

## Field Reference

### `message` (object, required)

Root wrapper for the entire notification request. FCM requires exactly one target field inside it — here it's `token`.

| Field   | Type   | Description                                                                                                        |
| ------- | ------ | ------------------------------------------------------------------------------------------------------------------ |
| `token` | string | The FCM registration token of the target device. Identifies the single device/app instance to receive the message. |

***

### `android.notification.channel_id` — Channel-Based Sound (Recommended for Android)

| Field        | Type   | Description                                                                                                                                                                                                                                                                       |
| ------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `channel_id` | string | The ID of an Android Notification Channel that **already exists on the device** with your custom sound attached (created via `NotificationManager.createNotificationChannel()` in your Android app code). FCM applies this channel automatically — no app code needed to read it. |

If the channel doesn't already exist on the device, Android falls back to a default channel and plays the default sound.

***

### `data.sound` / `data.channel_id` — App-Handled Sound (Alternative for Android)

Use this instead of `android.notification.channel_id` only if your app already builds notifications manually in code (custom rendering, rich media, etc.) rather than relying on FCM's auto-displayed notification.

| Field        | Type   | Description                                                                                                                                                                                                                                                                 |
| ------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sound`      | string | Sound resource identifier your own app code reads to pick a sound or channel when constructing the notification. **Not applied automatically** by FCM/Android — your app must read `data.sound` itself.                                                                     |
| `channel_id` | string | Target Android Notification Channel ID. Like `sound`, this sits in the custom `data` block, so it is *not* auto-applied — your app code must read this value and pass it explicitly (e.g. `NotificationCompat.Builder(context, channelId)`) when building the notification. |

> **All `data` values must be strings.** FCM rejects numbers/booleans in `data` — send `"1"`/`"true"` instead.

***

### `apns.payload.aps.sound` — iOS Sound

Native Apple Push payload (`aps` dictionary), passed through unmodified by FCM to APNs.

| Field             | Type      | Description                                                                                                                                                                                                                                                                                              |
| ----------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sound`           | string    | Name of a custom sound file uploaded for the app (e.g. `custom_notify.caf`/`.wav`), matching what's uploaded per [OS Notification Sound](broken://pages/09988be90eb079f645463201974d762252eb3a17). Played natively by APNs when the app is backgrounded or killed. Use `"default"` for the system sound. |
| `mutable-content` | int (0/1) | When `1`, allows a Notification Service Extension to intercept and modify the notification before display. Not required just for a custom sound — only needed if you're also modifying content (e.g. downloading media).                                                                                 |

***

### `apns.headers.apns-priority`

| Field           | Type   | Description                                                                                                                                                         |
| --------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apns-priority` | string | `"10"` = send immediately — **required** for a notification with a custom sound to actually play it; `"5"` delivers silently in the background with no sound/alert. |

***

## Sample cURL Request

```bash
curl -X POST "https://fcm.googleapis.com/v1/projects/YOUR_PROJECT_ID/messages:send" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \
  -d '{
    "message": {
      "token": "eH3kP9vQxT2:APA91bF7sN4dR8mLzYcW1oJpX6qKvA0uZtG3nBhMwEsRfCiVdT5jHkOlPmNq",
      "android": {
        "priority": "high",
        "notification": { "channel_id": "high" }
      },
      "apns": {
        "headers": { "apns-priority": "10" },
        "payload": {
          "aps": {
            "alert": { "title": "Your order has shipped", "body": "Order #48213 is on its way and will arrive by Thursday." },
            "sound": "custom_notify.wav",
            "mutable-content": 1
          }
        }
      },
      "data": {
        "title": "Your order has shipped",
        "body": "Order #48213 is on its way and will arrive by Thursday.",
        "sound": "custom_notify",
        "channel_id": "high"
      }
    }
  }'
```

***

## Implementation Checklist

* [ ] Sound file uploaded for the Android/iOS app per [OS Notification Sound](broken://pages/09988be90eb079f645463201974d762252eb3a17)
* [ ] Android: a Notification Channel created on-device (via app code) with the custom sound attached, and its ID passed as `android.notification.channel_id`
* [ ] iOS: `apns.payload.aps.sound` set to the exact uploaded filename, with extension
* [ ] `apns.headers.apns-priority` set to `"10"` (a custom sound will not play at priority `"5"`)
* [ ] Sent a real test push to a physical device on each platform to confirm the sound plays (simulators are unreliable for custom sounds on iOS)

***

## Frequently Asked Questions

<details>

<summary>Why does my custom sound not play on Android even though I set `data.sound`?</summary>

`data` is a custom payload — Android/FCM never reads it automatically. Either use `android.notification.channel_id` pointing at a channel that already has your sound attached (so FCM applies it for you), or read `data.sound`/`data.channel_id` yourself in app code when constructing the notification.

</details>

<details>

<summary>Why does my custom sound not play on iOS?</summary>

Check two things: `apns.payload.aps.sound` must exactly match the uploaded filename including its extension, and `apns-priority` must be `"10"` — at `"5"` iOS delivers the notification silently with no sound regardless of what's in `sound`.

</details>

<details>

<summary>I changed the Android channel's sound but existing users still hear the old one — why?</summary>

This is an Android OS restriction, not an FCM one — a Notification Channel's sound is fixed the first time it's created on a device and can't be changed afterward. Create a new channel ID with the new sound and point `channel_id` at that instead of editing the old channel.

</details>

<details>

<summary>Should I send through FCM directly or through OneSignal?</summary>

If you're already sending pushes through the OneSignal Dashboard, use [Custom Notification Sound via OneSignal](broken://pages/779a0ef0befe98707a901247ecef9ccf836170c0) — it's simpler and needs no backend code. Use this FCM page only if your backend calls the FCM API directly instead of going through OneSignal.

</details>

***

## Official Documentation References

* [FCM HTTP v1 API — Send Messages](https://firebase.google.com/docs/cloud-messaging/send-message) — overview of building and sending messages via the v1 API.
* [`projects.messages.send` REST Reference](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages/send) — full request/response schema for the `messages:send` endpoint.
* [`ApnsConfig` Reference](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#apnsconfig) — schema for the `apns` block (`headers`, `payload`).
* [Apple: Generating a Remote Notification (`aps` payload)](https://developer.apple.com/documentation/usernotifications/generating-a-remote-notification) — canonical spec for `alert`, `sound`, `mutable-content`, and other `aps` keys.
* [Create and Manage Notification Channels (Android)](https://developer.android.com/develop/ui/views/notifications/channels) — `NotificationManager.createNotificationChannel()` and channel importance/sound behavior.


# WordPress Plugin Guide

Install and configure the WebToNative WordPress plugin to integrate native app features, including push notifications, biometrics, and media controls.

## **WebtoNative Plugin for WordPress: Quick Installation and Overview**

***

### **Introduction**

The WebtoNative plugin for WordPress bridges the gap between your website and native mobile applications, allowing seamless integration with mobile device features. It has several sub-plugins to handle various native operations, making it a powerful tool for enhancing your app experience.

***

### **Installation Guide**

1. **Download the Plugin**\
   Obtain the WebtoNative plugin ZIP file from the official source.
2. **Upload the Plugin**
   * Log in to your WordPress admin dashboard.
   * Go to **Plugins > Add New > Upload Plugin**.
   * Upload the downloaded ZIP file and click **Install Now**.
3. **Activate the Plugin**
   * After installation, click **Activate** to enable the plugin.
4. **Sub-Plugin Configuration**
   * Navigate to the **WebtoNative Settings or Settings** menu in the WordPress admin panel.
   * Enable or configure sub-plugins like **Push Notifications**, **Biometric Authentication**, or **Radio Player**.
   * Follow the instructions for each sub-plugin to ensure they work seamlessly with your app.

***

### **How the WebtoNative Plugin Helps**

1. **Mobile-Native Features**\
   Integrates your website with native device functionalities like push notifications, biometric authentication, media playback, and more.
2. **Seamless User Experience**\
   Enhances the user experience by synchronizing web content with native app components, such as notifications or media controls.
3. **Customizable Sub-Plugins**\
   Includes various sub-plugins tailored to specific needs:
   * **Push Notifications**: Send targeted notifications to app users.
   * **Biometric Authentication**: Secure user logins with fingerprint or face recognition.
   * **Radio Player**: Play and manage media with native media player controls.
4. **Easy Management**\
   Manage all native features directly from the WordPress admin panel without complex coding or setup.

### **Support**

For questions or issues, contact [WebtoNative Support](mailto:support@webtonative.com).

***

### **Conclusion**

The WebtoNative plugin simplifies integrating native features into your mobile app, enhancing functionality and user engagement. Whether you want to add notifications, secure authentication, or rich media playback, this plugin has you covered.


# Push notification Guide (WooCommerce)

Enable WooCommerce push notifications with the WebToNative WordPress plugin. Send instant order updates and customer alerts to your mobile app.

## 1. Admin Setup

### Step 1:  Enable Push Notifications

1. Navigate to **WebToNative Settings > Push** Notification in the WordPress admin dashboard.

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeNj9Z7r73RTopJWP61-rWU1L-37ree5NjSzb4B_DeiYafi0C8cTJrH3QIM-QzdseVWVtrrCpcWIRbRMlNCJyZU9drBPX5dix1u4JMmzJcK2jHbOcHBVdxX45qSuTcsizIYJyIebQ?key=8fypB0WwX012kuShdOKyH1VA" alt="" width="375"><figcaption></figcaption></figure>

2. Enable the **"Enable Push Notification"** checkbox to activate push notifications

### Step 2:  Configure OneSignal Settings

1. Fill in the required fields:
   * **OneSignal App ID**: Enter your OneSignal application ID.
   * **REST API Key**: Enter the REST API key for your OneSignal account.

<figure><img src="/files/VTwfB5UKUisPwh3osBkh" alt=""><figcaption></figcaption></figure>

### Step 3:  Customize Order Status Messages

1. In the Customize Messages section:
   * Provide a **custom message** for each WooCommerce order status (e.g., "Processing," "Completed").
   * These messages will be sent to users as notifications when their order status changes.

### Step 4:  Save Settings

* Click Save Changes to apply the configuration.

## 2. Automatic Notifications for WooCommerce

* Push notifications are automatically triggered when a WooCommerce order status changes.
* The system:
  * Fetches the order details and user information.
  * Hashes the user ID with a secret key to generate an *<mark style="color:green;">**externalUserId**</mark>*.
  * Sends the custom message for the new order status via the OneSignal API.

## 3. Key Features

1. **Dynamic External User ID**: Ensures secure identification of users via hashed <mark style="color:green;">externalUserId</mark>.
2. **Customizable Notifications**: Allows custom messages for different WooCommerce order statuses.
3. **Automatic WooCommerce Event Handling**: Triggers notifications on order status changes.

## 4. Troubleshooting

* Notifications Not Sending?
  * Ensure push notifications are enabled in the settings.
  * Verify the OneSignal App ID and REST API Key.
  * Check that the wton\_notification\_key is properly set.
* Frontend Integration Issues?
  * Confirm the WTN.OneSignal object is available in the app environment.
  * Check the browser console for errors related to the script.


# WordPress In-App Purchase Guide

Configure in-app purchases with the WebToNative WordPress plugin. Enable secure subscriptions and digital purchases for your mobile app.

### What is In-App Purchase (IAP)?

In-app purchase (IAP) is a feature that allows users to buy digital products and services within an app. These purchases can include premium features, subscriptions, virtual goods, and more. IAP is managed by the respective app stores:

* **iOS (Apple App Store)**: Uses StoreKit to handle in-app purchases.
* **Android (Google Play Store)**: Uses Google Play Billing for in-app transactions.

### Types of In-App Purchases

Both iOS and Android offer similar types of in-app purchases:

1. **Consumables**: Items that are used once and can be purchased again (e.g., in-game currency).
2. **Non-Consumables**: One-time purchases that provide permanent access to a feature (e.g., removing ads).
3. **Subscriptions**: Recurring purchases that grant access to content or services over time.

### Configuring In-App Purchases

Before integrating an IAP plugin, you must configure in-app purchases in your respective app store.

* [**iOS & Android IAP Configuration Guide**](https://docs.webtonative.com/website-plugins/wordpress/in-app-purchase/in-app-purchase-iap-configuration-guide-for-ios-and-android)

Following these steps ensures a smooth setup before implementing an IAP plugin in your app.


# Configuration Guide for iOS and Android

Configure iOS and Android in-app purchases with the WebToNative WordPress plugin for secure subscriptions and digital product purchases.

## iOS In-App Purchase (IAP) Configuration

### Step 1: Access the Apple Developer Account

1. Open an [Apple Developer Account](https://developer.apple.com/account).

<figure><img src="/files/khF7jAAJFZsZ0BsURkCh" alt="" width="563"><figcaption></figcaption></figure>

2. Click on **Apps**.
3. Select your app or create a new app.

<figure><img src="/files/Y8qsWI4vFsnySS9JExil" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/E766rj5yhmOw9vqOclBX" alt=""><figcaption></figcaption></figure>

### Step 2: Configure In-App Purchase or Subscriptions

#### ============== Configure In-App Purchase ==============

1. Navigate to **In-App Purchases**.

<figure><img src="/files/lJtik5uP2T39DBhHK6xF" alt=""><figcaption></figcaption></figure>

2. Click on the **Create** button.

<figure><img src="/files/kEZhllIA83tnAvp2DReX" alt=""><figcaption></figcaption></figure>

3. Choose **Create an In-App Purchase**.

<figure><img src="/files/fCJawAdqpYcVb7jKW8et" alt=""><figcaption></figcaption></figure>

4. Enter the required details:

<figure><img src="/files/KlfQY8FdFjjXRYbzNrBi" alt=""><figcaption></figcaption></figure>

* **Type** (Consumable, Non-Consumable, Auto-renewable Subscription, Non-renewing Subscription)
* **Reference Name**
* **Product ID**

5. Configure the in-app purchase:

<div><figure><img src="/files/lMFp0m2KzKzZt7N1YzoU" alt=""><figcaption></figcaption></figure> <figure><img src="/files/n1A4dnatUM2l5Kr37aoL" alt=""><figcaption></figcaption></figure> <figure><img src="/files/SY6wKkxT5258jK5pWJ0m" alt=""><figcaption></figcaption></figure> <figure><img src="/files/DUcfuuowT5iTJs61u0KG" alt=""><figcaption></figcaption></figure></div>

* **Availability**: Specify regions where the in-app purchase will be available.
* **Pricing**: Set pricing tiers.
* **Localization**: Provide translations for different regions.
* **Image**: Upload an appropriate image for the product.
* **Screenshot**: Add screenshots showcasing the purchase flow.
* **Review Notes**: Provide additional information for Apple’s review.

#### ============== Configure Subscriptions ==============

1. Navigate to **Subscriptions**.

<figure><img src="/files/PX3kl7fDyVtkvPF01Kqy" alt=""><figcaption></figcaption></figure>

2. Click on **Create Subscription Group**.

<div align="left"><figure><img src="/files/6eYqWwXicYd6iYppZ70s" alt="" width="375"><figcaption></figcaption></figure> <figure><img src="/files/e3szr9iYKkO1C5MQWSUV" alt="" width="375"><figcaption></figcaption></figure></div>

3. Enter a **Reference Name**.
4. Create a **Subscription**.

<div align="left"><figure><img src="/files/ptwsHfh6poXI7xxvdC2L" alt="" width="375"><figcaption></figcaption></figure> <figure><img src="/files/ZeavOQwCWwE2WThUQNM4" alt="" width="375"><figcaption></figcaption></figure></div>

<figure><img src="/files/E1KlS61RztqFxtNCur29" alt=""><figcaption></figcaption></figure>

5. Configure the subscription:

<div><figure><img src="/files/B5DJhSOSadx0ixu9LyT4" alt=""><figcaption></figcaption></figure> <figure><img src="/files/9LeQ5LPh0Uirkga3g9u2" alt=""><figcaption></figcaption></figure> <figure><img src="/files/qIAYTsF8kwJ9aw1OeUfP" alt=""><figcaption></figcaption></figure></div>

* **Availability**: Define regions.
* **Pricing**: Set up subscription pricing.
* **Localization**: Add translations.
* **Image**: Upload an appropriate image.
* **Tax Category**: Assign relevant tax information.
* **Screenshot**: Provide screenshots of the subscription process.
* **Review Notes**: Include additional notes for Apple’s review.

### Step 3: **Configuring the Paid Apps Agreement after Setting Up In-App Purchases in iOS**

#### Step 1: Ensure In-App Purchases Are Set Up

Before proceeding, confirm that you have set up in-app purchases in **App Store Connect**:

1. Select **My Apps** and choose your app.
2. Go to the **Features** tab and select **In-App Purchases**.
3. Ensure all necessary in-app purchases are configured.

#### Step 2: Navigate to the Business Section

<figure><img src="/files/NP9SRQlMeJYviIv7kOv3" alt="" width="375"><figcaption></figcaption></figure>

1. In the main dashboard, click on **Business**.

#### Step 3: Review and Accept the Paid Apps Agreement

1. Under **Agreements**, locate the **Paid Apps Agreement**.
2. Click on **View** to review the terms.
3. Accept the agreement if required.

#### Step 4: Add Banking and Tax Information

<figure><img src="/files/YOtYDZDCR0jQI3KfPAec" alt=""><figcaption></figcaption></figure>

1. In the **Bank Accounts** section, click **Manage**.
2. Add or verify your bank details for receiving payments.
3. In the **Tax Forms** section, click **Add** to submit any required tax forms.

#### Step 5: Confirm Activation Status

1. Ensure that the **Paid Apps Agreement** status shows as **Active**.
2. If any action is required, complete the missing details and resubmit.

#### Step 6: Verify Your App’s Pricing and Availability

1. Go to **App Store Connect** > **My Apps**.
2. Select your app and navigate to the **Pricing and Availability** section.
3. Ensure your app is properly priced and available for purchase.

#### Step 7: Submit Your App for Review

Once everything is set up:

1. Go to **App Store Connect** > **My Apps**.
2. Select your app and submit it for review.

Your app is now configured for paid transactions, and users can make purchases once it's approved and live on the App Store.

***

## Android In-App Purchase (IAP) Configuration

### Step 1: Access Google Play Console

1. Open [Google Play Console](https://play.google.com/console).
2. Select your app or create a new app.

<div><figure><img src="/files/BSNGq82LXb08bUtZ3kpY" alt=""><figcaption></figcaption></figure> <figure><img src="/files/TCgLNzqWsnKMuLBDhzEf" alt=""><figcaption></figcaption></figure></div>

### Step 2: Configure In-App Purchase or Subscriptions

#### ============== Configure In-App Purchase ==============

1. Navigate to the **Monetize with Play** section.

<figure><img src="/files/NF8cSVaUCSTR8f7f73J8" alt="" width="375"><figcaption></figcaption></figure>

2. Click on **In-app Products**.

<figure><img src="/files/wjc8TsY5olx7AoFrNZZE" alt="" width="375"><figcaption></figcaption></figure>

3. Click **Create In-App Product**.

<div><figure><img src="/files/4edW5ylM4FwKsPfD60no" alt=""><figcaption></figcaption></figure> <figure><img src="/files/M5WFmUB9WBWvE50SgbVF" alt=""><figcaption></figcaption></figure></div>

4. Enter the required details:

* **Product ID** (Unique identifier for the in-app product)
* **Name** (User-friendly name of the product)
* **Description** (Detailed explanation of the product)
* **Price**: Set the price for the product.
* **Tax and Compliance**: Configure tax and regulatory settings.

3. Click **Activate** to enable the in-app product.

<figure><img src="/files/jGG1MLwvjIthy4K5amLY" alt="" width="375"><figcaption></figcaption></figure>

#### ============== Configure Subscriptions ==============

1. Navigate to the **Monetize with Play** section.
2. Click on **Subscriptions**.

<figure><img src="/files/xvzv9EgvZSjcMMg8OCqx" alt=""><figcaption></figcaption></figure>

3. Click **Create Subscription**.

<figure><img src="/files/c9WANBD63B5Za0FI3g9N" alt=""><figcaption></figcaption></figure>

4. Enter the required details:

<figure><img src="/files/7Tqj8Xsz36ARSJodUk1n" alt=""><figcaption></figcaption></figure>

* **Product ID** (Unique identifier for the subscription)
* **Name** (User-friendly name for the subscription)

5. Add a **Base Plan**:

<div><figure><img src="/files/hr0MynEWpTvfZjsfrnHZ" alt=""><figcaption></figcaption></figure> <figure><img src="/files/ef2IU9CMuNUDj2KP7XXt" alt=""><figcaption></figcaption></figure></div>

* Enter **Base Plan ID**.
* Select **Type** (Auto-renewing or prepaid subscription).
* Assign relevant **Tags**.
* Define **Price** and configure availability.

<div><figure><img src="/files/TNu6TX3GsTl0694jfBW7" alt=""><figcaption></figcaption></figure> <figure><img src="/files/O2LqBlOQZFVgP4a5Fkcw" alt=""><figcaption></figcaption></figure> <figure><img src="/files/V0jxwXRF2XCoECdNyoK6" alt=""><figcaption></figcaption></figure> <figure><img src="/files/q0Sjlob2UMShg7rbpTiQ" alt=""><figcaption></figcaption></figure> <figure><img src="/files/TMNu8NKMDlbPpXbalL0k" alt=""><figcaption></figcaption></figure> <figure><img src="/files/rv3cysOOtTap7h0cjRbQ" alt=""><figcaption></figcaption></figure></div>

6. Click **Activate** to enable the subscription.

***

### Conclusion

This guide provides step-by-step instructions for configuring In-App Purchases (IAP) for iOS and Android platforms. Before submitting your app for review, ensure that all necessary details are entered accurately and that it complies with the platform-specific guidelines.


# WooCommerce In-App Purchase Guide

The In-App Purchase (IAP) integration enables seamless payment handling via the Google Play Store (Android) and Apple App Store (iOS) within WooCommerce.

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeCOfUQ-oBOhaGog5e2aJir74o4VcOoEnCPlNyKiLbfhzOdIqsSijnwGNwz5sMJydiTPJoOZIULQL9hKmpOnFy9x4TjIrKCN0gIjA_2loF9leInqYcmnvQMFV9DT3eEnqIdwo4x7g?key=8fypB0WwX012kuShdOKyH1VA" alt=""><figcaption><p> Android IAP</p></figcaption></figure>

<figure><img src="/files/fJ81mrNLuPY9DlibVbYS" alt="" width="563"><figcaption><p>iOS IAP</p></figcaption></figure>

## Admin Setup

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXelO3183_HPS1yB16sq3PWSbdFFNtlnt4j3MHRhonwc1IVGGW1wzJSvd3kRqujVCKT83hk1p0o5TbRpWhBg8_Y-gi5YhMkJKzwKeXouai-FQ5CcyntZ2eHp65Gl-kNdkECgJoyToQ?key=8fypB0WwX012kuShdOKyH1VA" alt=""><figcaption></figcaption></figure>

1. **Custom Fields for Products**

When creating or editing WooCommerce products, the following fields are available for configuring in-app purchases:

* **Google Play Product ID**:  ID for the product in Google Play
* **App Store Product ID**:  ID for the product in the App Store
* **Product Type**:  (<mark style="color:green;">INAPP</mark> for one-time purchases or <mark style="color:green;">SUBS</mark> for subscriptions)
* **Is Consumable**:  (Checkbox indicating if the product is consumable)

2. **Configure the IAP Settings**

Admins can customize the text for various buttons displayed during the checkout process in the **WooCommerce IAP Settings** section. To access these settings:

<figure><img src="/files/ELUWVpM89xw8UMEpAwC4" alt=""><figcaption></figcaption></figure>

1. Go to your WordPress admin dashboard.
2. Navigate to **Webtonative > WooCommerce IAP Settings** (specific section for Webtonative integration).
3. **Configure the IAP Settings:**&#x20;

<figure><img src="/files/hLXRXCfkZmTxd1iVFYut" alt=""><figcaption></figcaption></figure>

1. **Configure the IAP Settings**:\
   The following options are available to customize the In-App Purchase functionality:
   * **Enable In-App Purchase**:
     * Check the box labeled "Enable In-App Purchases" to activate in-app purchases for your store.
     * Default: Unchecked (disabled).
   * **Enable Test Mode**:
     * Check the box labeled "Enable Test Mode" to enable test mode for in-app purchases. This is useful for testing transactions without processing real payments.
     * Default: Unchecked (disabled).
   * **App Store Bundle ID:**
     * Enter your App Store Bundle ID. This uniquely identifies your app in the Apple App Store
     * Default: Empty.
     * **Example**: com.example.app
   * **App Store Key ID:**
     * Enter your App Store Key ID. This is used to authenticate with Apple's servers for in-app purchase validation.
     * Default: Empty.
     * **Example**: QVI...
   * **App Store Issuer ID:**
     * Enter your App Store Issuer ID. This is associated with the key issued by Apple for your app.
     * Default: Empty.
     * **Example**: d1956586-...-73185c2e0d
   * **App Store Private Key (.p8):**
     * Paste the content of your .p8 private key file here. This key is used for signing and validating in-app purchase transactions with Apple.
     * Default: Empty.
     * **Example**:&#x20;

       ```
       -----BEGIN PRIVATE KEY-----
       MIG...2DOW...
       cp1c...
       21yu9Sh...
       -----END PRIVATE KEY-----
       ```
   * **Cart Button Text**:
     * Set the text for the cart button that initiates the in-app purchase process.
     * Default: "Buy Now".
   * **Processing Button Text**:
     * Define the text displayed on the button while the payment is being processed.
     * Default: "Processing...".
   * **Failed Button Text**:
     * Specify the text shown on the button if the payment fails.
     * Default: "Payment Failed".
   * **Payment Completed Button Text**:
     * Set the text displayed on the button after a successful payment.
     * Default: "Payment Completed".
2. **Save the Settings**:
   * After configuring the options, click the **Save Settings** button to apply your changes.

***

#### Important Notes:

* **Test Mode**: Remember to uncheck **Is Test** and switch to the live environment before launching your store.

***

## Payment Validation

**Android Payment Validation**

* Data validated includes orderId, purchaseToken, purchaseState, and other parameters received from Google Play.
* A WooCommerce order is created if the purchase is verified successfully.

**iOS Payment Validation**

* Receipt data is validated using the App Store's API (sandbox or production).
* If validated successfully, a WooCommerce order is created and marked as completed.

## Where In-App Purchase Data is Saved

1. **WooCommerce Order Notes**

* Each order created via IAP includes a detailed note with purchase data.
* Example note:\
  Webtonative Payment Data:

  \- Product ID: \<product\_id>

  \- Order ID: \<order\_id>

  \- Platform: \<ANDROID/IOS>

  \- Other purchase details...

2. **Post Meta**

* Metadata Purchase details are saved as metadata in WooCommerce orders.
* Meta Key: <mark style="color:green;">\_wtn\_payment\_data</mark>
* It contains a JSON object with transaction details, such as <mark style="color:green;">productId</mark>, <mark style="color:green;">receiptData</mark>, <mark style="color:green;">platform</mark>, and more.<br>


# WordPress In-App Purchase Setup

Set up in-app purchases using the WebToNative WordPress plugin. Configure subscriptions and digital products for Android and iOS apps.

### **WTN IAP Plugin for WebtoNative**

The **WTN IAP Plugin** is a sub-plugin for WebtoNative that enables easy handling of iOS and Android in-app purchases (IAP). It provides a mechanism to verify purchases with the App Store and Google Play Store and allows developers to consume the verification result via a custom callback function.

***

### **Key Features**

* **In-App Purchase (IAP) Button**: Easily integrate IAP functionality into your WordPress site using a shortcode.
* **Platform Detection**: Automatically detects whether the user is on iOS or Android and processes the purchase accordingly.
* **Server-Side Verification**: Verifies the purchase receipt with the respective store (App Store or Google Play).
* **Callback Integration**: Enables developers to handle the verification result with a custom PHP callback function.

***

### **Configuration**

1. Navigate to **Settings > WTN IAP (In-App Purchase)** in your WordPress admin dashboard.

<figure><img src="/files/hnGF0bblX1kHhG4gs6gX" alt="" width="159"><figcaption></figcaption></figure>

2. Configure the following options:

<figure><img src="/files/0STM5DWngHa1GtqxmuTo" alt=""><figcaption></figcaption></figure>

* **App Store Secret Key**:
  * Enter the shared secret from your App Store Connect account. This is required for verifying iOS purchases.
* **Callback Function Name**:
  * Specify the name of the PHP function that will handle the verification results.

***

### **Usage**

**Shortcode**

Use the <mark style="color:green;">**`[wtn_iap]`**</mark> shortcode to add an IAP button to your pages or posts.

**Attributes**

| Attribute                                          | Description                                                            | Default Value |
| -------------------------------------------------- | ---------------------------------------------------------------------- | ------------- |
| <mark style="color:green;">`google_play_id`</mark> | The product ID of the in-app purchase on Google Play.                  | `''`          |
| <mark style="color:green;">`app_store_id`</mark>   | The product ID of the in-app purchase on the App Store.                | `''`          |
| <mark style="color:green;">`product_type`</mark>   | The type of product (INAPP for consumable or `SUBS` for subscription). | INAPP         |
| <mark style="color:green;">`is_consumable`</mark>  | Whether the product is consumable (`true` or `false`).                 | `false`       |

**Example**

```html
[wtn_iap google_play_id="com.example.app.product" app_store_id="example_product" product_type="INAPP" is_consumable="true"]
```

***

### **How It Works**

1. **IAP Initialization**:
   * The user clicks the IAP button.
   * The platform (Android or iOS) is detected using the `window.WTN` object.
   * The purchase process is initiated using `WTN.inAppPurchase`.
2. **Purchase Handling**:
   * After the user completes the purchase, a receipt is generated.
   * The receipt is sent to the WordPress backend for server-side verification.
3. **Server-Side Verification**:
   * The plugin verifies the receipt with the respective store's API.
   * A success or failure message is returned based on the verification result.
4. **Callback Execution**:
   * If a custom callback function is configured, it is invoked with the platform and verification data.

***

### **Server-Side Callback Integration**

Developers can consume the IAP verification result by implementing a custom PHP callback function.

**Callback Function Requirements**

* **Function Name**: The function's name must match the one configured in the plugin settings.
* **Parameters**:
  * `$platform`: The platform of the purchase (`ios` or `android`).
  * `$receipt_data`: An array containing:
    * `receipt`: The raw receipt data.
    * `verification_result`: The verification result returned by the respective store's API.

**Example Callback Function for Verification**

Users must define a callback function in their theme’s <mark style="color:green;">**`functions.php`**</mark> or a custom plugin. This function will process the verification result and take appropriate actions, such as granting access to content or updating the user’s status.

```php
function my_custom_callback($platform, $receipt_data) {
    // Log the platform and data for debugging
    error_log("Platform: $platform");
    error_log("Verification Data: " . print_r($receipt_data, true));

    // Process the verification result
    $receipt = $receipt_data['receipt'];
    $verification_result = $receipt_data['verification_result'];

    if ($verification_result['success']) {
        // Example: Grant access to premium content
        $user_id = get_current_user_id();
        update_user_meta($user_id, 'premium_access', true);
        error_log("User ID $user_id granted premium access.");
    } else {
        error_log("Verification failed: " . $verification_result['message']);
    }
}
add_action('simple_iap_callback', 'my_custom_callback');
```

***

### **Frontend Integration**

**JavaScript Integration**

The plugin includes a JavaScript file (`wtn-iap.js`) that handles the IAP process on the client side.

1. **IAP Button**:
   * When the button is clicked, the `WTN.inAppPurchase` a method is invoked with the product details.
2. **Purchase Callback**:
   * The purchase callback (`paymentCallback`) processes the response and sends the receipt to the server via an AJAX request.
3. **Server Response Handling**:
   * The front end displays appropriate success or error messages based on the server's response.

**Example Workflow**

1. The user clicks the "Buy Now" button.
2. The IAP process starts, and the receipt is obtained.
3. The receipt is sent to the WordPress backend for verification.
4. A success or error message is displayed based on the verification result.

***

### **Error Handling**

**Frontend Errors**

* **Payment Failure**: Alerts the user if the purchase fails.
* **Invalid Response**: Logs and displays an error message if the server returns an invalid response.

**Backend Errors**

* **Missing App Store Secret**: Returns an error if the secret key is not configured.
* **Invalid Receipt**: Returns an error if the receipt verification fails.

### For more reference

{% embed url="<https://docs.webtonative.com/plugin/in-app-purchase-ios-setup>" %}
In App Purchase - iOS Setup
{% endembed %}

{% embed url="<https://docs.webtonative.com/plugin/in-app-purchase-android-setup>" %}
In App Purchase - Android Setup
{% endembed %}

{% embed url="<https://docs.webtonative.com/javascript-apis/in-app-purchase-ios-integration>" %}
In App Purchase - iOS Integration
{% endembed %}

{% embed url="<https://docs.webtonative.com/javascript-apis/in-app-purchase-android-integration>" %}
In App Purchase - Android Integration
{% endembed %}

***

This documentation provides a comprehensive guide for using the **Simple IAP Plugin** and customizing it to meet specific requirements.


# WordPress Offer Card Setup Guide

Configure Offer Cards with the WebToNative WordPress plugin. Show a video/image card to your users for discounts, offers, or information.

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXd60U4GK3C4jTNcY4aFyC3J7iLrw1D2Mr96I1-roLRV15u8gctJLsTPM-MY9kH6CWn-j5Ru0re8unfeCu7fvfl12dEemTRFcGIvmlTAcp_RSXk4hh8jK_RhWE3ZuQj1MY5czNGKiA?key=8fypB0WwX012kuShdOKyH1VA" alt=""><figcaption></figcaption></figure>

```
[wtn_offercard action_url="https://www.webtonative.com" button_text_color="#FFFFFF" button_bg_color="#c2175b" button_text="Hurry!" card_size="SMALL" card_position="RIGHT"  card_bg_color="#000000" content_type="IMAGE" content_url="https://files.123freevectors.com/wp-content/resized/509296-holi-banner.jpg?w=500&q=95" id="1" schedule_duration=9 schedule_unit="minutes"]
```

The <mark style="color:green;">**\[wtn\_offercard]**</mark> shortcode displays a customizable offer card with the following options:

* **action\_url**: URL to navigate to when the button is clicked.
* **button\_text\_color**: Color of the button text (hex code).
* **button\_bg\_color**: Background color of the button (hex code).
* **button\_text**: Text displayed on the button.
* **card\_size**: Size of the card (<mark style="color:green;">SMALL</mark>, <mark style="color:green;">FULL\_SCREEN</mark>, or <mark style="color:green;">FULL\_WIDTH</mark>).
* **card\_position**: Horizontal alignment of the card, (SMALL size card position default position right) (Has no impact in case of FULL\_SCREEN | FULL\_WIDTH )  (<mark style="color:green;">LEFT</mark>, <mark style="color:green;">RIGHT</mark>).
* **card\_bg\_color**: Background color of the card (hex code).
* **content\_type**: Type of content on the card (<mark style="color:green;">IMAGE</mark> or <mark style="color:green;">VIDEO</mark>).
* **content\_url**: URL of the image or video content displayed.
* **id**: Unique identifier for the card.
* **schedule\_duration**: Duration for scheduling the card's display.
* **schedule\_unit**: Unit of time for the schedule (<mark style="color:green;">minutes</mark>, <mark style="color:green;">hours</mark>, <mark style="color:green;">day</mark>).

<div><figure><img src="/files/A2pyNkQyzosgJNYfvSRN" alt="" width="270"><figcaption><p>Small screen offercard</p></figcaption></figure> <figure><img src="/files/SrWAFGs0OrNMMDZxwF5C" alt="" width="295"><figcaption><p>Full screen offercard</p></figcaption></figure></div>

This shortcode generates an offer card with a button, an image, or video content that is visually appealing and functional.

<br>


# WordPress Biometric Authentication Guide

Enable biometric authentication with the WebToNative WordPress plugin. Add fingerprint and Face ID login for Android and iOS apps.

This plugin enables biometric authentication for users in your WordPress application. Follow the steps below to configure and use the plugin effectively.

## Step 1: Configure Biometric Authentication Settings

1. Navigate to the **WordPress Admin Dashboard.**
2. Go to **Settings > Biometric Authentication.**

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfrlhRQv4gzsuurtCpSDK1umt6IKDvhYaQvzCA1EBHKbcP_cw53dhzWFteOx49xU27jU-XFq9LGWxMuKg_Bm_JQyRH54ehOTKtRYwj1y4fIcwsnM4pWI4TQQo6UClH2Sw4Gnv0bbg?key=8fypB0WwX012kuShdOKyH1VA" alt="" width="188"><figcaption></figcaption></figure>

3. Configure the following options:

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdPxZtcin-DZE7h6XnaJ1pKVq0mAMc5OUxztN89Mr4rEdEW3cVthRk9iLmKTXjxdf5di04pMNfTWG-kZYp7sQwrY_hsfvTBVc1VFbhbDJVYKxcJzTIkDLLDoIe08fFB12uIzyb29Q?key=8fypB0WwX012kuShdOKyH1VA" alt=""><figcaption></figcaption></figure>

* **Enable Biometrics**: Check this box to activate biometric authentication for users.
* **Prompt on Resume**: Enable a biometric prompt when the app is resumed.
* Prompt on Open: Enable a biometric prompt when the app is opened.
* Biometric Timeout (Minutes): Set the time delay (in minutes) after which biometric authentication will be required again.

4. Click Save Changes to apply the settings.

## Step 2: Allow Users to Enable Biometric Authentication

1. Add the shortcode <mark style="color:green;">**\[wtn\_biometric\_settings]**</mark> to a page (profile page) or post where users can manage their biometric preferences.
2. Logged-in users will see an option to enable or disable biometric authentication.
3. **When enabled:**

* Users will be prompted to authenticate via biometrics (e.g., fingerprint or face recognition).
* A secure token will be stored for subsequent authentication.

## Step 3: Enable Biometric Login (optional)

1. Add the shortcode <mark style="color:green;">**\[wtn\_biometric\_login]**</mark> to display a **Login with Biometric** button.
2. Unauthenticated users with biometric authentication enabled can log in using their biometrics.
3. The user will be redirected to their dashboard or the specified page if authentication is successful.

*<mark style="color:orange;">**Note**</mark>*: The user will only be able to perform biometric login when their session or token has expired. Biometric login will not be available in the case of a forced logout.

## Step 4: Automatic Biometric Prompts

* The plugin will automatically prompt for biometric authentication based on the admin settings:
  * **On App Resume**: If the app is inactive for the configured timeout.
  * **On App Open**: Every time the app is launched.
  * **On Logout**: If the user logs out, they must reauthenticate to access the app.

## FAQs

#### How do I reset biometric authentication for a user?

* As an admin, you can manually delete the user's biometric token from their profile or reset the settings from the admin panel.

#### What happens if biometric authentication fails?

* Users will not be logged in or authorized to proceed unless they successfully authenticate.

#### Can users disable biometric authentication?

* **Yes**, users can toggle biometric authentication off from the settings page added via the <mark style="color:green;">**\[wtn\_biometric\_settings]**</mark> shortcode.


# WordPress Social Login Guide

Enable Google, Apple, and Facebook login with the WebToNative WordPress plugin for secure authentication in Android and iOS apps.

## **Webtonative Social Login Setup Guide**

**Prerequisites**

1. A WordPress site with admin access.
2. Google and Facebook developer accounts for setting up OAuth credentials.
3. The provided plugin code is installed and activated on your WordPress site.

***

#### **Configuration Steps**

<figure><img src="/files/LADshm5MdquoHbt1V5bp" alt="" width="375"><figcaption></figcaption></figure>

**1. Setting Up Google Login**

1. **Create Google Credentials:**
   * Go to the [Google Cloud Console](https://console.cloud.google.com/).
   * Create a new project or select an existing one.
   * Navigate to **APIs & Services** > **Credentials**.
   * Click **Create Credentials** and select **OAuth Client ID**.
   * Configure the consent screen with the required details.
   * Choose **Web Application** as the application type and set the redirect URI as:

     ```
     https://your-site.com/wp-json/webtonative/social-login/verify/google
     ```
   * Save the credentials to get the `Client ID` and `Client Secret`.
2. **Add Credentials to WordPress:**

<figure><img src="/files/XbKbC1dGLm9CtNXLlZZm" alt=""><figcaption></figcaption></figure>

* In your WordPress admin panel, go to **Settings** > **Webtonative Social Login**.
* Enter the `Client ID` and `Client Secret` in the Google configuration section.
* Enable Google Login.

***

**2. Setting Up Facebook Login**

1. **Create Facebook App:**
   * Go to the [Facebook Developer Portal](https://developers.facebook.com/).
   * Create a new app or use an existing one.
   * Add the **Facebook Login** product to your app.
   * In the **Facebook Login** settings, add the redirect URI as:

     ```
     https://your-site.com/wp-json/webtonative/social-login/verify/facebook
     ```
   * Obtain the `App ID` and `App Secret` from the **Settings** > **Basic** section.
2. **Add Credentials to WordPress:**

<figure><img src="/files/to6OT61SIzl16do4oo24" alt=""><figcaption></figcaption></figure>

* In your WordPress admin panel, go to **Settings** > **Webtonative Social Login**.
* Enter the `App ID` and `App Secret` in the Facebook configuration section.
* Enable Facebook Login.

***

**3. Setting Up Apple Login**

1. **Create Apple Service:**
   * Log in to the [Apple Developer Portal](https://developer.apple.com/).
   * Create an identifier for the app and configure it for **Sign in with Apple**.
   * Generate a private key for authentication and set the redirect URI as:

     ```
     https://your-site.com/wp-json/webtonative/social-login/verify/apple
     ```
   * Obtain the **Team ID**, **Key ID**, and **Client ID** (Bundle ID).
2. **Add Credentials to WordPress:**
   * In your WordPress admin panel, go to **Settings** > **Webtonative Social Login**.
   * Enter the **Team ID**, **Key ID**, and **Client ID** in the Apple configuration section.
   * Enable Apple Login.

***

#### **4. Testing the Social Login**

* Navigate to your WordPress site's login page.
* You should see buttons for Google, Facebook, and Apple login.
* Test each login method to ensure they redirect properly and create a new user or log in an existing user.

***

#### **5. Redirecting After Login**

* The plugin automatically redirects users to the configured redirect URI after a successful login.
* To set the redirect URI:
  * Go to **Settings** > **Webtonative Social Login**.
  * Enter the desired URL in the **Redirect URI** field.

***

#### **6. Troubleshooting**

* Ensure all redirect URIs match those specified in the respective developer platforms.
* Check your WordPress site's permalink settings. Enable **Pretty Permalinks** in **Settings** > **Permalinks**.
* If login fails, review error logs or the **OAuth Debugging Tools** provided by Google and Facebook.

***

#### **FAQs**

**Q: What if I don’t see the social login buttons?**\
Ensure the plugin is active and correctly configured.

**Q: How can I disable a specific social login?**\
Disable the corresponding option in **Settings** > **Webtonative Social Login**.

**Q: Can I use this for custom login pages?**\
Yes, but you might need to add the login buttons manually by editing your theme or using a shortcode.

***

This documentation should guide you through setting up and configuring social login for your WordPress site!




---

[Next Page](/llms-full.txt/1)

