Skip to main content

Mobile WebView

Overview

This guide shows how to embed the Nimbbl Web checkout inside a mobile WebView and handle the two things a WebView cannot do on its own: launching UPI apps natively and returning the payment result to your app. Each platform below mirrors an official open-source DIY sample app you can clone and run.

  • Android and iOS implement the full checkout flow — call update-order before loading, hand installed UPI apps to the checkout page over a JavaScript bridge, launch the selected app, intercept the mobile redirect, and render a native result screen.
  • React Native and Flutter implement the lighter-weight flow — intercept UPI deep-links and handle bank 3DS / net-banking popups.
RECOMMENDED

We strongly recommend using the platform-specific SDKs (Android SDK, iOS SDK, React Native SDK, Flutter SDK) for better performance, seamless UPI handling, and an optimized user experience. Use this WebView approach only if you cannot adopt a client SDK.

Reference sample apps

Each platform section is a walkthrough of its open-source DIY sample. Clone the one you need for the complete, runnable source:

How the full flow works

Android and iOS follow the same sequence — only the platform APIs differ:

  1. Collect the order token. The checkout URL is https://sonic.nimbbl.tech/?token=<ORDER_TOKEN>. The screen refuses to open without a non-empty token.
  2. Update the order. Before loading, call PATCH /api/v3/order with callback_mode=callback_mobile so the backend routes the result to the mobile redirect URL instead of a browser one. You can skip this step if you pass the same parameters (callback_mode, referrer_platform, referrer_platform_version) to the create-order API when the order is created.
  3. Load the checkout in the WebView.
  4. Offer installed UPI apps. After the first page load, the app discovers installed UPI apps and injects them into the page via window.nimbbl_web.UPIIntentAvailable(...).
  5. Launch on selection. When the user picks an app, the page calls the native bridge (openUpiIntent); the app launches the UPI app and, once the user returns, notifies the page via postMessage.
  6. Handle the result. The checkout redirects to <sonic-host>/mobile/redirect?response=...; the app intercepts that URL and shows a native result screen.
  7. Device back is forwarded to the checkout page via postMessage({ sdk_action: "device_back_initiated" }) rather than navigating WebView history — the page decides what to do.

Platform Integration

FULL SOURCE

Complete, runnable app: nimbbl_mobile_android_webview_diy_sample_app. Snippets below are the key pieces of WebViewActivity.kt and UpiAppUtils.kt.

1. Require a token, then open the WebView

// HomeActivity.kt
private fun isValidUrl(url: String): Boolean {
if (url.isEmpty()) return false
return try {
val formatted = if (!url.startsWith("http")) "https://$url" else url
val uri = Uri.parse(formatted)
uri.scheme != null && !uri.host.isNullOrEmpty() &&
!uri.getQueryParameter("token").isNullOrEmpty()
} catch (e: Exception) { false }
}

2. Call update-order before loading

Decode the order_id from the token's JWT payload and tell the backend this is a mobile WebView session.

// PATCH https://<api-host>/api/v3/order   (Authorization: Bearer <token>)
val body = JSONObject().apply {
put("callback_mode", "callback_mobile")
put("referrer_platform", "android")
put("order_id", orderId)
put("referrer_platform_version", "1.0.0")
}.toString()
Skip update-order

This call is optional. If you pass the same parameters — callback_mode, referrer_platform, and referrer_platform_version — to the create-order API when the order is created, the order is already set up for mobile WebView and you can skip the PATCH /api/v3/order step entirely, loading the checkout URL directly.

3. Discover installed UPI apps and hand them to the page

Query the apps that can handle upi://pay, then inject the list.

// UpiAppUtils.kt — query installed UPI apps
val upiIntent = Intent(Intent.ACTION_VIEW,
Uri.Builder().scheme("upi").authority("pay").build())
val resolved = packageManager.queryIntentActivities(upiIntent, 0)
// build { "UPIApps": [ { "upi_app_name", "package_name" }, … ] }

// WebViewActivity.kt — after onPageFinished
val jsonStr = JSONObject.quote(upiAppsJson.toString())
webView.evaluateJavascript("window.nimbbl_web.UPIIntentAvailable($jsonStr)", null)

Register the JavaScript bridge the checkout page calls when a UPI app is selected:

webView.addJavascriptInterface(SonicJsInterface(this), "NimbblSDK")

private inner class SonicJsInterface(private val ctx: Context) {
@JavascriptInterface
fun openUpiIntent(parameters: String) = runOnUiThread {
val json = JSONObject(parameters)
val upiUrl = json.getString("url")
val packageName = json.getString("package_name")
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(upiUrl)).apply {
setPackage(packageName)
}
if (intent.resolveActivity(packageManager) != null) {
upiResultLauncher.launch(intent) // launch the chosen UPI app
} else {
sendUpiBackToWebView() // notify the page to resume
}
}
}

When the user returns from the UPI app, notify the page so it can resume polling:

private fun sendUpiBackToWebView() {
val payload = JSONObject().apply {
put("sdk_upi_intent_response", "close")
put("sdk_transaction_enquiry_response", "")
}
val js = JSONObject.quote(payload.toString())
webView.evaluateJavascript("window.postMessage($js, '$checkoutOrigin');", null)
}

4. Intercept the redirect and show the result

override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
val url = request?.url?.toString() ?: return false
if (url.startsWith(callbackUrlBase)) { // <sonic-host>/mobile/redirect
val response = Uri.parse(url).getQueryParameter("response")
startActivity(Intent(this, PaymentResultActivity::class.java).apply {
putExtra(PaymentResultActivity.EXTRA_RESPONSE, response ?: "")
})
finish()
return true
}
return false
}

5. Forward device back to the page

onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
val payload = JSONObject().apply {
put("sdk_action", "device_back_initiated")
put("source", "nimbbl_android_sdk")
}
val js = JSONObject.quote(payload.toString())
webView.evaluateJavascript("window.postMessage($js, '$checkoutOrigin');", null)
}
})

Manifest

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

<!-- Required on Android 11+ to query installed UPI apps -->
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="upi" android:host="pay" />
</intent>
</queries>

Supported UPI App Schemes

Representative UPI app URL schemes used across the samples. Android discovers apps dynamically via PackageManager; iOS declares each scheme in LSApplicationQueriesSchemes.

UPI AppURL scheme
Google Paygpay://upi/
PhonePephonepe://
Paytmpaytmmp://
BHIMbhim://upi/
Amazon Payamazonpay://
CREDcredpay://upi/
MobiKwikmobikwik://upi/
Jupiterjupiter://
Bajaj Paybajajpayupi://
Navinavipay://
Super.Moneysupermoney://
Kotak811kotak811://
POPpopclubapp://
Generic UPIupi://pay

Best Practices

  1. Error Handling: Always handle the case when a UPI app is not installed — notify the checkout page (sendUpiBackToWebView) so it can resume, and show clear feedback to the user.
  2. Update order first: On Android and iOS, call update-order (or pass the parameters at create-order time) so the checkout returns to the mobile redirect URL.
  3. Forward device back: Post device_back_initiated to the page rather than navigating WebView history — the checkout decides whether to close or step back.
  4. Testing: Test on real devices with real UPI apps installed; simulators/emulators won't have them.
  5. Security: Block mixed content and validate URLs before opening them.

Troubleshooting

UPI App Not Opening

  • Ensure the scheme is declared (LSApplicationQueriesSchemes on iOS, <queries> on Android)
  • Check that the UPI app is installed on the device
  • Verify the payload passed to openUpiIntent has url, package_name, and transaction_id

WebView Not Loading Checkout

  • Ensure JavaScript and DOM storage are enabled
  • Confirm the checkout URL includes a valid ?token=
  • Verify network permissions are granted

Payment Result Not Showing

  • Confirm update-order ran with callback_mode=callback_mobile
  • Verify the redirect URL (<sonic-host>/mobile/redirect) is being intercepted
  • Check that the response query parameter is parsed correctly
IMPORTANT
  • Always test UPI redirects on real devices, as simulators may not have UPI apps installed
  • Ensure proper error handling for cases when UPI apps are not available
  • Keep your WebView implementation updated to handle new UPI app schemes