CargoLabs Docs

Direct iframe integration

Use the hosted quote flow without the SDK, in your own iframe or a mobile webview.

Everything the SDK does is built on one hosted page:

https://buy.coverport.io/embed/quote?pk=<publishable key>&host=<your origin>&product=<slug>

You can load that URL in any iframe or webview and talk to it with postMessage. This is the supported path for iOS and Android apps today; native SDKs are planned.

URL parameters

Prop

Type

Embedding

<iframe
  src="https://buy.coverport.io/embed/quote?pk=pk_test_…&host=https%3A%2F%2Fshop.example.com"
  title="CoverPort insurance quote"
  allow="payment"
  style="width:100%;max-width:480px;height:min(720px,92vh);border:0;border-radius:12px"
></iframe>
  • allow="payment" is required for wallet payments (Apple Pay, Google Pay, Link) to work inside the frame.
  • The flow is responsive down to 320px wide. 480 by 720 is the layout the SDK uses.
  • The page sets X-Frame-Options/CSP so it can only be framed when the pk is valid. There is no allowlist of framing origins; the key is the credential.

Messages from the flow

The flow posts messages to window.parent. Every message is an object with __coverport: true and a type.

typePayloadWhen
readynoneThe flow has loaded and rendered its first screen. Use it to hide a spinner.
completesale: CoverportSalePayment succeeded and the policy was issued. Fires once.
closenoneThe buyer asked to leave the flow from inside it. Remove the iframe.

CoverportSale is documented in the Reference.

Listening safely

Always check event.origin and event.source before trusting a message. The SDK does exactly this:

const iframe = document.querySelector('#coverport-frame');
const EMBED_ORIGIN = 'https://buy.coverport.io';

window.addEventListener('message', (event) => {
  if (event.origin !== EMBED_ORIGIN) return;
  if (event.source !== iframe.contentWindow) return;

  const data = event.data;
  if (!data || data.__coverport !== true) return;

  switch (data.type) {
    case 'ready':
      spinner.hidden = true;
      break;
    case 'complete':
      recordSale(data.sale);
      break;
    case 'close':
      iframe.remove();
      break;
  }
});

Mobile webviews

Load the same URL in a WKWebView (iOS) or WebView (Android) and bridge messages back to native code.

The flow calls window.parent.postMessage, and in a top-level webview window.parent === window, so the flow does not post. Inject a shim that forwards to a WKScriptMessageHandler instead:

let shim = """
window.parent = { postMessage: function (msg) {
  window.webkit.messageHandlers.coverport.postMessage(msg);
}};
"""
let script = WKUserScript(source: shim, injectionTime: .atDocumentStart, forMainFrameOnly: true)
config.userContentController.addUserScript(script)
config.userContentController.add(self, name: "coverport")

webView.load(URLRequest(url: URL(string:
  "https://buy.coverport.io/embed/quote?pk=pk_live_…&host=app://coverport-host")!))
func userContentController(_ c: WKUserContentController, didReceive m: WKScriptMessage) {
  guard let body = m.body as? [String: Any], body["__coverport"] as? Bool == true else { return }
  switch body["type"] as? String {
  case "complete": handleSale(body["sale"])
  case "close": dismiss()
  default: break
  }
}

Card entry in webviews

Payment is collected by Stripe Checkout inside the flow. Both platforms support this in a standard webview; do not disable JavaScript or third-party cookies for the CoverPort origin.

The flow deliberately does not rely on cookies. Third-party cookies are blocked inside iframes by every major browser, so the buyer's identity step runs server-side on CoverPort and the resulting session lives in the frame's own memory. Reloading the frame starts a fresh session.

On this page