Skip to content

Events

This is the contract reference for the events the embedded widget emits to your page via ExpeditionInsure.on(). It covers the full catalog, each payload shape (premiumCents is the money field — integer minor units), which tier each event fires in, and ordering and idempotency. For how to mount the widget and subscribe, see the embed integration guide; for the HTTP endpoints the iframe calls, see the embed HTTP API, and for the policy.issued webhook see webhooks.

ExpeditionInsure.on() is available once the loader script is on the page. Add the loader and the global window.ExpeditionInsure appears:

<script src="https://expedition.insure/widget.js"></script>

For the full mount-and-subscribe walkthrough see /get-started/quickstart/; for the precise mount() / on() / embedOrigin contract see /reference/loader/.

You subscribe with ExpeditionInsure.on(eventName, handler). It returns an unsubscribe function. Subscribing to an unknown event name throws. Handlers run isolated — a throw inside your handler is caught and logged, never breaking the bridge or stopping other handlers. Multiple handlers per event are allowed and run in subscribe order.

const off = ExpeditionInsure.on("quote.selected", (e) => {
console.log(e.planId, e.premiumCents, e.currency);
});
// off(); // unsubscribe later

Under the hood the widget bridges a namespaced ei:* postMessage channel, origin-locked in both directions — inbound only from our embed origin and the exact mounted iframe, outbound only to the iframe with our origin as targetOrigin, never "*". The ei: prefix is stripped before your handler sees the event.

Subscribe to the three base-flow events before mounting. This captures the quoteId from quote.ready, the selection from quote.selected, and handles failure on quote.error:

let currentQuoteId = null;
ExpeditionInsure.on("quote.ready", (e) => {
// Capture quoteId here — it is NOT on quote.selected. See §5.
currentQuoteId = e.quoteId ?? currentQuoteId;
console.log("quote ready", currentQuoteId, "plans:", e.plansCount);
});
ExpeditionInsure.on("quote.selected", (e) => {
// premiumCents is integer minor units (cents) — divide by 100 for display.
console.log("selected", e.planId, e.premiumCents / 100, e.currency ?? "USD");
// Associate the selection with the quoteId you captured above.
});
ExpeditionInsure.on("quote.error", (e) => {
// code is an optional free-form string; render message defensively.
console.warn("quote failed", e.code, e.message);
});

Five events are valid. Subscribing to any other name throws. The Tier column refers to the three-tier integration model — see /get-started/overview/ for what each tier means.

EventFires whenTier
quote.readyThe quote form first renders / instant options are ready.All tiers
quote.selectedA traveler clicks a plan card.All tiers
quote.errorThe quote or options request failed.All tiers
payment.succeededThe insurance PaymentIntent confirmed inside the iframe.Tier-3 only
payment.failedThe insurance payment failed inside the iframe.Tier-3 only

quote.ready, quote.selected, and quote.error fire in the base quote flow, which runs in every embed tier. The payment.* events fire only in the Tier-3 in-iframe embedded checkout (/embed/checkout/embed/confirmed) — if you don’t use embedded checkout, you never see them.

The ei: prefix is stripped, so your handler receives the payload below for each event name.

{ quoteId?: string, plansCount?: number }
FieldTypeNotes
quoteIdstringThe created quote’s id. May be absent early.
plansCountnumberNumber of plans/options surfaced.
{ planId: string, premiumCents: number, currency?: string }
FieldTypeNotes
planIdstringThe selected plan’s id.
premiumCentsnumberInteger minor units (cents). Divide by 100 for display.
currencystringOptional ISO currency code (e.g. "USD"), forwarded untouched.

quote.selected has no quoteId — capture it from quote.ready. The quote.ready payload is { quoteId?, plansCount? } and quote.selected is { planId, premiumCents, currency? } — it does not carry the quoteId. To link a selection back to a quote (and to your booking), capture quoteId on quote.ready and associate it yourself. payment.succeeded also includes quoteId. The quoteId may be absent on a very early quote.ready but is present once options are ready, so keep the last non-empty value you see.

{ code?: string, message?: string }
FieldTypeNotes
codestringOptional machine-readable code.
messagestringOptional human-readable message.
{ quoteId?: string, planId?: string, premiumCents?: number, currency?: string }
FieldTypeNotes
quoteIdstringThe quote whose insurance was paid.
planIdstringThe purchased plan’s id.
premiumCentsnumberInteger minor units (cents). May be absent.
currencystringOptional ISO currency code (e.g. "USD").

All payment.succeeded fields are optional — coerce and guard before use.

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 off the Stripe payment_intent.succeeded event — that webhook is also what writes the commission. Use payment.succeeded to sequence or gate your own checkout UX; reconcile sales and attribution from policy.issued (see /operate/reconciliation/). Your trip charge is a separate rail — never treat the insurance payment as your trip payment.

{ code?: string, message?: string }
FieldTypeNotes
codestringOptional free-form string.
messagestringOptional human-readable message.

payment.failed has the same shape as quote.error — both fields are optional. There is no documented closed set of failure codes today: treat code as an optional free-form string, do not switch on specific values, and render message defensively (it may be absent). A stable, enumerated code set is [needs-product-work].

Both quote.selected and payment.succeeded carry the premium as premiumCents — an integer in minor units (cents). Read it and divide by 100 yourself for display.

currency is an optional string forwarded untouched (e.g. "USD"). The loader re-emits premiumCents exactly as received — it does not recompute it.

ExpeditionInsure.on("quote.selected", (e) => {
const dollars = e.premiumCents / 100;
render(dollars.toLocaleString(undefined, {
style: "currency",
currency: e.currency ?? "USD",
}));
});

Reading premiumCents (integer minor units) as whole dollars makes the value look 100× off. If a premium looks wrong, confirm you’re reading premiumCents and dividing by 100 for display.

The widget events are an in-page UX channel — treat them as best-effort signals, not a transactional ledger.

quote.ready ──► quote.selected ──► [Tier-3] payment.succeeded
│ │
└──► quote.error └──► payment.failed
  • Ordering within a session is logical, not guaranteed delivered. quote.ready precedes quote.selected, which precedes a Tier-3 payment.succeeded. But any event can be missed (tab closed, bridge dropped, outer sandbox strips the channel), so do not require an earlier event to have fired before handling a later one.
  • No delivery or replay guarantee. Events are not retried, queued, or re-emitted. If your handler throws, the event is logged and lost — it is not redelivered.
  • quote.selected can fire repeatedly — once per plan-card click. The payload reflects the latest selection; the last one wins. Make your handler idempotent (e.g. overwrite, don’t append).
  • payment.succeeded must not be treated as exactly-once. It can fire, fire late, or never arrive. For exactly-once, idempotent post-sale processing, consume the policy.issued webhook instead, which carries a stable eventId, is delivered at-least-once, and is the source of truth (see /operate/reconciliation/).

There is no deselect, decline, or “insurance dismissed” event. [needs-product-work] quote.selected fires on every plan-card click — a later click supersedes an earlier one — but there is no signal that a traveler actively opted out of insurance. If you add the premium to a trip total off quote.selected, treat the latest quote.selected as the current selection and provide your own “remove insurance” affordance in your UI; the widget will not tell you the traveler changed their mind.

If you run your own checkout (Tier-1 or Tier-2), sequence it off quote.selected — see /guides/checkout-sequencing/ for the quote.selected → your-checkout pattern. Reiterate the rule above: payment.succeeded is UX-only and may never arrive; reconcile from the policy.issued webhook, never from the browser event.