Webclat logoWebclat . | Amplitude Solutions

QA - identity

Why does Amplitude's device ID regenerate on every session/tab instead of persisting?

The fix, why it happens, and how to confirm it actually worked in your own instance.

The fix, in short

A device ID that regenerates every session almost always means Amplitude's SDK is writing its device ID to storage successfully but failing to read it back on the next page load - typically because cookies are scoped to the wrong domain for a multi-subdomain site, storage is blocked entirely (private browsing, a strict cookie policy), or a config option explicitly disabled persistence. The fix is to find which of those three it is, not to manually generate and pass your own device ID as a workaround.

Why this happens

By default, Amplitude's browser SDK persists the device ID either in a cookie (name typically prefixed `amp_`) or in localStorage, depending on SDK version and configuration, and reads that same value back on every subsequent init() call on the same origin. If the read-back fails, the SDK has no way to distinguish "this is a returning device" from "this is a new device" and correctly generates a fresh ID - it's behaving exactly as designed given the information it has.

The most common cause on sites with a subdomain structure (marketing site on the root domain, app on `app.` or similar) is a cookie scoped to the wrong domain: if the cookie is set on `app.example.com` specifically rather than `.example.com`, a session that starts on the root domain and later reaches the subdomain (or vice versa) looks like a brand-new device each time it crosses that boundary, even though it's the same browser.

The second common cause is the browsing context itself denying persistence: Safari's Intelligent Tracking Prevention caps script-writable storage lifetime far shorter than most other browsers, private/incognito windows may block first-party storage entirely depending on settings, and some corporate or privacy-extension configurations block all `document.cookie` writes from third-party-looking scripts. In every one of these cases the write silently succeeds or silently fails without an exception your code would ever see.

The third cause is self-inflicted: an explicit `disableCookies: true` or a custom, non-persistent storage provider passed into init() options, often added earlier to satisfy a cookie-consent requirement without also wiring up an equivalent persistent (but consent-respecting) storage mechanism to replace it.

Fix it

Multi-subdomain sites

  1. Decide the cookie domain you actually need: if the same user should be recognized across `www.example.com` and `app.example.com`, the cookie domain must be the parent, e.g. `.example.com`, not either subdomain individually.
  2. Pass that domain explicitly in init options (the exact option name depends on SDK version - check the cookieOptions/domain field in your installed package's init signature) rather than relying on the browser's default cookie-domain inference, which scopes to the exact host by default.
  3. If you deliberately want each subdomain treated as a separate device (a legitimate choice for some architectures), stop here - this isn't a bug, it's the configuration you should keep, and the fix is only needed if cross-subdomain continuity is actually the goal.

Safari / private browsing / storage-blocking contexts

  1. Confirm this is actually the cause by reproducing in a normal (non-private) Chrome or Firefox window on the same site - if the ID persists there and only fails in Safari or a private window, you've isolated it to storage-lifetime policy, not your integration.
  2. Accept that a fully persistent device ID is not achievable in every browsing context - ITP and private modes are deliberate privacy features, not bugs to route around - and instead measure how much of your traffic falls into these contexts (via browser/incognito-adjacent signals in your own analytics) so it's a known, sized gap rather than a mystery.
  3. Where identity continuity actually matters more than anonymous device tracking (a logged-in product, not a marketing site), prioritize `setUserId()` at login over device ID persistence - a stable user ID survives storage resets that a device ID cannot.

disableCookies or a custom storage provider is set

  1. Grep your init call for `disableCookies`, `cookieStorage`, `storageProvider`, or similar options - this is a config choice someone made deliberately, usually for a consent or compliance reason, and needs a replacement, not a deletion.
  2. If the reason was cookie consent: gate the write behind your consent-management flow (send the tracking script itself as `type="text/plain"` until consent is granted, per the standard consent-gating pattern) instead of disabling persistence outright - that gets you both compliance and a working device ID for consented users.
  3. Test with the option removed (or replaced with a consent-gated equivalent) in a fresh incognito window to confirm the ID now persists across a reload before rolling the change out.

How to verify it worked

  1. In devtools Application tab, find the Amplitude cookie or localStorage key, note its exact value, then hard-reload the page (not just navigate) and check the same key again - it should be byte-for-byte identical, not a new value.
  2. Open a second tab to the same site (not a new window) and confirm both tabs report the same device ID via `amplitude.getDeviceId()` in the console - same-origin tabs should always share one device ID.
  3. If you fixed a cross-subdomain issue, repeat the check by starting on one subdomain, then navigating to the other, and confirming the ID carried over rather than resetting.
  4. In Amplitude's User Look-Up, search for the device ID from your test session and confirm a single device profile now accumulates multiple sessions over time, instead of a new device profile appearing on every visit.

Related: What's the correct setup sequence to get Amplitude tracking events inside a React, React Native, or Expo app? · How do I merge Branch.io/Segment attribution data with Amplitude's own user identity so it's not double-counted? · Why does Amplitude intermittently throw "Invalid apiKey" or a runQueuedFunctions error?

Frequently asked questions

Should I just generate my own device ID and pass it to setDeviceId() instead of relying on Amplitude's default?+

Only as a last resort for a context where persistence is structurally impossible (e.g. a server-rendered environment with no client storage at all) - and even then, whatever you generate needs to persist somewhere durable on your end, or you've just moved the same problem one layer over. Fix the underlying storage/domain issue first; it's almost always fixable.

Does a regenerating device ID actually hurt my data, or is it just an odd number in a dashboard?+

It inflates your unique-device counts and breaks any funnel or retention analysis that spans more than one session, because Amplitude has no way to know two sessions with different device IDs were the same visitor. If retention or multi-session funnels look implausibly bad, an unstable device ID is one of the first things worth ruling out.

Still stuck, or want it checked properly?

The free scored audit reads your Amplitude instance across ten dimensions - taxonomy health, identity resolution, revenue tracking, and more - and hands back the three highest-payoff fixes. No commitment, 48-hour turnaround.

Request the free audit