Skip to content

Data, privacy, and DPA

What personal data crosses the boundary when you embed Expedition Insure, who processes it, and how to get a data processing agreement (DPA). For partner and operator tech teams doing a privacy or security review before going live.

For the mechanics that this page describes — the iframe, the postMessage events, and the server-side allowlist — start with the embed integration guide. For the post-sale policy.issued webhook payload, see the same guide’s webhook section.

Not legal advice. This page describes how the integration handles data today, grounded in the running system. It is not a contract. For a signed DPA or specific contractual commitments, email help@expedition.insure.

The embed is deliberately structured so your page never handles insurance data directly. widget.js injects a sandboxed <iframe> that loads https://expedition.insure/embed/quote — served from our origin, not yours. The quote form, the options list, and (for in-iframe checkout) the Stripe Elements form all run inside that frame.

Your page Our iframe (expedition.insure) Expedition Insure backend
───────── ────────────────────────────── ─────────────────────────
widget.js loader ──mount──▶ /embed/quote ──HTTPS──▶ Convex HTTP API (.convex.site)
│ postMessage ◀──ei:*── React island │
│ (locked origins) ├─ pricing + eligibility
└─ your handlers └─ Stripe (checkout only)

Two consequences for data flow:

  • Traveler PII is entered into our frame, not yours. Names, ages or dates of birth, email, and trip details are typed into the iframe and posted directly to our backend. They do not transit your page’s DOM or your servers.
  • What comes back to your page is minimal and non-identifying. The only data we post to your page is the ei:* event payloads (see section 3) — plan IDs, premiums, a quote ID, and an optional currency. No traveler PII is in those events.

If you pre-fill the widget via mount(target, config), the config params travel in the iframe src query string. Most are trip parameters, but a few can be personal data depending on what you pass:

FieldPersonal data?Notes
destination, startDate, endDate, tripCost, travelersNoTrip parameters.
agesPossiblyAges of travelers, serialized as CSV. Not directly identifying on its own.
residencePossiblyResidence country code.
refPossiblyYour referral/tracking ref — personal only if you encode something personal in it.

You do not pass traveler names, email, or dates of birth through mount() — those fields are collected inside our frame. The host param the loader appends is your page origin (window.location.origin), not personal data.

Pre-filled params are in a URL. Query-string values can land in browser history and referrer headers on your side. Don’t pre-fill anything you wouldn’t put in a URL. If you pass ref, keep it opaque.

Entered inside our frame (traveler → us)

Section titled “Entered inside our frame (traveler → us)”

The quote endpoint (POST /api/embed/quote) accepts and stores these fields against the quote. The traveler types them into our form:

FieldPersonal data?Required?
emailYesRequired.
nameYesOptional (defaults to a placeholder).
phoneYesOptional.
travelerAges / travelerDobsYesUsed to price; DOB resolves to age at departure.
residence, statePossiblyUsed for eligibility.
destination, startDate, endDate, durationDays, tripCost, currencyNoTrip parameters.

The quote is attributed to your operator account server-side (your publishable key maps to your operatorId). That attribution lets you read back the quote’s options via GET /api/embed/options, but the options read is scoped to your operator — you cannot read a quote owned by another operator.

The ei:* postMessage events are the only data we hand to your page. None carry traveler PII:

EventPayloadContains PII?
quote.ready{ quoteId, plansCount }No
quote.selected{ planId, premiumCents, premium, currency }No
quote.error{ code, message }No
payment.succeeded{ quoteId, planId, premiumCents, premium, currency }No
payment.failed{ code, message }No

premiumCents (integer minor units) is the premium field — build against premiumCents and divide by 100 for display.

In the policy.issued webhook (us → your server)

Section titled “In the policy.issued webhook (us → your server)”

If you configure a webhook, the signed policy.issued POST carries policyId, operatorId, premiumCents, commissionCents, and timestamps — financial and policy-reference data, not traveler PII (no name, email, or DOB). Verify the X-EI-Signature HMAC and dedupe on eventId, as the integration guide describes.

Sequencing. payment.succeeded is a UX-only signal posted from the iframe and can be missed (closed tab, lost message). The authoritative post-sale signal is the server-to-server policy.issued webhook. Reconcile against the webhook, never against payment.succeeded.

Personal data is constrained at three independent layers, none of which rely on the publishable key being secret:

  1. postMessage origin lock (both directions). Our iframe only posts ei:* messages to your exact page origin, and it refuses to broadcast to "*". The loader independently drops any inbound message whose event.origin is not our embed origin. A plan ID or premium can never leak to an unrelated enclosing frame.
  2. Server-side Origin allowlist. Every call to the embed HTTP API is checked against your operator’s allowedOrigins. A request from an origin you haven’t allowlisted gets 403 with no CORS headers — the browser blocks the body. We never reflect * as an allowed origin; only your matched origin is echoed.
  3. Per-key scope. GET /api/embed/options only returns options for quotes owned by your operator. A foreign or unknown quote ID returns a bare 403 with no existence oracle.

The publishable key (pk_op_...) is public by design, like a Stripe pk_. The security boundary is the Origin allowlist plus a per-key rate limit, not key secrecy. Manage your allowlist from the operator portal.

The embed and checkout path involve these processors. Exact entities and the current sub-processor list are confirmed in the DPA — request it at help@expedition.insure.

ProcessorRoleData it sees
CloudflareEdge serving and the iframe origin (expedition.insure); requests transit Cloudflare’s network.Request metadata, traffic; iframe page content.
ConvexBackend database and HTTP API that stores quotes and serves pricing/options.Quote records, including the traveler PII listed in section 2.
StripePayment processing for in-iframe checkout. Expedition Insure is the Merchant of Record; the insurance charge is a separate PaymentIntent on our Stripe.Payment/card data, entered directly into Stripe Elements (loaded from js.stripe.com).

Card data is entered into Stripe’s own Elements iframe and is not handled by your page or, in raw form, by ours. Your operator account itself is not a sub-processor — attribution and commission data stay between you and us.

Quote records (including the traveler PII a traveler enters) are stored in Convex for as long as the quote and any resulting policy are operationally needed — pricing, options retrieval, fulfillment, and post-sale support. Commission ledger rows are immutable and retained for financial reconciliation.

Rate limiting keeps only a per-key counter and window, not request contents. Webhook delivery is logged per attempt (status, URL, timestamp) for operational inspection.

Manual data deletion requests (to comply with GDPR). Exact retention windows and deletion SLAs are [needs-product-work]. There is no self-serve data-deletion or export API for embedded quotes today. To request deletion or export of specific records — for a data-subject request you are fielding — email help@expedition.insure and we will handle it manually.

  • Roles. For traveler data entered into the embed, Expedition Insure acts as the controller of the insurance relationship and the quote/policy data. For data you pass via mount() config, you determine what you send. The precise controller/processor split for your integration is set out in the DPA.
  • Lawful basis and disclosures. You are responsible for the notice and consent on your own page — including telling travelers that submitting the form sends their details to Expedition Insure to produce an insurance quote. Don’t pre-fill personal params without a basis to do so.
  • Data-subject requests. Access, correction, and deletion requests for embedded quote data are handled manually today (see section 5) — route them to help@expedition.insure.
  • CCPA. Expedition Insure does not sell traveler personal information. Specific CCPA contractual terms are covered by the DPA.

This is current-state description, not a compliance certification. The signed DPA is the binding instrument for GDPR/CCPA commitments.

The embed origin and API run on Cloudflare’s edge network and Convex; requests transit Cloudflare globally, and the production Convex backend is the source of truth for stored quote data. We do not guarantee a specific data region or residency, and we do not offer a contractual data-region pin today.

If your review requires data to stay in a specific jurisdiction, raise it before going live: help@expedition.insure.

Email help@expedition.insure to request a data processing agreement. Include your legal entity name, the integration tier you’re using, and any jurisdiction-specific requirements (for example a region pin or a specific sub-processor commitment) so we can scope the agreement to your integration.