DocsCustom events
Guide

Setup custom events

Track anything that matters: feature usage, workflow milestones, revenue events. From the browser or from your server.

Client-side events

The analytics script exposes a low-level window.grademypage.track(eventType, options?) for any event that isn't a conversion. Pass any eventType you want (use snake_case), plus an optional eventName and eventData object.

// Fire any event you want — pick a stable eventType string. window.grademypage?.track('video_played'); // With a name and custom properties. window.grademypage?.track('feature_used', { eventName: 'pdf_export', eventData: { plan: 'pro', pages: 12 }, });
No pre-registration
Events appear in your dashboard the first time they fire. No schema, no migration, no deploy.

track vs trackConversion

trackConversion is a one-line shorthand for track('conversion', ...). Use it when the event is a goal you want to chart on the Conversions panel; use track for everything else.

// Low-level — any eventType you choose. window.grademypage?.track('onboarding_step', { eventName: 'completed', eventData: { step: 3 }, }); // Shorthand — always fires eventType: 'conversion'. window.grademypage?.trackConversion('signup'); // …is exactly the same as: window.grademypage?.track('conversion', { eventName: 'signup' });

Events fired automatically

EventPayload
pageviewpathname, fullUrl, referrer, UTMs
scroll{ depth: 25 | 50 | 75 | 100 }
engagement{ engagementSeconds: number }
experiment_impression{ experimentId, variantId, variantName }

Server-side events

For events that must always be recorded (payment confirmations, subscription changes, abuse signals), call the server-events endpoint from your backend. It uses a secret key instead of the public project key.

POST https://www.grademypage.com/api/analytics/server-events Content-Type: application/json Authorization: Bearer <YOUR_SECRET_KEY> { "eventType": "conversion", "eventName": "subscription_started", "eventData": { "plan": "pro_annual", "revenue": 249.00 }, "idempotencyKey": "sub_1Oxyz_created" }
Keep your secret key on the server
Never ship the secret key to a browser. Generate and rotate it from Dashboard → Settings → API keys.

Stripe webhook example

app/api/webhooks/stripe/route.tstypescript
// app/api/webhooks/stripe/route.ts export async function POST(req: Request) { const event = await verifyStripeWebhook(req); if (event.type === "checkout.session.completed") { await fetch("https://www.grademypage.com/api/analytics/server-events", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${process.env.GMP_SECRET_KEY}`, }, body: JSON.stringify({ eventType: "conversion", eventName: "purchase", eventData: { plan: event.data.object.metadata.plan, revenue: event.data.object.amount_total / 100, }, idempotencyKey: event.id, }), }); } return Response.json({ received: true }); }

Idempotency

Webhooks retry. Pass a stable idempotencyKey (like the Stripe event ID) and Grademypage will dedupe, so at-least-once delivery becomes exactly-once in your reports.

Attributing to experiments

If the server-side conversion should count toward an A/B experiment variant, your backend needs to know which variant the visitor was bucketed into. The browser knows; you need to forward that context.

  1. On the page where the conversion starts (signup form, checkout button, etc.), read the visitor's current assignment:
    const [assignment] = window.grademypage?.getExperimentAssignments?.() ?? []; // { experimentId, variantId, variantName } | undefined
  2. Forward experimentId and variantId through whatever carries your conversion to the backend (a hidden form field, your signup API payload, Stripe metadata, Clerk unsafeMetadata, etc.).
  3. Pass them to /server-events as top-level fields:
    { "eventType": "conversion", "eventName": "signup", "idempotencyKey": "clerk_evt_123", "visitor": { "id": "user_abc" }, "experimentId": "exp_hero_v2", "variantId": "var_b" }
Backwards compatible
Existing integrations that don't pass experimentId/variantId keep working. The fields are optional; only attribution to experiments requires them.