> For the complete documentation index, see [llms.txt](https://docs.onside.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.onside.io/sdk/integrations/js-bridge.md).

# JS ↔ Native Bridge

Use the Onside JS Bridge to call OnsideKit from JavaScript in a \`WKWebView\`. Covers setup, \`window\.onside\` APIs, callbacks, transactions, and typed errors.

Use the JS <--> Native Bridge to call OnsideKit from JavaScript running inside a `WKWebView`. This integration fits apps built as a web wrapper, where most product logic lives in a website but the app still needs native OnsideKit features such as in-app purchases, attribution, and event tracking.

The bridge is only a transport layer between JavaScript and native code. It does not add new capabilities and does not change the semantics, lifecycle, or error handling of existing OnsideKit APIs. The behavior described in [Installation](/sdk/getting-started/installation.md), [Authentication & User Account](/sdk/core-concepts/authentication.md), [Attribution](/sdk/attribution-and-analytics/attribution.md), [Building funnels with event tracking](/sdk/attribution-and-analytics/building-funnels-with-event-tracking.md), and [Making a Purchase](/sdk/purchasing/making-a-purchase.md) still applies. This page focuses on `WKWebView` configuration and the JavaScript API exposed by the bridge.

### Initialize OnsideKit first

Start with the same OnsideKit setup used for a native integration.

Complete the installation, callback URL scheme setup, and SDK initialization exactly as described in [Installation](/sdk/getting-started/installation.md) and [Initializing the SDK](/sdk/getting-started/initialization.md).

The only difference is delegate and observer setup.

{% hint style="warning" %}
If you plan to use OnsideKit from JavaScript, do not assign OnsideKit delegates or transaction observers in native code. When the page calls `initializeOnside`, `OnsideJS` installs itself as the delegate and observer layer — and re-installs itself after every page reload, replacing anything native code assigned in the meantime.
{% endhint %}

### Configure WKWebView

After OnsideKit is installed and initialized, configure your `WKWebView` with `OnsideJS.configureOnsideForWebView(_:allowedOrigin:)`.

This is the only required `WKWebView` setup step.

```swift
import WebKit
import OnsideKit

let webView = WKWebView(frame: .zero)

do {
    try OnsideJS.configureOnsideForWebView(
        webView,
        allowedOrigin: URL(string: "https://shop.example.com")
    )
} catch {
    // The bridge is not installed. Do not load content that expects it.
}
```

`allowedOrigin` is the origin your web content is served from. The bridge answers calls only from documents on that origin, and only from the main frame — a third-party `iframe` on the page cannot reach native, and neither can the page after it navigates somewhere else. Only the scheme, host and port are used; any path is ignored.

Passing `nil` disables the origin check and lets **any** document in that web view call the bridge, including `purchase`, `logout` and `getSignedInAppsHistory`. Use it only for local development.

{% hint style="warning" %}
Documents with an opaque origin never receive the bridge. In particular `loadHTMLString(_:baseURL: nil)` will not work — load your content from a real URL, or pass a `baseURL`.
{% endhint %}

`configureOnsideForWebView` throws `OnsideJSSetupError`:

| Case                                 | Meaning                                                                                     |
| ------------------------------------ | ------------------------------------------------------------------------------------------- |
| `notInitialized`                     | `Onside.initialize()` has not been called yet. Initialize the SDK first.                    |
| `invalidAllowedOrigin`               | `allowedOrigin` has no scheme or host, so no origin can be derived from it.                 |
| `alreadyConfiguredForAnotherWebView` | Another `WKWebView` sharing the same `WKWebViewConfiguration` already installed the bridge. |
| `internalError`                      | Unexpected failure inside the SDK.                                                          |

### Load your web content

After configuration, use the `WKWebView` as usual and load your web content in the normal way.

If configuration succeeds, JavaScript running inside the page gets access to `window.onside`.

Before you call any bridge method, initialize the JS API version your code supports.

Call `window.onside.initializeOnside('v1')` during your JS bootstrap. It returns a `Promise` that you must **await** before any other `window.onside` method or callback.

`'v1'` declares that your client code supports JS Bridge API v1. This is currently the only supported version.

```js
await window.onside.initializeOnside('v1');
```

{% hint style="warning" %}
Await `window.onside.initializeOnside('v1')` before calling any other `window.onside` method or assigning any callback. Until it resolves, no other `window.onside.*` method exists — calling one throws a `TypeError` synchronously instead of returning a rejected promise.
{% endhint %}

`initializeOnside` is idempotent, so calling it again is safe. Call it on every page load: after a full page navigation or reload the JavaScript context is new, and your bootstrap has to run again. Native state — the payment queue, the signed-in user — survives the reload, so use `getTransactions()` afterwards to rebuild your UI.

{% hint style="info" %}
Properties named `_pontoon*` on `window.onside` are reserved by the bridge. Do not assign them.
{% endhint %}

After initialization, you can call methods and assign callbacks on `window.onside`.

#### Conventions

* **Every method returns a `Promise`.** There are no synchronous methods — the setters included. A setter resolves with an empty object on success and rejects if you pass it something it cannot decode, so attach a `.catch` even to `setAppearance` and `trackEvent`.
* **Two kinds of rejection.** A *domain error* is the native error enum, serialized as `{ <caseName>: {} }` — discriminate with `'caseName' in error`. A *bridge error* means the call never reached native logic at all (malformed argument, unknown function, a frame the origin policy refuses); it arrives as a real `Error` with `name === 'PontoonBridgeError'` and a stable `code`.

```js
try {
  const { products, invalidProductIdentifiers, unsupportedProductIdentifiers } = await window.onside.loadProducts({
    productIdentifiers: ['my.product.id'],
  });
} catch (error) {
  if (error instanceof Error) {
    // Bridge error — a bug in the call itself, or a refused document.
    console.error(error.name, error.code);
  } else if ('connectionError' in error) {
    // Domain error — retry, show a message, ...
  }
}
```

Bridge error codes: `invalidArguments`, `functionNotDefined`, `disallowedFrame`, `brokenRequest`, `parametersNotRepresentable`, `cannotSerializeResponse`, `bridgeDetached`.

The bridge uses API version **`v1`**. Bridge initialization also manages `Onside.externalLoginDelegate`; use the JS configuration methods for that delegate. Orientation-override configuration is not exposed as a JS method.

### API Reference — v1

This reference describes JS Bridge API version `v1`.

#### Configuration

**`initializeOnside('v1')`**

```js
window.onside.initializeOnside('v1');
```

Initialize the JS bridge API. Call this once before any other `window.onside` method or callback.

The argument declares the API version your JS client supports. Currently only `'v1'` is supported.

**`setAppearance(theme)`**

```ts
setAppearance(theme: 'light' | 'dark' | 'system'): Promise<void>;
```

Override the theme used for native UI presented from JS (login, payment methods, etc.). `'system'` follows the OS-level appearance.

**`setLoginMethod(method)`**

```ts
setLoginMethod(method: 'auto' | 'local'): Promise<void>;
```

Select regular login: `'auto'` (default) tries the Onside store app when available and falls back to the SDK phone login; `'local'` uses the SDK phone login. This setting applies when external login is disabled or falls back. Await the setter before requesting login or purchasing.

**`setExternalLoginEnabled(enabled)`**

```ts
setExternalLoginEnabled(enabled: boolean): Promise<void>;
```

Enable the optional external-login delegate (disabled by default). When enabled, `externalAccountToken` takes priority over the regular login method.

```js
await window.onside.initializeOnside('v1');
window.onside.externalAccountToken = async () => currentUser?.id ?? null;
await window.onside.setLoginMethod('auto');
await window.onside.setExternalLoginEnabled(true);
await window.onside.requestLogin();
```

Here `currentUser` belongs to your app’s account system. Register the callback again after each page load, before starting an operation that may log in. The native external-login setting survives a page reload, but the JavaScript function does not.

`setExternalLoginEnabled(false)` removes the external delegate for future attempts; it does not log out an existing session. To switch a current external session to regular Onside login, disable external login, `await logout()`, then `await requestLogin()`.

**`setShouldForceLocalLoginMethods(force)`** (deprecated)

```ts
setShouldForceLocalLoginMethods(force: boolean): Promise<void>;
```

Deprecated alias for `setLoginMethod`: `true` selects `'local'`, `false` selects `'auto'`. The latest call to either setter determines the regular login method. It does not control federated-provider visibility or disable external login.

**`setDefaultCountryCodeAssumption(countryCode)`**

```ts
setDefaultCountryCodeAssumption(countryCode: string): Promise<void>;
```

Pre-login country assumption, used before the device region. Once authenticated, the account’s region takes precedence. Pass an empty string to clear the assumption.

#### Analytics

**`trackEvent(input)`**

```ts
trackEvent(input: {
  name: string;
  parameters?: Record<string, JSEventParameterValue>;
}): Promise<void>;
```

Submit a single analytics event. Public tracking delivers events only for installations distributed through Onside. A resolved Promise does not confirm server delivery. See [Event tracking](/sdk/attribution-and-analytics/building-funnels-with-event-tracking.md).

`name` may be a predefined Onside event (e.g. `purchaseCompleted`, `checkoutStarted`) or any custom identifier. Validation is enforced natively:

* name and parameter keys must match `^[a-zA-Z][a-zA-Z0-9_]*$` and be ≤ 40 chars (longer names are truncated; reserved prefixes `onside_` and `$$` are rejected);
* string values longer than 100 chars are truncated;
* at most 25 **effective** parameters per event. Every element of an array parameter consumes one slot, and an empty array still costs one. An array that would exceed the remaining budget is truncated to fit, and once the budget is exhausted the remaining parameters are dropped. Parameters are processed in alphabetical order of their keys, so which ones survive depends on the key names, not on insertion order.

{% hint style="warning" %}
Validation failures are silent: an invalid parameter key drops that parameter, an invalid event **name** drops the whole event, and the promise resolves successfully either way. Verify your event names and keys during development.
{% endhint %}

A parameter value the bridge cannot represent — `null`, `NaN`, an object, or a nested array — is a different matter: it rejects the whole call with a bridge error, and nothing is tracked.

#### Auth

**`requestLogin()`**

```ts
requestLogin(): Promise<void>;
```

Show the login flow. Resolves on successful authentication. Rejects with `OnsideLoginError`.

**`logout()`**

```ts
logout(): Promise<void>;
```

Log out the current user.

#### UI

**`presentPaymentMethodsManager()`**

```ts
presentPaymentMethodsManager(): Promise<void>;
```

Show the payment-methods management screen. Triggers login first if the user is not authenticated. An external profile rejects with `{ notSupportedForExternalLogin: {} }`. Success means presentation started, not that the user dismissed the screen. Rejects with `OnsidePaymentMethodsManagerError`.

#### Storefront

**`getCurrentStorefront()`**

```ts
getCurrentStorefront(): Promise<JSStorefront | null>;
```

Currently selected storefront, or `null` if none is selected yet. The selection can change at any time — subscribe to `onStorefrontChanged` for updates.

#### Products

**`loadProducts(input)`**

```ts
loadProducts(input: { productIdentifiers: string[] }): Promise<JSProductsResponse>;
```

Load products by identifiers. The response contains:

* `products` — products that resolved successfully;
* `invalidProductIdentifiers` — identifiers that did not resolve (unknown, malformed, etc.);
* `unsupportedProductIdentifiers` — valid products unavailable to the current profile. Subscriptions and non-consumables are excluded from `products` for external profiles and listed here instead. In local testing this list is empty.

Returned products are also cached natively so that `purchase` can look them up by `productIdentifier`. The cache is dropped whenever the storefront changes — including on login and logout — because prices and availability may differ. After `onStorefrontChanged` fires, call `loadProducts` again before the next `purchase`, or it will reject with `unknownProduct`.

Rejects with `OnsideProductsRequestError`.

#### Payments

**`purchase(input)`**

```ts
purchase(input: {
  productIdentifier: string;
  appAccountToken?: string;
}): Promise<void>;
```

Initiate a purchase. The product identifier must refer to a product previously returned by `loadProducts`; otherwise the promise rejects with `{ unknownProduct: {} }`.

The promise itself only reports the **pre-flight** outcome (login outcome, lookup, etc.). Subsequent transaction state changes are delivered via `onTransactionsUpdated`; JS is responsible for calling `finishTransaction` once a transaction reaches a terminal state.

External profiles can purchase only consumables and require Apple Pay. A subscription or non-consumable already in the cache is rejected with `notSupportedForExternalLogin`; a consumable without Apple Pay is rejected with `applePayRequired`. These failures occur before enqueueing and before `shouldContinueTransactionAfterLogin`, so no transaction callback follows. See [External Login](/sdk/core-concepts/external-login.md).

Rejects with `JSPurchaseError`.

**`restoreCompletedTransactions()`**

```ts
restoreCompletedTransactions(): Promise<void>;
```

Restore previously completed transactions. The promise only reports the pre-flight outcome (e.g. `loginDiscarded`). The completion of the restore flow itself is delivered via `onRestoreCompletedTransactionsFinished` or `onRestoreCompletedTransactionsFailedWithError`. Restored transactions arrive through `onTransactionsUpdated`.

Rejects with `OnsidePaymentQueueRequestRestoreError`.

**`getTransactions()`**

```ts
getTransactions(): Promise<JSPaymentTransaction[]>;
```

Snapshot of currently alive transactions in the payment queue. Useful on page reload to rebuild UI state without waiting for the next event.

**`finishTransaction(id)`**

```ts
finishTransaction(id: string): Promise<void>;
```

Finish a transaction by its opaque id (the `id` field of `JSPaymentTransaction`). `onTransactionsRemoved` fires once the transaction leaves the queue.

Removal is immediate only for a transaction that has not been sent for payment yet. For `purchased`, `restored` and `failed` the transaction is marked for completion and removed once that completes, so `onTransactionsRemoved` may arrive noticeably later.

{% hint style="info" %}
A transaction that is `purchasing` because payment is already in flight cannot be finished. The promise still resolves, but nothing happens. Wait for a terminal state before calling `finishTransaction`.
{% endhint %}

Rejects with `JSFinishTransactionError`.

#### Attribution

**`getAttributionMetadata()`**

```ts
getAttributionMetadata(): Promise<JSAttributionMetadata>;
```

Fetch attribution metadata for the current install/user (e.g. the referer URL that brought the user to the install).

Rejects with `OnsideAttributionMetadataError`.

#### History

**`getSignedInAppsHistory()`**

```ts
getSignedInAppsHistory(): Promise<JSSignedInAppsHistory>;
```

Fetch the signed in-apps history JWT for the current user. The response carries both the raw bytes (base64-encoded) and a UTF-8 representation, when applicable.

Rejects with `OnsideSignedInAppsHistoryRequestError`.

### Native → JS Callbacks

Assign a function to the corresponding property on `window.onside` to start receiving the event. Callbacks that only report events are optional and missing handlers are no-ops. The token callback and transaction gates have the fallback behavior described below.

The origin policy applies to this direction too: callbacks are delivered only while the web view is showing a document on the allowed origin. If the page navigates away — to a payment provider, or to an external link opened in place — native stops delivering them, so a transaction that changes state meanwhile produces no callback at all. Call `getTransactions()` once you are back to reconcile.

{% hint style="warning" %}
Native does not serialize callbacks. Each event is dispatched as soon as it happens, so a handler that returns a `Promise` may still be running when the next callback fires. Handlers are invoked in the order the events occurred, but they can **finish** in any order — if yours mutate shared state, guard against overlap yourself.
{% endhint %}

**`externalAccountToken()`**

```ts
externalAccountToken(): string | null | undefined | Promise<string | null | undefined>;
```

Called when login is needed and external login is enabled. Return the current host account’s `external_user_id`, not an Onside access token. The bridge requests it for each login attempt and does not cache it.

If the callback is missing, returns `null`/`undefined`, throws, rejects, or returns an invalid type, the SDK falls back to the selected login method. An unavailable or disallowed page also produces no token. Empty strings are passed through; return `null` if there is no user. Session-creation failure uses the same fallback; failure to load the profile after session creation opens local SDK login.

This identifier does not automatically populate a payment’s `appAccountToken`. Supply that separately in `purchase` if your backend uses it.

**`onTransactionsUpdated(transactions)`**

```ts
onTransactionsUpdated(transactions: JSPaymentTransaction[]): void | Promise<void>;
```

Transactions added to the queue or transitioned to a new state. Inspect `transactionState` and react accordingly. Once the state reaches `purchased`, `restored` or `failed`, JS must call `finishTransaction` to remove it from the queue.

**`onTransactionsRemoved(transactions)`**

```ts
onTransactionsRemoved(transactions: JSPaymentTransaction[]): void | Promise<void>;
```

Transactions that have been removed from the queue (typically after `finishTransaction`).

**`onStorefrontChanged(storefront)`**

```ts
onStorefrontChanged(storefront: JSStorefront | null): void | Promise<void>;
```

Storefront selection changed; `null` means the storefront is no longer available.

**`onRestoreCompletedTransactionsFinished()`**

```ts
onRestoreCompletedTransactionsFinished(): void | Promise<void>;
```

The `restoreCompletedTransactions` flow finished successfully.

**`onRestoreCompletedTransactionsFailedWithError(error)`**

```ts
onRestoreCompletedTransactionsFailedWithError(
  error: OnsideTransactionsRestoreError
): void | Promise<void>;
```

The `restoreCompletedTransactions` flow failed.

**`shouldContinueTransaction(input)`**

```ts
shouldContinueTransaction(input: {
  transaction: JSPaymentTransaction;
  storefront: JSStorefront;
}): boolean | Promise<boolean>;
```

Gate the queue invokes when a queued transaction is about to execute in a storefront **different** from the one it was enqueued in — typically because the user logged into an account registered in another country, where the price or availability differs. Same-storefront purchases never call it. This is the JavaScript counterpart of the native delegate described in [Handling Storefront & Price Changes](/sdk/purchasing/storefront-price-changes.md).

Return `false` to discard the transaction. It is then removed from the queue and reported through `onTransactionsRemoved` — it does **not** reach `failed`, and the `purchase` promise that created it has already resolved by then.

**`shouldContinueTransactionAfterLogin(input)`**

```ts
shouldContinueTransactionAfterLogin?(input: {
  transaction: JSPaymentTransaction;
}): boolean | Promise<boolean>;
```

Gate the queue invokes when `purchase` had to log the user in first. It fires after the login succeeds and before the transaction is enqueued, so the transaction passed here is not in the queue yet.

Return `false` to drop the purchase — `purchase` then rejects with `rejectedAfterLogin`, and no transaction callback is delivered for that attempt.

{% hint style="warning" %}
Both gates default to `true` in three cases: no handler registered, the handler threw, and the handler returned anything that is not a boolean. A handler that forgets to `return` therefore approves the purchase.
{% endhint %}

### Domain Types

**`JSPrice`**

```ts
{ value: number; currencyCode: string }
```

Numeric monetary value with an ISO-4217 currency code (e.g. `"EUR"`).

**`JSPeriod`**

```ts
{ unit: 'day' | 'week' | 'month' | 'year'; numberOfUnits: number }
```

A length of time — analogous to StoreKit's `SKProductSubscriptionPeriod`. Used for the subscription period and for the duration of introductory and discounted prices.

**`JSPricePeriod`**

```ts
{ price: JSPrice; period: JSPeriod }
```

A price that applies for the given period — an introductory or discounted price.

**`JSProduct`**

```ts
{
  productIdentifier: string;
  localizedTitle: string;
  localizedDescription: string;
  iconUrl?: string;
  subscriptionGroupIdentifier?: string;
  subscriptionPeriod?: JSPeriod;
  price: JSPrice;
  introductoryPrice?: JSPricePeriod;
  discountedPrice?: JSPricePeriod;
}
```

A product as returned by `loadProducts`. `subscriptionGroupIdentifier`, `subscriptionPeriod`, `introductoryPrice` and `discountedPrice` are present only for subscriptions.

{% hint style="info" %}
Optional fields are **omitted** from the payload rather than sent as `null`, so `'iconUrl' in product` is `false` when a product has no icon.
{% endhint %}

**`JSStorefront`**

```ts
{ id: string; countryCode: string }
```

The selected storefront — opaque storefront `id` plus its ISO-3166 `countryCode`.

{% hint style="warning" %}
`id` is stable only for the lifetime of the app process; it is regenerated on every launch. Never persist it or compare it across sessions — use `countryCode` for anything durable.
{% endhint %}

**`JSPayment`**

```ts
{ product: JSProduct; appAccountToken?: string }
```

The payment that produced a transaction, embedded inside `JSPaymentTransaction.payment`. `appAccountToken` is the optional opaque token passed to `purchase`.

**`JSPaymentTransactionState`**

```ts
'purchasing' | 'purchased' | 'restored' | 'failed'
```

Lifecycle states of a transaction:

| State        | Meaning                                                                      |
| ------------ | ---------------------------------------------------------------------------- |
| `purchasing` | Still in flight: created, awaiting payment, etc.                             |
| `purchased`  | Completed successfully. Call `finishTransaction(id)`.                        |
| `restored`   | Returned by `restoreCompletedTransactions`. Call `finishTransaction(id)`.    |
| `failed`     | Final, failed. `error` is populated. Call `finishTransaction(id)` to remove. |

**`JSPaymentTransaction`**

```ts
{
  id: string;
  transactionIdentifier?: string;
  originalTransactionIdentifier?: string;
  payment: JSPayment;
  transactionState: JSPaymentTransactionState;
  storefront: JSStorefront;
  error?: OnsidePaymentTransactionError;
}
```

* `id` — opaque UUID string. The only stable handle for this transaction; pass it to `finishTransaction`.
* `transactionIdentifier` — server-side order id. Absent until payment has been sent.
* `originalTransactionIdentifier` — currently always equal to `transactionIdentifier`. Reserved for a future renewal chain; do not group renewals by it yet.
* `error` — populated only for `failed` transactions.

**`JSAttributionMetadata`**

```ts
{ refererUrl?: string }
```

Currently exposes only the referer URL. May be extended in the future.

**`JSSignedInAppsHistory`**

```ts
{ dataBase64: string; string?: string }
```

* `dataBase64` — raw payload bytes, base64-encoded.
* `string` — UTF-8 decoded payload, when applicable (the payload is typically a JWT and decodes to a printable string).

**`JSProductsResponse`**

```ts
{
  products: JSProduct[];
  invalidProductIdentifiers: string[];
  unsupportedProductIdentifiers: string[];
}
```

Result of `loadProducts`.

**`JSEventParameterValue`**

```ts
string | number | boolean | JSEventArrayParameterValue[]
```

Value type for `trackEvent` parameters: a primitive or an array of primitives. Array elements (`JSEventArrayParameterValue`) follow the same primitive set without further nesting.

### Errors

All native `enum: Error` cases without associated values arrive on the JS side as `{ <caseName>: {} }`. The bridge also defines a few JS-specific error types listed at the end.

Rejections that never reached native logic are not in this list — they arrive as an `Error` with `name === 'PontoonBridgeError'` and a `code`, as described under [Conventions](#conventions).

**`OnsideLoginError`**

| Case             | Meaning                                                                                |
| ---------------- | -------------------------------------------------------------------------------------- |
| `loginDiscarded` | The login flow did not complete — the user cancelled it, or it could not be presented. |

**`OnsideProductsRequestError`**

| Case                       | Meaning                                        |
| -------------------------- | ---------------------------------------------- |
| `cancelled`                | The request was cancelled.                     |
| `connectionError`          | Network connectivity error.                    |
| `appNotRegistered`         | The current app is not registered with Onside. |
| `invalidProductIdentifier` | Server rejected the product identifier(s).     |
| `serviceUnavailable`       | Server returned a 5xx.                         |
| `internalError`            | Other unexpected error.                        |

**`OnsideSignedInAppsHistoryRequestError`**

| Case                         | Meaning                                                     |
| ---------------------------- | ----------------------------------------------------------- |
| `notLoggedIn`                | Operation requires an authenticated user.                   |
| `notSupportedInLocalTesting` | Not supported when running with a local-testing storefront. |
| `cancelled`                  | The request was cancelled.                                  |
| `connectionError`            | Network connectivity error.                                 |
| `appNotRegistered`           | The current app is not registered with Onside.              |
| `serviceUnavailable`         | Server returned a 5xx.                                      |
| `internalError`              | Other unexpected error.                                     |

**`OnsidePaymentMethodsManagerError`**

| Case                           | Meaning                                                                            |
| ------------------------------ | ---------------------------------------------------------------------------------- |
| `loginDiscarded`               | The implicit login did not complete — dismissed, or not presentable.               |
| `notSupportedInLocalTesting`   | Not supported when running with a local-testing storefront.                        |
| `notSupportedForExternalLogin` | The current external profile cannot manage saved payment methods.                  |
| `presentationFailed`           | OnsideKit couldn't present the screen (no active window scene); nothing was shown. |

**`OnsidePaymentQueueRequestRestoreError`**

| Case             | Meaning                                                              |
| ---------------- | -------------------------------------------------------------------- |
| `loginDiscarded` | The implicit login did not complete — dismissed, or not presentable. |

**`OnsideTransactionsRestoreError`**

| Case                 | Meaning                                        |
| -------------------- | ---------------------------------------------- |
| `cancelled`          | The restore was cancelled.                     |
| `connectionError`    | Network connectivity error.                    |
| `appNotRegistered`   | The current app is not registered with Onside. |
| `serviceUnavailable` | Server returned a 5xx.                         |
| `internalError`      | Other unexpected error.                        |

**`OnsidePaymentTransactionError`**

Populated on `JSPaymentTransaction.error` for `failed` transactions.

| Case                 | Meaning                                                                                |
| -------------------- | -------------------------------------------------------------------------------------- |
| `cancelled`          | The transaction was cancelled.                                                         |
| `presentationFailed` | OnsideKit couldn't present the purchase UI (no active window scene); it never started. |

**`OnsideAttributionMetadataError`**

| Case                 | Meaning                                        |
| -------------------- | ---------------------------------------------- |
| `connectionError`    | Network connectivity error.                    |
| `appNotRegistered`   | The current app is not registered with Onside. |
| `serviceUnavailable` | Server returned a 5xx.                         |
| `internalError`      | Other unexpected error.                        |

**`JSPurchaseError`**

JS-bridge specific error for `purchase`.

| Case                           | Meaning                                                                       |
| ------------------------------ | ----------------------------------------------------------------------------- |
| `unknownProduct`               | The `productIdentifier` is not in the local cache. Call `loadProducts` first. |
| `loginDiscarded`               | The implicit login did not complete — dismissed, or not presentable.          |
| `rejectedAfterLogin`           | `shouldContinueTransactionAfterLogin` returned `false`.                       |
| `notSupportedForExternalLogin` | An external profile cannot purchase subscriptions or non-consumables.         |
| `applePayRequired`             | Apple Pay is unavailable for a consumable purchase by an external profile.    |

**`JSFinishTransactionError`**

JS-bridge specific error for `finishTransaction`.

| Case                 | Meaning                                                 |
| -------------------- | ------------------------------------------------------- |
| `unknownTransaction` | No transaction with the given `id` exists in the queue. |

### Reference

The full machine-readable contract is available in [`onside.d.ts`](https://github.com/onside-io/OnsideKit-iOS/blob/main/onside.d.ts).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.onside.io/sdk/integrations/js-bridge.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
