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.
1. Where the boundary sits
Section titled “1. Where the boundary sits”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.
2. What PII crosses the boundary
Section titled “2. What PII crosses the boundary”Into our frame (you → us)
Section titled “Into our frame (you → us)”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:
| Field | Personal data? | Notes |
|---|---|---|
destination, startDate, endDate, tripCost, travelers | No | Trip parameters. |
ages | Possibly | Ages of travelers, serialized as CSV. Not directly identifying on its own. |
residence | Possibly | Residence country code. |
ref | Possibly | Your 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:
| Field | Personal data? | Required? |
|---|---|---|
email | Yes | Required. |
name | Yes | Optional (defaults to a placeholder). |
phone | Yes | Optional. |
travelerAges / travelerDobs | Yes | Used to price; DOB resolves to age at departure. |
residence, state | Possibly | Used for eligibility. |
destination, startDate, endDate, durationDays, tripCost, currency | No | Trip 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.
Back to your page (us → you)
Section titled “Back to your page (us → you)”The ei:* postMessage events are the only data we hand to your page. None carry
traveler PII:
| Event | Payload | Contains 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.succeededis 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-serverpolicy.issuedwebhook. Reconcile against the webhook, never againstpayment.succeeded.
3. Origin locking — who can read what
Section titled “3. Origin locking — who can read what”Personal data is constrained at three independent layers, none of which rely on the publishable key being secret:
- 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 whoseevent.originis not our embed origin. A plan ID or premium can never leak to an unrelated enclosing frame. - 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 gets403with no CORS headers — the browser blocks the body. We never reflect*as an allowed origin; only your matched origin is echoed. - Per-key scope.
GET /api/embed/optionsonly returns options for quotes owned by your operator. A foreign or unknown quote ID returns a bare403with 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.
4. Sub-processors
Section titled “4. Sub-processors”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.
| Processor | Role | Data it sees |
|---|---|---|
| Cloudflare | Edge serving and the iframe origin (expedition.insure); requests transit Cloudflare’s network. | Request metadata, traffic; iframe page content. |
| Convex | Backend database and HTTP API that stores quotes and serves pricing/options. | Quote records, including the traveler PII listed in section 2. |
| Stripe | Payment 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.
5. Retention
Section titled “5. Retention”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.
6. GDPR and CCPA posture
Section titled “6. GDPR and CCPA posture”- 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.
7. Data residency
Section titled “7. Data residency”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.
8. Getting a DPA
Section titled “8. Getting a DPA”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.