Skip to content

SLA, uptime, and status

This page covers what to expect from the Expedition Insure embed for availability — what we publish today, and, more importantly, how to build your trip checkout so that an embed outage degrades gracefully instead of blocking your sale. It is for the engineers who own the page the widget runs on.

Read the security model for why the embed fails closed at the framing layer, and data and privacy for what crosses the boundary. For the event payloads referenced below, see the integration guide; for how a sale reaches your ledger after payment, see reconciliation and attribution.

The short version: your trip checkout must not hard-depend on the embed. Insurance is an add-on. If the widget is slow, throws, or never loads, the customer should still be able to book the trip. Everything below is about making that true in your code.

We run a public status page and a machine-readable feed for the embed:

  • Status page (developers.expedition.insure/status) — live per-component health (loader, quote API, iframe, webhook delivery, MCP) with 90-day uptime and any active incidents.
  • JSON feed + incident RSS — linked from the status page, for wiring embed health into your own dashboards and alerting.

A few honest caveats:

  • The status page reflects our own synthetic monitoring — we probe each surface every few minutes. It’s a transparency signal, not an independent third-party uptime guarantee.
  • There is no contractual SLA on the embed today: no committed availability or response-time number you can hold us to. Treat the published uptime as informational.
  • The authoritative signal that a sale completed is not the embed at all — it is the policy.issued webhook delivered to your server (see reconciliation and attribution). Treat the in-page widget as a best-effort UX layer, and your server-side webhook as the system of record.

A status feed tells you what we observe; your integration should still detect degradation itself. The rest of this page is how.

2. The one rule: insurance is optional at checkout

Section titled “2. The one rule: insurance is optional at checkout”

Design your booking flow so the insurance step can fail without taking the trip purchase with it.

customer books trip
├─ insurance embed loads ──► customer adds a policy ──► you collect the quote.selected event
│ and proceed to the embed's checkout/handoff
└─ insurance embed slow / errored / absent ──► customer skips insurance ──► trip purchase still completes

Concretely:

  • Keep the “continue without insurance” path always reachable. Never gate your trip’s “pay” button on a successful embed mount or on receiving a quote.ready.
  • Render the widget into a dedicated container that can collapse to nothing. If it never paints, your layout should not break.
  • Do not block your own checkout on any embed postMessage event. The events (quote.ready, quote.selected, payment.succeeded, payment.failed) are advisory UX signals, not gates.

3. What “down” looks like, and how each fails

Section titled “3. What “down” looks like, and how each fails”

The embed has several independent failure points. Each one degrades on its own — design for all of them.

FailureWhat the customer seesWhat your page receivesGraceful response
widget.js fails to loadEmpty containerExpeditionInsure global never definedDetect a missing global, hide the container, keep checkout open
mount() throwsNothing rendersSynchronous exception from mount()Wrap mount() in try/catch; on throw, hide the container
iframe loads but quote API is slow/downSpinner, then an error state inside the iframeA quote.error event ({ code, message }), or simply no quote.readyDon’t wait on quote.ready; show your skip path
Origin not allowlistedBlank frame (framing fails closed)No eventsHide the container; contact us to allowlist your origin
Rate limit hit (429)Quote can’t generatequote.error inside the iframeSurface the iframe’s own error; keep checkout open

Framing fails closed by design. If your origin isn’t on the allowlist, the browser refuses to render the frame rather than showing a degraded one. See the security model for the frame-ancestors mechanism. The practical takeaway here: a blank frame is a possible normal state, so your layout must tolerate it.

The embed HTTP API returns standard status codes — 401/403 for auth and origin problems, 429 with a Retry-After header when a publishable key exceeds 60 requests per minute, and 200 for success. Quote generation runs inside the iframe, so your page sees these as a quote.error event rather than a raw HTTP status. You don’t have to parse them yourself; you only have to not block on success.

Guard every entry point. The loader exposes exactly three members on the global — mount, on, and embedOrigin — and mount() throws on a missing target or a missing/invalid pk, so both the global and the call are worth guarding.

<div id="ei-quote"></div>
<script src="https://expedition.insure/widget.js"></script>
<script>
function mountInsurance() {
// 1. Did the loader script even load?
if (typeof window.ExpeditionInsure === "undefined") {
hideInsurance(); // your function: collapse the container
return;
}
try {
// 2. mount() throws on a bad target or a missing pk
window.ExpeditionInsure.mount("#ei-quote", {
pk: "pk_op_...", // ships in your HTML — public by design
destination: "antarctica",
tripCost: 18000, // whole dollars
});
} catch (err) {
console.warn("[insurance] embed failed to mount:", err);
hideInsurance();
return;
}
// 3. The quote API may still be slow or down. Don't wait on it —
// just log the in-iframe error if it surfaces.
window.ExpeditionInsure.on("quote.error", function (payload) {
console.warn("[insurance] quote error:", payload && payload.code, payload && payload.message);
// Your trip checkout stays open regardless.
});
}
mountInsurance();
</script>

Notes:

  • on() itself throws if you subscribe to an unknown event name. Stick to the documented names (quote.ready, quote.selected, quote.error, payment.succeeded, payment.failed).
  • A faulty handler you register can’t break the bridge — the loader wraps each handler in try/catch and logs listener error if it throws. Other listeners still fire.
  • There is no unmount() or update(). To recover a broken instance, clear the container and call mount() again.

5. Don’t time out your own checkout on the embed

Section titled “5. Don’t time out your own checkout on the embed”

If you wait for quote.ready (or any embed signal) before letting the customer continue, a slow quote API becomes a blocked sale. Instead, treat the embed as fire-and-forget and let the customer drive:

  • Never put your “pay for the trip” button behind a quote.ready await.
  • If you want a loading affordance, give it your own short timeout and then reveal the skip path — don’t wait indefinitely for an event that may never arrive.
  • The widget reports its own height via an internal resize message, so a slow or empty iframe won’t leave a fixed gap once it settles; still, your container should look intentional when collapsed.

When the embedded checkout completes, the iframe emits payment.succeeded so you can update your UI immediately. Do not treat it as proof of sale.

payment.succeeded is a UX signal only. It can be missed — a closed tab, a dropped frame, or a strict outer sandbox all swallow it. The authoritative post-sale signal is the policy.issued webhook delivered to your server, which is what records the commission and is safe to reconcile against. Build fulfillment and accounting on the webhook, never on the in-page event. Full payload, signature, and retry behavior are in reconciliation and attribution.

This separation is exactly what makes an embed outage survivable: even if every in-page event is lost, the sale is still recorded server-side and delivered to you out of band.

When you read amounts off any event, use premiumCents (integer minor units) and divide by 100 for display.

  • Trip checkout completes with the embed entirely absent.
  • widget.js missing → container collapses, no layout break.
  • mount() wrapped in try/catch; a throw hides the container.
  • No “pay” button gated on quote.ready or any embed event.
  • A blank frame (origin not allowlisted) doesn’t break your layout.
  • quote.error is logged, not fatal.
  • Fulfillment keys off the policy.issued webhook, not payment.succeeded.
  • Amounts read from premiumCents (integer minor units), divided by 100 for display.

If you need your origin allowlisted, or you’re seeing repeated 429s and want to discuss limits, reach a human at help@expedition.insure.