EventRail
Docs menu

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

app/api/eventrail/route.ts

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:

app/(signed-in)/layout.tsx

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.