Skip to main content

WebView Integration (Native Apps)

The Embedded SDK mounts a full web form inside an iframe. Most of what that form does is plain DOM/network activity and works identically whether the host page is a desktop browser tab or a native app's WebView. A couple of features are the exception: they're built on browser-native APIs that a native WebView either doesn't implement or silently no-ops. This page covers which features those are, why they break, and how to take them over.

Why this only affects native WebViews

window.print() and anchor-click downloads are ordinary browser APIs — they work without any special handling when the SDK is embedded in a normal <iframe> on a normal web page. Native mobile WebViews (iOS WKWebView, Android WebView) render web content but don't fully implement every browser-chrome behavior around it:

FeatureBrowser API behind itWhat happens in a native WebView
Print (report view, terms PDF)window.print()No print dialog exists to open — the call does nothing, silently
Download PDF (report "Save to PDF", terms PDF)Invisible <a> anchor clickNo download manager/save-to-Files sheet — the click does nothing, silently

Both failures are silent — no error is thrown, nothing reaches onError, and the user just sees an unresponsive button. If you're embedding the SDK in a native app and haven't done anything described on this page, this is almost certainly what your users are hitting.

The fix: takeover handlers

Two mount() options exist specifically for this: handlePrintRequested and handleDownloadRequested. Provide either one, and the form suppresses that browser-native default and calls your handler instead — your native code performs the action however you see fit.

These are documented in full under Takeover Handlers (Native WebViews) in SDK Methods, and their types are in the Type Reference. The short version:

sdk.mount("#form", {
handlePrintRequested: () => {
// Your code — handle the print request however fits your app
},
handleDownloadRequested: ({ url, filename }) => {
// Your code — retrieve `url` and get it to the user as `filename`
},
});

They're named handle* rather than on* deliberately, to signal that providing one changes behavior rather than just notifying you of something that already happened — every other mount option starting with on* is a passive callback. Omit either handler and that action keeps its default (browser-only) behavior unchanged; this is why shipping the handlers is additive and doesn't affect existing web integrations.

What each handler gives you

Both handlers are intentionally unopinionated about what you do on the native side — the SDK's job ends at handing you the request; everything past that is entirely your implementation to design.

Print

handlePrintRequested() takes no arguments and returns nothing. It fires whenever the user asks to print (including a Ctrl/Cmd+P on the host page), and once you've provided it, the form never calls window.print() itself. A typical approach is to forward the request to your native layer and let it drive whatever print experience your platform offers — the SDK has no expectations beyond that.

Detecting a stale form build

Delegation only takes effect once the embedded form acknowledges it when the view becomes ready. If your host is bundled against an older form build that predates delegation support, handlePrintRequested/handleDownloadRequested are silently never invoked and the form falls back to its browser-native (non-functional-in-WebView) default — except for a non-fatal onWarning with code DELEGATION_UNSUPPORTED, whose invalidFields lists which of 'print'/'download' weren't honored. If your takeover handlers appear to do nothing, check for this warning before assuming a bug in your own handler.

Download

handleDownloadRequested({ url, filename }) fires when the user asks to download a file, and once you've provided it, the form never triggers its own browser download. You get a URL and a suggested filename; getting that file into the user's hands — a share sheet, a save-to-storage flow, or anything else — is up to you.

Two properties of url are worth knowing regardless of how you implement it:

  • It's self-authenticating. It's a presigned URL with authentication embedded in its own query string, so a plain unauthenticated GET from any HTTP client retrieves the file — you don't need to forward cookies, headers, or any other credential from the WebView's context.
  • It's short-lived. It expires within minutes, so fetch it promptly rather than queuing the request for later.
Do not persist the report on your own systems

The file behind url is a screening report sourced from Experian. Experian's terms prohibit retaining these reports outside of delivering them to the requesting consumer or end user — whatever your implementation looks like, it should get the file to that person and not archive, cache, log, or otherwise keep a copy under your own control once that's done.

Putting it together

A native integration generally means: detect that your web content is running inside your app's WebView, give your JavaScript a way to call into native code (however you choose to structure that), and forward handlePrintRequested/handleDownloadRequested calls to your native print/download implementation. Skip registering both handlers when the same bundle runs in a normal browser tab — you want the default browser behavior there, not a call into a bridge that doesn't exist.

Checklist for native app developers

  • Detect that your web bundle is running inside your app's WebView
  • Provide handlePrintRequested and implement printing however fits your platform
  • Provide handleDownloadRequested, fetch the presigned URL promptly with no extra auth, and get the file to the user without retaining a copy — see the note under Download
  • Skip both handlers for the same bundle running in a normal browser tab, so default behavior there is unaffected
  • Test the print and "Save to PDF" buttons in the report view, and the terms-of-service PDF download in the screening flow
  • Watch for an onWarning with code DELEGATION_UNSUPPORTED — see Detecting a stale form build

Next Steps