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, Authentication & User Account, Attribution, Building funnels with event tracking, and Making a Purchase 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 and Initializing the SDK.
The only difference is delegate and observer setup.
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.
Configure WKWebView
After OnsideKit is installed and initialized, configure your WKWebView with OnsideJS.configureOnsideForWebView(_:allowedOrigin:).
This is the only required WKWebView setup step.
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.
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.
configureOnsideForWebView throws OnsideJSSetupError:
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.
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.
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.
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.catcheven tosetAppearanceandtrackEvent.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 realErrorwithname === 'PontoonBridgeError'and a stablecode.
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')
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)
Override the theme used for native UI presented from JS (login, payment methods, etc.). 'system' follows the OS-level appearance.
setLoginMethod(method)
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)
Enable the optional external-login delegate (disabled by default). When enabled, externalAccountToken takes priority over the regular login method.
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)
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)
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)
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.
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 prefixesonside_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.
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.
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()
Show the login flow. Resolves on successful authentication. Rejects with OnsideLoginError.
logout()
Log out the current user.
UI
presentPaymentMethodsManager()
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()
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)
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 fromproductsfor 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)
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.
Rejects with JSPurchaseError.
restoreCompletedTransactions()
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()
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)
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.
Rejects with JSFinishTransactionError.
Attribution
getAttributionMetadata()
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()
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.
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.
externalAccountToken()
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)
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)
Transactions that have been removed from the queue (typically after finishTransaction).
onStorefrontChanged(storefront)
Storefront selection changed; null means the storefront is no longer available.
onRestoreCompletedTransactionsFinished()
The restoreCompletedTransactions flow finished successfully.
onRestoreCompletedTransactionsFailedWithError(error)
The restoreCompletedTransactions flow failed.
shouldContinueTransaction(input)
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.
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)
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.
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.
Domain Types
JSPrice
Numeric monetary value with an ISO-4217 currency code (e.g. "EUR").
JSPeriod
A length of time — analogous to StoreKit's SKProductSubscriptionPeriod. Used for the subscription period and for the duration of introductory and discounted prices.
JSPricePeriod
A price that applies for the given period — an introductory or discounted price.
JSProduct
A product as returned by loadProducts. subscriptionGroupIdentifier, subscriptionPeriod, introductoryPrice and discountedPrice are present only for subscriptions.
JSStorefront
The selected storefront — opaque storefront id plus its ISO-3166 countryCode.
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.
JSPayment
The payment that produced a transaction, embedded inside JSPaymentTransaction.payment. appAccountToken is the optional opaque token passed to purchase.
JSPaymentTransactionState
Lifecycle states of a transaction:
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
id— opaque UUID string. The only stable handle for this transaction; pass it tofinishTransaction.transactionIdentifier— server-side order id. Absent until payment has been sent.originalTransactionIdentifier— currently always equal totransactionIdentifier. Reserved for a future renewal chain; do not group renewals by it yet.error— populated only forfailedtransactions.
JSAttributionMetadata
Currently exposes only the referer URL. May be extended in the future.
JSSignedInAppsHistory
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
Result of loadProducts.
JSEventParameterValue
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.
OnsideLoginError
loginDiscarded
The login flow did not complete — the user cancelled it, or it could not be presented.
OnsideProductsRequestError
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
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
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
loginDiscarded
The implicit login did not complete — dismissed, or not presentable.
OnsideTransactionsRestoreError
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.
cancelled
The transaction was cancelled.
presentationFailed
OnsideKit couldn't present the purchase UI (no active window scene); it never started.
OnsideAttributionMetadataError
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.
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.
unknownTransaction
No transaction with the given id exists in the queue.
Reference
The full machine-readable contract is available in onside.d.ts.
Last updated
Was this helpful?