Skip to content

Loader API reference

The widget.js loader is the small, React-free script that injects the Expedition Insure quote iframe into your page. This page is the precise contract for its global, window.ExpeditionInsure: every method signature, return value, throw condition, and config param. It’s for the engineer wiring the widget in and anyone reviewing what the loader does to the page.

For the event payloads you receive through on(), see /reference/events/; for the HTTP endpoints the iframe calls, see /reference/embed-api/; for a narrative walkthrough start at /embedded-insurance/integration-guide/.

After widget.js loads, the global has exactly three members — no more:

window.ExpeditionInsure = {
mount, // (target, config) => HTMLIFrameElement
on, // (eventName, handler) => () => void (unsubscribe)
embedOrigin, // string — the embed origin this build trusts
};
  • mount(target, config) — inject a sandboxed iframe and return it. See §2.
  • on(eventName, handler) — subscribe to a lifecycle event; returns an unsubscribe function. See §3.
  • embedOrigin — a read-only string: the origin this build of widget.js trusts and frames. It is resolved at runtime from the origin that served widget.js, so <script src="https://expedition.insure/widget.js"> makes embedOrigin equal https://expedition.insure. It exists for debugging — there is no setter. The loader uses it to lock the postMessage channel (it only ever trusts inbound messages whose event.origin equals embedOrigin, never "*").

There is no unmount() and no update() — see §5 for the remount workaround.

Inject the quote iframe into a target element.

mount(target: string | HTMLElement, config: MountConfig): HTMLIFrameElement
  • target — either a CSS selector string (resolved with document.querySelector) or a direct HTMLElement.
  • config — see the params table in §4. pk is required; everything else is optional and pre-fills the quote form.
  • Returns the created HTMLIFrameElement, already appended into the target element. Keep the reference if you want to style its wrapper or read it back later.
<div id="quote"></div>
<script src="https://expedition.insure/widget.js"></script>
<script>
const iframe = ExpeditionInsure.mount("#quote", {
pk: "pk_op_...", // your publishable key
destination: "antarctica",
startDate: "2026-12-01",
endDate: "2026-12-14",
ages: [45, 47],
residence: "US",
tripCost: 18000, // whole dollars, total trip cost
travelers: 2,
});
</script>

mount() throws synchronously in exactly two cases:

ConditionError message
Target not found — the selector matches nothing, or the passed element is null.[ExpeditionInsure] mount target not found: <target>
Missing or invalid pkconfig is absent, or config.pk is not a non-empty string.[ExpeditionInsure] mount requires a `pk` (publishable key).
  1. Resolve the target element; throw if missing.
  2. Validate pk; throw if missing or invalid.
  3. Install the shared message bridge (idempotent — one window message listener is registered the first time and reused by all instances).
  4. Create an <iframe> and set iframe.src to ${embedOrigin}/embed/quote?<serialized config params>&host=<your-origin> (see §4 for serialization).
  5. Set the sandbox attribute (§5), title="ExpeditionInsure quote", and loading="lazy".
  6. Apply first-paint inline styles: width:100%, border:0, display:block, height:520px. The height is replaced as soon as the iframe reports its real content height through an ei:resize relay, so you never set a height and never see an inner scrollbar.
  7. Append the iframe into the target and track the instance.
  8. Return the iframe.

Multiple mount() calls are supported — each is tracked independently, and inbound messages are matched back to the right iframe by its contentWindow. The host param the loader appends is your page origin; it is advisory and spoofable, used by the iframe as its postMessage targetOrigin. The real trust boundary is the server-side pk_op_ Origin allowlist, not this param.

Subscribe to a lifecycle event emitted by the widget.

on(eventName: PublicEventName, handler: (payload: unknown) => void): () => void
  • Returns an unsubscribe function. Call it to remove your handler; calling it more than once is a safe no-op.

  • Handlers are isolated. Each handler runs inside a try/catch; a throw in your handler is caught and logged ([ExpeditionInsure] listener error:), never breaking the bridge or stopping other listeners.

  • Multiple handlers per event are allowed and run in subscribe order.

  • Unknown event names throw synchronously:

    [ExpeditionInsure] unknown event "<name>".
    Valid: quote.ready, quote.selected, quote.error, payment.succeeded, payment.failed

The five valid event names are quote.ready, quote.selected, quote.error, payment.succeeded, and payment.failed. The widget bridges a namespaced ei:* postMessage channel, origin-locked in both directions; the ei: prefix is stripped before your handler sees the payload.

const off = ExpeditionInsure.on("quote.selected", (e) => {
// premiumCents is integer minor units (cents); divide by 100 for display.
console.log(e.planId, e.premiumCents, e.currency);
});
// later, to stop listening:
off();

Read premiumCents (integer minor units). On quote.selected and payment.succeeded, premiumCents is the premium field — an integer in minor units (cents); divide by 100 yourself for display.

payment.succeeded is a UX signal, not the source of truth. It reports only the insurance charge confirmed inside our iframe, and it can be missed (the user closes the tab, the bridge drops). The authoritative post-sale signal is the policy.issued webhook, fired server-side — that is also what writes the commission. Use payment.succeeded to sequence your own checkout UX; reconcile sales and attribution from policy.issued. See /reference/events/.

Full payload shapes for each event are documented at /reference/events/.

Every param except pk is optional. Empty or undefined values are dropped from the iframe URL; arrays are serialized to CSV ([45, 47] becomes "45,47").

ParamTypeExampleNotes
pkstring (required)"pk_op_..."Your publishable key. Public by design (like a Stripe pk_); the trust boundary is the server-side Origin allowlist, not key secrecy. Missing or invalid → mount() throws.
destinationstring"antarctica"Primary destination slug or name.
startDatestring (YYYY-MM-DD)"2026-12-01"Trip departure date.
endDatestring (YYYY-MM-DD)"2026-12-14"Trip return date. Duration is derived from startDateendDate.
agesnumber[] or CSV string[45, 47] / "45,47"One age per traveler. Strongly recommended — without ages, estimate quality drops.
residencestring"US"Traveler residence (country code). Drives carrier eligibility.
tripCostnumber or string18000Total trip cost in whole dollars — not per-traveler, not cents.
travelersnumber or string2Number of travelers.
refstring"booking-9281"Your attribution/reference tag, echoed back for reconciliation.
logoUrlstring (URL)"https://cdn.you/logo.svg"Co-brand logo shown in the widget header.
accentColorstring (CSS color)"#00B4A0"Accent color for buttons and links inside the widget.

The loader also appends a host=<your-origin> param automatically. It is not part of MountConfig, it is advisory, and it is spoofable — the real trust boundary stays the server-side pk_op_ allowlist.

mount() sets this exact sandbox attribute on the iframe. You don’t configure it — it’s listed so your security review knows what to expect:

sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-top-navigation-by-user-activation"
TokenWhy it’s set
allow-scriptsRuns the quote app and Stripe.js inside the iframe.
allow-same-originThe framed document is our origin (embedOrigin), so same-origin is safe here. Required for Stripe.js and for the same-origin checkout handoff.
allow-formsThe quote and checkout forms submit.
allow-popupsProvider and hosted flows that open a tab (e.g. Stripe / 3DS).
allow-top-navigation-by-user-activationLets the Buy→hosted-checkout handoff navigate the top window — but only on a user click (the Buy button). The iframe can never silently redirect your page. If your own outer sandbox strips this token, the loader covers it with an origin-validated ei:navigate fallback that only ever navigates to a URL on our embed origin.

The iframe also carries title="ExpeditionInsure quote" and loading="lazy".

6. No unmount() or update() — the remount workaround

Section titled “6. No unmount() or update() — the remount workaround”

There is no unmount() and no update() today. [needs-product-work] The global exposes only mount, on, and embedOrigin. There is no API to tear down a mounted iframe instance or to push new config into a live one.

To change config — for example, when the traveler edits their trip dates or destination on your page — remount: clear the target element and call mount() again with the new config.

const target = document.querySelector("#quote");
target.innerHTML = ""; // remove the old iframe
ExpeditionInsure.mount(target, nextConfig);

Notes on remounting:

  • Each mount() call appends a new tracked instance; clearing the target’s DOM first removes the previous iframe from the page.
  • The shared message bridge is installed once and reused, so remounting does not stack listeners on the window — re-mounting is cheap.
  • Subscriptions you registered with on() are global to the widget, not bound to a single iframe; they keep firing for the new iframe and don’t need re-registering after a remount.