Authenticated users only
Accept events only from signed-in users, and reject everything else before it reaches the Hub.
The collector accepts events from anyone. Its origin check protects against CSRF, but it does not authenticate the caller. To accept only signed-in users, check the session in the collector route first, and hand the request to the collector only when there is a user.
Check the session before the collector
Next.js · app/api/eventrail/route.ts
import { cookies } from "next/headers";
import { createCollector } from "@eventrail/nextjs/server";
import { hub } from "@/lib/eventrail-hub";
import { getUserIdFromSession } from "@/lib/auth"; // your existing auth
const collector = createCollector(hub, {
allowedOrigins: ["https://yourapp.com"],
});
export async function POST(request: Request) {
const session = (await cookies()).get("session")?.value;
const userId = session ? await getUserIdFromSession(session) : null;
if (!userId) return new Response(null, { status: 401 });No signed-in user: rejected before the Hub sees the event.
return collector.POST(request);
}Everything after the check stays with the collector: the origin check, validation and the 202
answer.
The check only decides whether an event is accepted. To record which user sent it, add the user
in hubProperties, as in Server-resolved user IDs.
Signed-out visitors
Events the browser sends while nobody is signed in are rejected with a 401 and lost. Each one
also reaches the browser producer's onError as
Collector answered 401. Don't fire them at all: skip track() on pages that signed-out visitors
can reach, and render the ready-made trackers only on pages that require sign-in.
In Next.js, move TrackPageViews and TrackErrors out of your root layout and into the layout of
those pages. A route group such as (signed-in) gives them a shared layout without changing their
URLs:
Next.js · app/(signed-in)/layout.tsx
import { TrackErrors, TrackPageViews } from "@eventrail/nextjs/client";
import { track } from "@/lib/eventrail-client";
export default function SignedInLayout({ children }: { children: React.ReactNode }) {
return (
<>
{children}
<TrackPageViews track={track} />
<TrackErrors track={track} />
</>
);
}A view of /pricing, outside the group, then records nothing, and a view of /dashboard, inside
it, records a page_view.
Server events
A server producer hands events to the Hub directly, not
through the collector route, so this check doesn't apply to them. They come from your own code:
call track() only where a user is signed in. Events from webhooks and scheduled jobs, such as a
subscription_renewed from your payment provider, never have a signed-in user, so decide whether
you want to keep them.