# Track signups, conversions and custom events

Signups aren't detected automatically — here's the one line that records them.

Pageviews are automatic. Anything meaning-specific to your product — a signup, a plan upgrade, a demo booked — is not: VisitTrack can't know which button or which redirect means 'this person signed up'. You tell it, with one call.

## From your own code (recommended)

Call this exactly where your code already knows the account was created — after the API responds, not when the button is clicked. That way a failed signup never counts as one.

```
// After your signup succeeds:
window.visitrack("signup");

// With extra detail, if useful:
window.visitrack("signup", { plan: "pro", provider: "google" });
```

## Without touching your logic

For a one-click flow — an OAuth button that redirects away, say — put the attribute straight on the element. It fires on click and on keyboard activation, with no JavaScript from you.

```
<button data-vt-goal="signup">Sign up with Google</button>
```

This counts the click, not a confirmed account — someone who abandons the Google consent screen still counts. If that matters, report the signup from your server instead (below).

## React, Next.js and TypeScript

Use optional chaining so nothing throws when an ad blocker stops the script, and declare the global once for TypeScript:

```
// global.d.ts
declare global {
  interface Window {
    visitrack?: ((name: string, props?: Record<string, unknown>) => void) & {
      identify?: (userId: string, traits?: Record<string, unknown>) => void;
      visitorId?: () => string;
    };
  }
}
export {};

// in your signup form
async function onSubmit(values: FormValues) {
  const res = await fetch("/api/signup", { method: "POST", body: JSON.stringify(values) });
  if (!res.ok) return;
  const user = await res.json();
  window.visitrack?.("signup", { method: "email" });
  window.visitrack?.identify?.(user.id, { plan: user.plan });
}
```

## OAuth and accounts created on the server

When the account is created in a server callback (Google/GitHub OAuth, a magic link, an invite), send the event from the server with a secret API key from Settings → API / MCP. To attribute it to the visitor's earlier visits, carry their visitor id — window.visitrack.visitorId() in the browser — through the flow, for example in a cookie set right before the redirect. Report each signup from one place only, so nothing is counted twice.

```
// before redirecting to the OAuth provider (browser)
document.cookie = "vt_vid=" + window.visitrack?.visitorId?.() + "; path=/; max-age=600; samesite=lax";

// in your OAuth callback, only when a NEW user was created (server)
await fetch("https://visitrack.app/api/track", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.VISITRACK_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ type: "event", visitorId: cookies.vt_vid, name: "signup", props: { method: "google" } }),
});
```

> **Let your AI assistant do it** — The Signups tab has a “Copy setup instructions for AI” button: a prompt with all of the above that you paste into Cursor, Claude Code or Copilot with your app open. It finds your signup handlers and adds the event for you.

## Checking it works

The script ignores localhost on purpose, so test on your deployed site: sign up once with a fresh account and open the Signups tab — it appears within a few seconds.

## Scroll-depth events

Fires once when the element is at least half visible — useful for 'did anyone actually reach the pricing table'.

```
<section data-vt-scroll="pricing_viewed"> … </section>
```

## Where these show up

- An event named 'signup' populates the Signups tab, including source and time-to-conversion.
- Any custom event can be turned into a goal in Settings, which then appears on the Overview and Goals tabs.
- Revenue is separate — it comes from your payment provider's webhook (Stripe, Polar, Lemon Squeezy, Paddle or Razorpay — see [Revenue attribution](https://visitrack.app/docs/revenue)), not from a browser event, so it can't be faked by a page visitor.

> **Name events once and keep the name** — The event name is the identifier. Renaming 'signup' to 'sign_up' later starts a new, empty series rather than renaming the old one.
