Versioning & changelog
This page tells you how Expedition Insure ships changes to the embed surfaces you depend on — the widget.js loader, the embed HTTP API, the operator portal, and the policy.issued webhook — and how to track them. It’s for the engineers who keep your integration running after launch. If you’re still wiring things up, start with the embed integration guide and the MCP overview; come back here when you need to know how things change underneath you.
1. What “version” means here
Section titled “1. What “version” means here”There is no global semantic version number you pin against today. Each surface evolves independently, and we treat its public contract — not a version string — as the thing we keep stable:
| Surface | Public contract you depend on | How you reference it |
|---|---|---|
widget.js loader | window.ExpeditionInsure global (mount, on, embedOrigin), MountConfig params, the ei:* event payloads | https://expedition.insure/widget.js (rolling) or https://expedition.insure/widget/<x.y.z>.js (pinned) |
| Embed HTTP API | POST /api/embed/quote, GET /api/embed/options, status codes, response JSON | Path + your pk_op_... key |
policy.issued webhook | The PolicyIssuedPayload JSON body, X-EI-Signature HMAC scheme | Your registered endpoint |
| Operator portal | api.operatorPortal.* read/write contracts | Session-authenticated portal |
The rules below describe how we change each contract without breaking you.
Pinning the loader
Section titled “Pinning the loader”The loader is published on two channels:
<!-- Rolling: the latest loader; picks up additive, backward-compatible fixes automatically (see §2). The right default for most integrations. --><script src="https://expedition.insure/widget.js"></script>
<!-- Pinned: this exact build. Immutable — it never changes under you. Upgrade deliberately by bumping the version in the URL. --><script src="https://expedition.insure/widget/1.0.0.js"></script>| Channel | URL | Caching | Use it when |
|---|---|---|---|
| Rolling | https://expedition.insure/widget.js | revalidated on every load | default — you want additive fixes automatically, no redeploy |
| Pinned | https://expedition.insure/widget/<x.y.z>.js | immutable, cached long-lived | you need change-control and want to review each loader upgrade yourself |
A pinned URL is an immutable snapshot: widget/1.0.0.js always serves the same bytes. A new loader build publishes a new pinned URL and updates the rolling widget.js; existing pinned URLs are never changed or removed, so a build you pinned can’t shift under you. New pinned versions are announced in the changelog (§6). To upgrade, change the version in your <script> tag and re-test.
Pinning covers the loader only. The embed HTTP API and the policy.issued webhook are always served from their live contracts — there is no pinned version of those, so build them for forward compatibility (below).
2. Compatibility policy
Section titled “2. Compatibility policy”We classify every change to a public surface as additive or breaking, and we treat them very differently.
Additive changes (ship without notice)
Section titled “Additive changes (ship without notice)”These are backward-compatible and may go live at any time, including on the rolling widget.js (a pinned widget/<x.y.z>.js stays frozen until you upgrade it):
- A new optional
MountConfigparam. Unknown params you don’t set are simply omitted from the iframe URL — adding one never affects an existingmount()call. - A new optional field on an existing event payload or API response. Your handlers and parsers must ignore fields they don’t recognize.
- A new optional field on the
policy.issuedwebhook body. Verify the signature over the raw body (see the webhook guide), then read only the fields you need. - A new
ei:*internal message type. The loader silently ignores inbound message types it doesn’t know (default: return), so this can’t break you. - A new event name added to the public set. Subscribing is opt-in via
on(); an event you never subscribe to costs you nothing.
Build for forward compatibility. Treat every event payload and API response as an open map: read the fields you use, ignore the rest. Never assert on an exact key set. This is the single most important thing you can do to stay compatible with the rolling loader and the always-live API and webhook surfaces.
Breaking changes (announced first)
Section titled “Breaking changes (announced first)”A change is breaking if it could make a correctly-written integration stop working. Examples:
- Removing or renaming a
MountConfigparam, an event name, an event payload field, an API field, or a webhook field. - Changing the type or units of an existing field (for example, treating a whole-dollar field as minor units).
- Changing a status code, an auth requirement, or the rate limit in a way that rejects previously-valid traffic.
- Changing the
X-EI-Signaturescheme or the bytes that are signed.
For breaking changes we follow the process in §4 — advance notice, a deprecation window, and a changelog entry — before anything is removed.
3. How we add fields
Section titled “3. How we add fields”We grow the contract additively. New fields appear on event payloads, API responses, and the policy.issued webhook body without notice (see §2) — your handlers and parsers must ignore fields they don’t recognize. Money is always carried in integer minor units: read premiumCents and commissionCents and divide by 100 yourself for display.
ExpeditionInsure.on("quote.selected", (e) => { // Integer minor units (cents): const cents = e.premiumCents; // e.g. 41900 const dollars = (cents / 100).toFixed(2); // "419.00"});If we ever need to change the type or units of an existing field, or remove one, that is a breaking change and follows the process in §4 — advance notice, a migration window, and a changelog entry — before anything changes.
4. How we communicate breaking changes
Section titled “4. How we communicate breaking changes”When a breaking change is unavoidable, the sequence is:
1. Announce → changelog entry (§6) + the affected guide updated, describing the change, the migration, and the window.2. Window → old + new behavior coexist for a deprecation window so you can migrate without downtime.3. Cut over → old behavior removed; a final changelog entry records the removal and its date.What you should do to stay ahead of it:
- Watch the changelog (§6). It is the authoritative record of what changed and when.
- Keep a real contact on file. For the operator portal and webhooks, make sure we have a technical contact who reads change notices.
- Build forward-compatible per §2 — read the fields you use, ignore the rest.
Surfaces with no breaking-change shield yet
Section titled “Surfaces with no breaking-change shield yet”Some capabilities don’t exist, so there’s nothing to deprecate — but you should know the current reality so you don’t build against a gap:
- No
policy.cancelled,policy.refunded, orcommission.voidedwebhook. [needs-product-work]policy.issuedis the only webhook event we emit. You are not notified when a commission is voided or clawed back. Reconcile commission state from the operator portal rather than assuming webhook coverage of the full lifecycle. - No operator-facing setter for
webhookUrl,webhookSecret, orcommissionRateBps. [needs-product-work] These are configured by Expedition Insure during onboarding, not self-served in the portal. The portal exposescommissionRateBpsread-only. - No
sk_op_(secret-key) HTTP endpoint. [needs-product-work] Secret keys can be minted, but no server-to-server route consumes them today. Build against the publishable-key embed API (pk_op_...) for now.
5. Authoritative signals vs. UX signals
Section titled “5. Authoritative signals vs. UX signals”One sequencing rule outlives any version change, so write your integration around it from day one:
payment.succeededis a UX-only signal. It fires from the in-iframe checkout return so you can update your UI promptly, but it can be missed (closed tab, dropped frame, blocked message). Never treat it as proof a policy exists.- The
policy.issuedwebhook is the authoritative post-sale signal. Policy issuance and the commission record are written server-side off the Stripe webhook, independent of whether the browser ever deliveredpayment.succeeded. Reconcile and fulfill onpolicy.issued, deduping on itseventId.
We will not break this contract: even as fields are added, policy.issued remains your source of truth and payment.succeeded remains advisory.
6. Changelog
Section titled “6. Changelog”Newest first. This is the public record of contract-affecting changes. Additive changes are listed for awareness; breaking changes and removals are always listed.
Embed v1 (current)
Section titled “Embed v1 (current)”widget.jsloader —window.ExpeditionInsurewithmount,on, andembedOrigin. Five public events:quote.ready,quote.selected,quote.error,payment.succeeded,payment.failed. Served rolling fromhttps://expedition.insure/widget.js, or pinned fromhttps://expedition.insure/widget/<x.y.z>.js. See §1.premiumCentsis the money field onquote.selectedandpayment.succeeded— an integer in minor units (cents). Divide by 100 for display.- Embed HTTP API —
POST /api/embed/quoteandGET /api/embed/options, authenticated by a publishablepk_op_...key over a per-operator origin allowlist, rate-limited at 60 requests/minute per key. policy.issuedwebhook — signed withX-EI-Signature(Stripe HMAC scheme,t=<unix-seconds>,v1=<hex>), delivered at-least-once with up to three attempts; dedupe oneventId. Body carriespremiumCentsandcommissionCents(integer minor units). It is the only webhook event today.- Operator portal — self-serve provisioning, publishable-key rotation, and allowed-origin management via
api.operatorPortal.*. Embedding starts disabled until enabled by Expedition Insure.
Keeping this current. When a contract-affecting change ships, it lands here first. If you spot drift between this changelog and observed behavior, reach a human at help@expedition.insure.