Identity & merge
How the client identifies a shopper — the anonymous id, the login merge, and why a browser can never claim to be a customer.
This page is the model behind "the wishlist follows the shopper": anonymous ids, and the merge that happens at login.
The anonymous id
A shopper who isn't logged in saves into a list keyed by an opaque UUID. The client mints it on
first use and keeps it in localStorage (envie:aid) — you never manage it in proxy or
public mode.
envie.currentAnonymousId; // "1e5f4b6a-…" — or undefined server-sideIt's a capability, not an identity: knowing the id grants access to that one list, like an unguessable URL. Don't log it or put it in shareable URLs.
Merge at login
When a shopper logs in, their anonymous list merges into their customer list: items copy over, duplicates collapse, timestamps survive, the anonymous list is deleted. Idempotent and race-safe — two tabs merging at once still merge once.
How you trigger it depends on where you run:
Liquid storefronts: automatic
The theme app embed passes customer.id to createEnvie, which runs the merge once per
(browser, customer) and refreshes component state. Nothing to write.
Headless: pass customerId where you create the client — or call identify()
If your server renders the storefront shell, the simplest path is the same one Liquid uses:
const envie = createEnvie({
mode: "private",
shop: "your-store.myshopify.com",
secretKey: process.env.ENVIE_SECRET_KEY!,
anonymousId: aidFromBrowser, // the shopper's envie:aid, sent with your login request
customerId, // your authenticated customer — triggers the one-shot merge
});For explicit control, identify() is the direct call:
const mergedList = await envie.identify({ customerId });
// → the combined list; hand it back to the browser to skip a refetchUnder the hood customerId uses identifyOnce() — same merge, plus a local marker so repeat
page views don't re-send it, and an envie:merged DOM event on success (browser only).
Never from a public-mode browser
identify() throws in public mode before any request is made. A browser-held key asserting "I
am customer 42" would let anyone read any customer's list — identity always comes from your
server (or from Shopify's signed proxy, on Liquid). The API enforces the same rule:
identify-forbidden.
After the merge
- The old anonymous id is spent — the client rotates to a fresh one automatically.
envie:mergedfires ondocumentwith the merged list (see events).- Server-side,
list.mergedgoes out via webhooks and Klaviyo.
Common mistakes
- One long-lived server client for all shoppers.
anonymousIdbinds at construction — build the client per request during login flows. - Caching list state keyed by the anonymous id. It's deleted by the merge; refetch after login.
- Trying to "merge" by re-adding items customer-side. Unnecessary and lossy —
identify()preservesaddedAtand dedupes for you.