"Who is signed in" depends entirely on your provider
The SDK auto-captures sessions, page views, clicks, and errors the moment you call init() — but every event is attached to an anonymous device ID. To turn that anonymous timeline into a named user, you call Crossdeck.identify(userId) with the same stable ID your auth provider already gives you.
Why isn't this automatic? Because "who is signed in" isn't knowable from inside a generic browser SDK — the shape of "the current user" depends on which provider you wired. Firebase exposes auth.currentUser.uid; Supabase exposes session.user.id; Auth0 exposes user.sub; Clerk exposes user.id. So you point Crossdeck at the right one. It's one line per provider.
Identify on sign-in, reset on sign-out
Find your provider, drop this into your auth state listener, and you're done. The pattern is always the same: identify(userId) when a user is present, reset() when they sign out.
import { onAuthStateChanged } from "firebase/auth";
onAuthStateChanged(auth, (user) => {
if (user) Crossdeck.identify(user.uid);
else Crossdeck.reset();
});
supabase.auth.onAuthStateChange((_event, session) => {
if (session?.user) Crossdeck.identify(session.user.id);
else Crossdeck.reset();
});
// useAuth0() — user is undefined during the /callback handoff,
// so identify inside an effect once it resolves
const { user, isAuthenticated } = useAuth0();
useEffect(() => {
if (isAuthenticated && user) Crossdeck.identify(user.sub);
else if (!isAuthenticated) Crossdeck.reset();
}, [isAuthenticated, user]);
// useUser() — same async hydration; wait for isLoaded
const { isLoaded, isSignedIn, user } = useUser();
useEffect(() => {
if (!isLoaded) return;
if (isSignedIn && user) Crossdeck.identify(user.id);
else Crossdeck.reset();
}, [isLoaded, isSignedIn, user]);
// useSession() — status gates the call
const { data: session, status } = useSession();
useEffect(() => {
if (status === "authenticated") Crossdeck.identify(session.user.id);
else if (status === "unauthenticated") Crossdeck.reset();
}, [status, session]);
The one timing rule — and what the alias preserves
The rule that makes every one of these reliable: call identify() when the user resolves, not at init(). Auth providers hydrate asynchronously — Firebase's onAuthStateChanged can fire 50–800ms after init; Auth0's user is undefined during the redirect handoff; Clerk and NextAuth gate on a loaded/status flag. That's exactly why the call lives inside the listener or effect: it runs the moment the real user ID exists, never against a half-loaded null.
And here's what you get for free: the alias preserves the anonymous timeline. When you call identify(), Crossdeck doesn't start a fresh person — it aliases the anonymous device ID into your user, so everything that happened before sign-up stays attached to them. Your sign-up funnel survives, because the visitor who browsed pricing and the user who converted are one record, not two.
Signed-in users, by name — history intact
Sign in. Within a moment the People page shows that user by their real ID, with the pre-signup events still on their timeline. Sign out and the next session is anonymous again — clean, no leakage between accounts on a shared device.
user_847 on the People page — named, with the pricing-page visit from before they signed up still attached. One person, whole story.