A cart event is a signal, not data. It tells you the cart moved. Read the
authoritative cart from Show a cart, which
needs only the cart token. Never treat the fields inside the message as the
cart itself.
How checkout loads your embed
Fluid renders your embed URL in a sandboxed iframe and appends the cart token as a query parameter, plus the company id when it is known:?token= to your configured embed URL as-is, so configure the URL without a query string of its own.
The iframe sends no referrer, so your page cannot learn the parent’s origin from document.referrer. Fluid-hosted checkout is served from checkout.fluid.app (some merchants use a subdomain of it) and, for staging, checkout.fluid-staging.app. Checkout posts every message to your embed’s exact origin — a message never reaches a window on a different origin.
Your iframe is sandboxed to allow-same-origin allow-scripts. window.open, links with target="_blank", and top-level navigation fail silently. Only embeds served from Fluid-owned hosts are granted popups and user-activated top navigation.
1
Announce that you are ready
Your listener may be installed after checkout has already sent its first
message. Announce readiness as soon as your page loads, and checkout replays
its latest cart snapshot to you:Post the handshake only to origins you accept, never to
"*" — the parent
window can navigate, and a wildcard would hand the cart token to whatever
document is there when the message is dispatched. A checkout served from a
subdomain of those hosts will not receive this first handshake; it still
replays when your iframe finishes loading, so nothing is lost, and a
duplicate is harmless. Once any validated message arrives, remember its
origin and post to that origin alone (next step).2
Listen for cart events and validate every one
Checkout posts The
FLUID_CART_UPDATED to your window each time its own cart
request completes and nothing else is in flight for this cart. Edits the
shopper is still making do not fire. Validate the sender, the origin, the
version, and the cart token before you act, and ignore any sequence you
have already seen:sequence counts up per cart, per checkout page and is shared by
every embed on that page, so two embeds see the same numbers and an embed
that mounts late sees a first number above 1. A replay — from the ready
handshake, or from checkout noticing your iframe loaded — carries the same
number as the message it repeats, so within one page load the guard above
makes it a no-op. After your iframe reloads, lastSequence starts over and
the replay triggers one read, which is exactly what a fresh page needs.3
Read the cart and render
Read the cart from your own server: your page asks your server, and
your server calls Show a cart with the
cart token from your URL. That page carries the request and response
contract; this guide only needs the token to reach it.Serialize your refreshes as above: if an event arrives while a read is in
flight, run one more read after it finishes rather than starting a second
one, and hand that later read’s result to the caller that asked for it — a
result from a read that began before the event says nothing about it. Catch
every failure and keep the last good render on screen, so a
read that fails never surfaces as an error in checkout. Rendering from the
cart you fetched, never from the message, means a spoofed or stale message
can at worst cause an extra read.
Browsers cannot call the checkout surface directly from a third-party
origin — cross-origin requests to it are allowed only from Fluid-owned and
registered merchant domains. If you must read from the browser, the
FairShare SDK’s own Retrieves a cart
operation accepts any origin and needs only the cart token. It is
SDK-internal: for a direct REST integration, use the checkout surface from
your server.
4
Keep a fallback for surfaces without events
Only the checkout page sends cart events. The same embed can also render on
the storefront cart, which has a live cart but sends no events, and on
order confirmation, where the Treat this fallback as permanent, not as scaffolding to remove later. It is
what makes your embed correct on every surface, not only on checkout.
token query parameter is an order token
— not a cart token — so do not read the cart there at all. Read once
immediately, then keep a slow poll — 30 seconds or more, paused while the
tab is hidden. Stop it only after an event-driven read has rendered
successfully, and start it again whenever an event-driven read fails:
once the poll is gone and the sequence has advanced, a replay is ignored,
so a failed read with no poll behind it would leave the embed stale until
the next cart change. Stop it, too, when a read answers 404, which means
the token is not a cart token on this surface:5
Report your height
Checkout sizes your iframe to the height you report. Send it after every
render, and again when your content changes:Until you report, the iframe uses the height configured for the drop zone,
or 120 pixels. Checkout accepts heights between 1 and 9,999 pixels, adds 16
pixels of padding, and ignores anything else.
Message reference
Checkout posts one message type to your embed:
Your embed posts two message types to checkout:
Payment details never travel in a cart event. Card data, tokens, and available payment methods are excluded from both the message and the change detection behind it.
Checkout sends at most 20 messages per 10 seconds for one cart. Past that it holds the newest state and sends it once the window rolls, so nothing is lost — do not debounce-and-drop on your side.
What triggers an event
Any change checkout learns about from its own cart request: items added, removed, or changed; the shipping address or method; a discount applied or removed; the email; and metadata written through Update cart metadata — including writes your own app makes. A metadata write does not by itself recalculate discounts. Transport-only updates do not fire. A refetch of the same cart, a cache refresh, or a change to fields that only carry request timestamps produce no message, so a quiet checkout stays quiet.Discount droplets: country-scoped registrations
If your droplet prices discounts through theupdate_cart_discount callback, two behaviors are worth knowing:
- Checkout selects your registration by the cart’s destination — the shipping address country first, falling back to the cart’s country before a shipping address exists. A registration scoped with
country_codesis chosen when that destination matches, and the same destination is used when the callback is dispatched, so the registration check and the call never disagree. When the cart has no destination yet, only registrations with an emptycountry_codesfire. Manage scoping with Create a callback registration and Update a callback registration. - External discounts are recalculated on apply, on remove, on a manual refresh, and otherwise at most once an hour per cart. So when the last active registration for a company is removed or deactivated, a discount your droplet previously applied clears on the next of those — immediately after a discount code changes, or within the hour otherwise. The FairShare SDK’s Recalculate the cart’s external discounts operation forces that pass from the browser; it is SDK-internal.
Security checklist
- Accept a message only when
event.source === window.parent— a sibling iframe on the same page shares your parent’s origin but not its window. - Check
event.originagainst the checkout hosts you expect:https://oncheckout.fluid.apporcheckout.fluid-staging.app, or a subdomain of either. - Never place a company bearer token in the embed. The cart token is the only credential a checkout drop zone needs.
- Require
version === 1and acart_tokenequal to your own. - Ignore a
sequenceat or below the last one you handled. - Render from Show a cart, never from the message.
- Keep your embed able to fail without blocking checkout: a failed read should leave your last good render in place, not throw an error into the page.