Envie
ReferenceSDK

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-side

It'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 refetch

Under 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:merged fires on document with the merged list (see events).
  • Server-side, list.merged goes out via webhooks and Klaviyo.

Common mistakes

  • One long-lived server client for all shoppers. anonymousId binds 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() preserves addedAt and dedupes for you.

On this page