Envie
Guides

Guest → customer merge

How an anonymous wishlist becomes the customer's when a shopper logs in — and what your integration has to do (usually nothing).

This page explains what happens to a guest wishlist at login, and the one call headless integrations make to trigger it.

The model

A shopper who isn't logged in saves into an anonymous list, keyed by an opaque anonymousId generated in their browser (stored in localStorage as envie:aid). Knowing the id is the access: it's a capability, like an unguessable URL — not an identity, not a session.

When that shopper logs in, the anonymous list merges into their customer list:

  • Items are copied over; a product already on the customer list is not duplicated.
  • The anonymous list is deleted. The anonymousId now points at nothing.
  • Original addedAt timestamps are preserved.
  • A list.merged event fires (webhooks, integrations), with the count of items actually moved.

The merge is idempotent and replay-safe: calling it twice, or from two tabs racing each other, merges once — the loser finds nothing left to merge and gets the customer's list back unchanged.

Liquid storefronts: nothing to do

The app embed tells the client who's logged in on every page load. First page view after login → merge happens automatically. Both the guest saves and the customer's earlier saves are in the list; the badge count updates.

Headless storefronts: one server-side call

A browser holding a public key can never assert "I am customer 42" — identity must come from your server, after your own authentication:

import { createEnvie } from "@getenvie/client";

// In your login handler, after the customer is authenticated:
const envie = createEnvie({
	mode: "private",
	shop: "your-store.myshopify.com",
	secretKey: process.env.ENVIE_SECRET_KEY!, // env_sk_… — server only
	anonymousId, // the shopper's `envie:aid`, sent up from the browser
});

const mergedList = await envie.identify({ customerId });

The response is the merged list — hand it back to the browser if you want the UI to update without a refetch.

Getting the anonymousId to your server: read localStorage.getItem("envie:aid") in the browser and include it in your login request (a form field, a JSON property — whatever your auth flow uses).

Watching merges happen

  • Server: subscribe to list.merged via outbound webhooksdata: { listId, customerId, mergedCount }.
  • Klaviyo: the merge forwards as Envie Wishlist List Merged when the integration is on — useful for a "welcome back, your wishlist is safe" flow.

Common mistakes

  • Calling identify() from the browser in public mode. Rejected, by design (identify-forbidden). Route it through your server.
  • Reusing one server-side client for every shopper. anonymousId binds at construction. Build the client per request, with that shopper's id.
  • Expecting the anonymous list to survive. After the merge it's gone. If your UI caches list state keyed by anonymousId, refetch after login.
  • Treating anonymousId as PII-free session data to log freely. It grants read/write access to that wishlist. Don't put it in URLs you share or logs you keep long-term.

On this page