The GlomoPay web checkout is built for browsers. To use it inside a native mobile app you have to host it in a system WebView (WKWebView on iOS, android.webkit.WebView on Android). A WebView is not a full browser — it omits or changes behaviour that payment and bank pages rely on, so a checkout that works perfectly in mobile Safari or Chrome can fail when the same page is loaded inside an app.
GlomoPay ships native Flutter and React Native SDKs precisely so you don't have to solve these problems yourself. Under the hood they load the hosted checkout page directly in a correctly-configured WebView, inject a bridge so the page can talk to the native layer, and handle every issue listed below.
The native SDK is the fastest path to a stable live integration, not a later optimisation — all of the handling on this page already comes solved. A self-built WebView host is slower to get right, not quicker, because you take on every item here yourself. If your app is written in a framework our SDKs cover, use the SDK.
This page is for teams who have no supported native SDK and must build their own WebView host. Treat it as a non-trivial integration: every item below is a real class of issue we have had to handle in our own native SDKs, a raw WebView host inherits none of that handling by default, and behaviour differs between iOS and Android, so both platforms must be tested independently.
The native SDKs do not run the Unified (Web) SDK JavaScript. That SDK is for web pages — it injects the checkout as an <iframe> into your page and communicates with the surrounding browser window. Instead, the native SDKs:
- Build the checkout page URL for the order type and load it directly as the top-level document in a WebView.
- Inject a native bridge into that WebView. The checkout page detects this bridge and switches into WebView mode — it routes results to the native layer instead of a parent web window, and uses in-app handling for 3D-Secure / OTP instead of browser popups.
- Handle new windows, redirects, keyboard, and the other issues below around that page.
This is why dropping the Unified (Web) SDK into a WebView does not give you a working native integration: you get an iframe inside a WebView whose results are delivered to an intermediate web page (not to native), and whose bank/3D-Secure popups fail. Loading the page URL directly, plus the bridge and handling below, is the approach that works.
Load a single entry URL as the top-level document in the WebView. It resolves the order type (standard vs LRS) on the server and 302-redirects the WebView to the correct checkout page — so you never hardcode the individual checkout hosts, and you don't need to know the order type up front:
https://api.glomopay.com/api/public/v1/checkout/redirect?resource_id={RESOURCE_ID}&public_key={PUBLIC_KEY}The same URL works for both live and test — the gateway routes to the correct environment from your public key's prefix.
| Parameter | Required | Where it comes from |
|---|---|---|
resource_id | Yes | The order / subscription / payment-session id you create on your server. Standard: Checkout → Steps to integrate Checkout (use the returned order_id) or the Order reference. LRS: LRS → Integration guide → Create an order on the server. |
public_key | Yes | Your merchant public key from the dashboard. The prefix (live_ / test_) selects the environment. |
Two things to get right:
- Navigate to it; do not
fetch()it. Set the URL as the WebView's document (a navigation). A cross-originfetchfrom your own JavaScript would be blocked by CORS; a top-level navigation is not. - Do not pass
mode. The checkout derives live/mock from your public key.
The entry URL gets you to the right checkout page, but loading it is not a complete integration on its own. The checkout still runs inside your WebView, so you must supply the WebView handling below — the message bridge (so results reach your app), new-window handling (bank / 3D-Secure), the iOS fixes, and back navigation. These are exactly what our native SDKs provide and a self-built WebView host does not get for free.
Several WebView settings are off or restrictive by default and will break the checkout if not enabled:
- JavaScript must be enabled.
- DOM storage / local storage must be enabled — the checkout keeps session state there.
- Third-party cookies must be allowed. On Android these are disabled by default and are a common cause of bank sessions silently failing partway through authentication.
- Multiple windows / automatic window opening must be enabled, because bank and 3D-Secure pages open new windows (see below).
- Mixed content often needs to be allowed — some bank pages serve a mix of secure and non-secure sub-resources.
- Caching should generally be disabled (or the WebView run in an incognito/no-persistence mode) so that state does not leak between one checkout attempt and the next. Clear cookies and storage when a checkout instance is torn down.
- Avoid overriding the WebView's user agent unless you have tested it — some bank pages serve a different (and sometimes broken) layout based on the user-agent string.
Symptom: The checkout container opens but shows nothing — a blank white screen with no content and no error.
Cause: Several distinct startup problems present the same way: any host-side script the checkout expects failed to load; the URL passed to the WebView was empty or malformed; a repeat attempt for the same order was served a cached 304 Not Modified instead of a fresh page; or the host tried to post a message to the page after the WebView was already torn down, crashing the page context.
What to handle: Validate the URL before loading and don't render if it is missing. Send no-cache headers (or otherwise bust the cache) so that retrying the same order does not return a stale cached response. Guard any message you post into the WebView so that posting after teardown fails safely instead of crashing. Surface a clear error state rather than leaving the customer on a blank screen.
Symptom: The customer reaches the point where a bank or 3D-Secure page should open, and nothing happens — the flow stalls on a blank or unchanged screen.
Cause: Bank and card-authentication pages routinely open a new window (window.open), and some submit a form targeting a new tab (target="_blank"). A WebView has no concept of a new tab, so these calls are silently dropped.
What to handle: Your host must intercept new-window requests and load the target URL somewhere the user can see it — typically a second WebView presented as an overlay on top of the checkout. You also need to handle the page programmatically closing that window when authentication finishes, and cases where the bank page navigates via a form submission intended for a new tab. Getting this wrong is the single most common reason bank flows appear "frozen" inside an app.
Symptom: A multi-step bank flow dead-ends or loops midway through authentication.
Cause: Bank and 3D-Secure flows chain through several redirects before returning to the checkout.
What to handle: Let these redirects proceed inside the overlay WebView rather than trying to intercept and route each one natively — over-eager interception is a common cause of broken bank flows. Keeping bank authentication inside the app (rather than bouncing out to an external browser) also keeps the session and cookies consistent across the whole redirect chain, which is how our native SDKs handle it.
Symptom (iOS): When the customer taps an OTP box or other input, the page visibly zooms in and the layout jumps; moving between single-character OTP boxes makes the screen and keyboard jitter.
Cause: iOS WebViews automatically zoom the viewport when an input with a computed font size below 16px receives focus, then zoom back out afterward.
What to handle: The zoom fires whenever an input's effective font size is below 16px — including OTP and login fields on the bank pages, which you do not control. The way to neutralise it is to inject CSS into the WebView that forces a 16px-or-larger font size on inputs. This is exactly how our native SDKs handle it — they inject CSS into the hosted page rather than modifying its source, which is how a WebView host reaches fields it does not own. OTP entry is the most affected area because it uses many small single-character inputs in a row.
Symptom (mainly iOS): When the keyboard opens, it covers the active input, or a fixed bottom element (such as a "Pay now" / amount footer) rides up and hides the field the customer is typing into.
Cause: Opening the keyboard shrinks the visible viewport. Fixed and sticky elements anchored to the bottom reposition into the reduced viewport and can overlap the input.
What to handle: React to the keyboard opening and closing so the active field stays visible, and avoid letting bottom-anchored elements cover inputs while the keyboard is up.
Symptom: The checkout loads already zoomed in, stays pinch-zoomable when it shouldn't, or auto-scrolls unexpectedly (for example jumping to the top of a bank consent page when a checkbox is tapped).
Cause: Default viewport settings differ from a browser, and naive "scroll the focused element into view" logic conflicts with how bank pages lay out their controls (some hide the real control off-screen).
What to handle: Establish and lock a sensible viewport (fit to device width, disable user scaling), and be very conservative about programmatic scrolling — over-eager auto-scroll causes more problems on bank pages than it solves. In our own SDK we ultimately removed aggressive auto-scroll behaviour because it fought with bank consent pages.
Symptom (iOS): The customer cannot paste into an input — for example, pasting a copied OTP or reference number does nothing and no paste menu appears.
Cause: Some checkout and bank pages disable text selection globally for security. iOS WebViews honour this even inside legitimate input fields, so the paste/selection menu never shows.
What to handle: Re-enable text selection and the paste menu specifically on editable fields so customers can paste into inputs, without re-enabling selection across the whole page.
Symptom (iOS): The checkout fails to bootstrap, or the bank session is lost between steps, even though the same flow works on the web.
Cause: iOS WebViews can be strict about sending cookies with background requests and, in some cases, about parsing the top-level document response.
What to handle: Ensure session cookies are sent with the checkout's own requests, and be prepared to retry the initial document load if the WebView reports a parse error on it. Confirming that third-party cookies are enabled (see configuration above) addresses most of the session-loss cases.
Symptom: The payment completes but the app never finds out, so the checkout screen stays open, or the app reports the wrong final status.
Cause: The checkout communicates results by posting messages to its host, not by any native callback. A WebView host must explicitly listen for these messages via its bridge.
What to handle: Wire up a message listener in your host and react to the checkout's lifecycle events — payment success, payment failure, and checkout closed — to decide when to dismiss the WebView and what status to record. In a WebView the message bridge is your result channel; always verify the signed result server-side.
If you host bank pages in a second (overlay) WebView, be aware that the same completion event can be emitted from both WebViews. De-duplicate on your side so a single payment does not fire your completion handling twice.
Symptom: The hardware/gesture back button (Android) or swipe-back (iOS) exits the whole checkout instead of stepping back within it — and in the worst case interrupts a payment that is already being processed.
Cause: A WebView does not tie the system back gesture to the checkout's own navigation state.
What to handle: Map back navigation to the checkout: if a bank overlay is open, close the overlay and return to the checkout rather than exiting; if the main checkout can go back, do that; only then treat back as a cancel. Critically, do not allow back to abort a payment that is already in progress — block it during that window so a customer cannot break an in-flight transaction.
Symptom: After the WebView opens, the customer stares at a blank white screen while the entry URL redirects and the checkout page loads and boots — then the underlying WebView may flash through before the checkout paints.
What to handle: Cover the gap with a native loading overlay — this is the single biggest perceived-quality item and it is entirely host-side. Render your own full-screen loader (spinner or branded skeleton) on top of the WebView from the moment you start loading, and hide it only once the checkout signals it has rendered: listen for the rendered/loaded event on your message bridge, falling back to the WebView's page-finished / load-progress callback. Also set the WebView's background colour to your brand colour so the unavoidable pre-paint moment is never a stark white flash. Apply a timeout so that a stuck load (for example under poor network) fails into a clear error state instead of an indefinite spinner — never leave the customer on an unbounded blank screen.
The redirect hop itself is fast (a bodyless 302); most of the gap is the checkout page fetching and booting, which you would see loading the page directly too. Our native SDKs solve this with exactly this pattern — a full-screen overlay held until the checkout's rendered event, with a timeout fallback — and a self-built WebView host must implement it as well.
Also distinguish a genuine network/connection failure (no connectivity, host unreachable, timeout) from an HTTP or payment-level error. The two surface through completely different signals on iOS and Android, and they need different responses: a connection failure should let the customer retry, whereas a payment failure is a real outcome you must record. Treating one as the other leads to misleading messaging.
Symptom (Android): Events the checkout posts are occasionally not received by the host.
Cause: Some Android WebView versions do not reliably deliver same-frame posted messages to a listener.
What to handle: Make the host resilient — for example by not assuming the very first messages will arrive and by having a fallback path — rather than trusting a single message listener to catch every event.
Do not try to detect payment outcomes by reading network response bodies inside the WebView. It is unreliable, differs between WebView implementations, and risks exposing sensitive payment data. Rely only on the checkout's posted lifecycle events (and server-side verification of the signed response) to determine payment status.
Every fix above lives in the native SDKs and ships as an SDK version update. With a hand-built WebView host there is no SDK version to bump — each change you make must be re-implemented and re-released through your own app-store cycle, and new WebView or bank-page behaviours become your team's responsibility to track. This maintenance burden is the main reason we recommend moving to a native SDK for production.