Embed overview
This page is for engineers deciding how to add Expedition Insure to a travel site or AI agent. It explains what the embed is, the three integration tiers and their status, who holds the insurance charge, and the trust boundaries that keep an iframe from touching your page. From here, see the integration guide for the loader API, the events reference for postMessage payloads, and the webhooks reference for the policy.issued signal.
What the embed is
Section titled “What the embed is”The embed lets you sell travel insurance on your own site without building a quote engine, holding carrier relationships, or becoming a regulated seller. You drop in a <script> that mounts an iframe served from our origin (https://expedition.insure). The iframe runs the full quote flow — destination, dates, travelers, live carrier pricing — and reports lifecycle events back to your page over a namespaced ei:* postMessage channel. Depending on the tier you choose, the shopper either hands off to our hosted checkout or pays inline, but the insurance transaction always settles on our rails. You authenticate every request with a publishable key (pk_op_...), which is public by design, like a Stripe pk_.
The three tiers
Section titled “The three tiers”Each tier is a different depth of integration, from a plain link to an in-iframe purchase. Pick the lowest tier that meets your needs — the lower tiers carry the least surface area on your page.
| Tier | What it is | How the shopper pays | Status |
|---|---|---|---|
| Link | An <a> to our hosted quote flow at https://expedition.insure/quote, with ?origin=<slug> for attribution. No script, no iframe. | On our hosted site | GA |
| Embedded quote → handoff | The widget.js loader mounts an iframe quote form on your page. On quote.selected, the shopper hands off to our hosted /options checkout via a user-activated top-navigation (or the ei:navigate fallback). | On our hosted site, after handoff | Beta |
| In-iframe checkout | The shopper completes payment inside the iframe via Stripe.js Elements against an insurance-only PaymentIntent on our Stripe account. Emits payment.succeeded / payment.failed. | Inline, inside the iframe | Beta |
Server-to-server API is not consumable yet. Secret keys (
sk_op_...) are mintable, but no HTTP route consumes them today — there is no Tier-3 server-to-server endpoint. All embed traffic authenticates with the publishable key.[needs-product-work]
Merchant of record
Section titled “Merchant of record”Expedition Insure is the merchant of record (MoR) for the insurance charge in every tier.
- The insurance charge is ours. The premium settles on our Stripe account on a PaymentIntent we create. In the in-iframe checkout tier, that PaymentIntent is insurance-only — your trip charge stays on your own payment rail, on a separate transaction.
- Statement descriptor. The insurance line item appears on the shopper’s card statement under our descriptor, not yours — it is our charge of record. Surface this in your own confirmation copy so shoppers recognize the line item.
- Chargeback boundary. Because we are MoR for the premium, insurance chargebacks, refunds, and disputes are ours to handle. Your trip charge and its disputes remain entirely yours. The two transactions never commingle.
Settlement of what you are owed runs through the commission ledger and is paid out-of-band on a monthly invoice. See the webhooks reference for the authoritative post-sale signal.
payment.succeededis a UX signal only. It tells your page to update the UI, and it can be missed (a closed tab, a dropped frame). The authoritative post-sale signal is thepolicy.issuedwebhook — reconcile sales and commissions against it, never againstpayment.succeeded. Always readpremiumCents(integer minor units) and divide by 100 for display.
Trust boundaries at a glance
Section titled “Trust boundaries at a glance”The embed is built so that a misbehaving or compromised iframe can never read or redirect your page, and so a leaked publishable key cannot be used from an origin you did not authorize.
| Boundary | What it does |
|---|---|
Publishable key (pk_op_...) | Public by design — like a Stripe pk_. It authenticates the request but is not the security boundary. Stored in plaintext; safe to ship in client code. |
| Per-origin allowlist | The real boundary. Every embed request is checked against your operator’s allowedOrigins. A request from an origin you have not allowlisted gets a bare 403 with no CORS headers — the browser blocks the body from the page. You manage this list from the operator portal. |
| Rate limit | 60 requests per minute, per publishable key, fixed-window. Over the limit returns 429 with a Retry-After header. Back off and retry; do not hammer. |
| Origin-locked postMessage | The ei:* channel is origin-locked in both directions. The iframe only posts to your exact page origin, and the loader only trusts messages from our origin — "*" is never used as a trust value. A leaked key cannot exfiltrate quote data to an arbitrary frame. |
| iframe sandbox | The iframe runs under a strict sandbox. Top-navigation is scoped to user activation, so the iframe can never silently redirect your page. |
The publishable key, the origin allowlist, and the rate limit are all per-operator and configured in the operator portal.
High-level flow
Section titled “High-level flow”The shopper interacts only with your host page and the framed quote UI. Pricing, payment, and carrier integrations all happen on our origin — you never touch a carrier API or hold the premium.
┌─────────────────────────────────────────────────────────────┐│ Host page (your site) ││ • <script src="https://expedition.insure/widget.js"> ││ • mount(target, { pk: "pk_op_...", ... }) ││ • on("quote.selected" | "payment.succeeded", handler) ││ ││ ┌───────────────────────────────────────────────────┐ ││ │ iframe (served from https://expedition.insure) │ ││ │ /embed/quote · /embed/checkout · /embed/confirmed │ ││ │ sandboxed · origin-locked ei:* postMessage │ ││ └───────────────────────────────────────────────────┘ │└───────────────────────────────┬─────────────────────────────┘ │ ei:* postMessage (origin-locked both ways) │ + pk-authenticated HTTPS ▼┌─────────────────────────────────────────────────────────────┐│ Our origin (expedition.insure) ││ • pk auth + per-origin allowlist + 60/min rate limit ││ • quote pricing, eligibility, instant options ││ • Stripe (we are merchant of record for the premium) ││ • policy.issued webhook → your endpoint (authoritative) │└───────────────────────────────┬─────────────────────────────┘ │ carrier rate lookups + binding ▼┌─────────────────────────────────────────────────────────────┐│ Carriers / underwriters │└─────────────────────────────────────────────────────────────┘Next steps
Section titled “Next steps”- Integration guide — install the loader, call
mount()andon(), and configure your operator. - Events reference — the full
ei:*event catalog and payload shapes. - Webhooks reference — verify and consume the authoritative
policy.issuedwebhook.