Skip to Content
Developer DocsConversion trackingLead trackingClerk

Clerk

💡

Conversion tracking is available on the Business plan and above .

In conversion tracking, a lead event is an action that signals interest in your product:

  • signing up for an account;
  • booking a demo;
  • joining a mailing list.

The lead event establishes the customer’s identity and the link they came from: every later action (a purchase, a plan upgrade) is attributed to that same link.

In this guide, we will be focusing on tracking new user sign-ups for a SaaS application that uses Clerk for user authentication.

Prerequisites

First, enable conversion tracking for your links — without it a click is never linked to the lead and sale events that follow.

💡

You can skip this step for partner program links: RevRoute enables conversion tracking on them automatically.

For the whole workspace. Open Settings → Analytics and turn on the Conversion tracking switch in the page header — it controls the workspace-level setting. Every link created from that point on has conversion tracking enabled.

For a single link. Open the link builder and turn on the Conversion tracking switch — inside the builder it is also bound to the C keyboard shortcut.

Via the API. Pass trackConversion: true when creating or updating a link (it defaults to false):

curl -X POST "https://api.revroute.ru/links" \ -H "Authorization: Bearer rr_xxxxxx" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com", "trackConversion": true }'

Then install the RevRoute client-side script on your site — it records the click and stores its ID, which is what lead events are attached to.

The RevRoute client-side script is served from our domain — there are no npm packages to install. It picks up the rr_id query parameter, stores it in a first-party rr_id cookie on your domain (90 days) and uses it to attribute future conversions to the original link.

Add the tag to the <head> section of every page on your site:

<script defer src="https://app.revroute.ru/analytics/script.js"></script>

In Next.js (App Router), use next/script in the root layout:

import Script from "next/script"; export default function RootLayout({ children }) { return ( <html lang="en"> <body>{children}</body> <Script defer src="https://app.revroute.ru/analytics/script.js" /> </html> ); }
📝

You can verify the installation in two ways:

  1. Open the browser console and type _revRouteAnalytics — if the script is installed, the object shows up in the console.
  2. Add the ?rr_id=test query parameter to your site URL and check that the rr_id cookie is set in your browser.

If neither works, make sure that:

  • the script tag sits in the <head> section of the page;
  • any CDN cache in front of your site has been purged.

The server-side client

The examples below use the TypeScript SDK from the dub npm package. Always pass the RevRoute API serverURL to the client — without it the SDK will not talk to our API.

npm install dub
import { Dub } from "dub"; export const revroute = new Dub({ token: process.env.REVROUTE_API_KEY, // secret rr_* key from your workspace settings serverURL: "https://api.revroute.ru", // required: the RevRoute API address });
⚠️

Keep the secret key (rr_*) in environment variables and never expose it to the browser. Create one under API keys .

💡

The SDK is optional: /track/lead and /track/sale are plain HTTPS endpoints you can call from any language.

Configure Clerk

Here’s a quick summary of the steps:

Add environment variables

Add the following environment variables to your app:

# get it here: https://dashboard.clerk.com/apps/new NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_publishable_key CLERK_SECRET_KEY=your_secret_key # get it here: https://app.revroute.ru/settings/tokens REVROUTE_API_KEY=your_api_key

Add a custom claim to your Clerk session token

Add the following JSON as a custom claim  to your Clerk session token:

{ "metadata": "{{user.public_metadata}}" }

Wire the client script to Clerk's `useUser` hook

Wrap the RevRoute client script in a component that calls a trackLead server action. No npm package needed — the script is served from our domain.

"use client"; import { trackLead } from "@/actions/track-lead"; import { useUser } from "@clerk/nextjs"; import { useEffect } from "react"; export function RevRouteAnalytics() { const { user } = useUser(); useEffect(() => { if (!user || user.publicMetadata.revrouteClickId) return; // if the user is loaded but hasn't been persisted to RevRoute yet, track the lead event trackLead({ id: user.id, name: user.fullName!, email: user.primaryEmailAddress?.emailAddress, avatar: user.imageUrl, }).then(async (res) => { if (res.ok) await user.reload(); else console.error(res.error); }); // you can also use an API route instead of a server action /* fetch("/api/track-lead", { method: "POST", body: JSON.stringify({ id: user.id, name: user.fullName, email: user.primaryEmailAddress?.emailAddress, avatar: user.imageUrl, }), }).then(res => { if (res.ok) await user.reload(); else console.error(res.statusText); }); */ }, [user]); return ( <script defer src="https://app.revroute.ru/analytics/script.js" data-api-host="https://api.revroute.ru" /> ); }

Then, add the DubAnalytics component to your app’s root layout component:

import { RevRouteAnalytics } from "@/components/analytics-overview"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html> <body> <RevRouteAnalytics /> {children} </body> </html> ); }

Implement the `trackLead` server action

On the server side, implement the trackLead server action. Alternatively, you can also create an API route instead:

// This is a server action "use server"; import { revroute } from "@/lib/revroute"; import { clerkClient } from "@clerk/nextjs/server"; import { cookies } from "next/headers"; export async function trackLead({ id, name, email, avatar, }: { id: string; name?: string | null; email?: string | null; avatar?: string | null; }) { try { const cookieStore = await cookies(); const clickId = cookieStore.get("rr_id")?.value; if (clickId) { // Send lead event to RevRoute await revroute.track.lead({ clickId, eventName: "Sign Up", customerExternalId: id, customerName: name, customerEmail: email, customerAvatar: avatar, }); // Delete the rr_id cookie cookieStore.set("rr_id", "", { expires: new Date(0), }); } const clerk = await clerkClient(); await clerk.users.updateUser(id, { publicMetadata: { revrouteClickId: clickId || "n/a", }, }); return { ok: true }; } catch (error) { console.error("Error in trackLead:", error); return { ok: false, error: (error as Error).message }; } }

Request fields for POST /track/lead:

FieldRequiredDescription
clickIdYesID of the click the lead is attributed to. Read it from the rr_id cookie. For a deferred lead an empty string is allowed — the click is then taken from the customer found by customerExternalId.
eventNameYesName of the event (e.g. Sign up). It also ties the lead to a later sale through leadEventName in /track/sale.
customerExternalIdYesThe customer’s ID in your system. All future events for this customer are attributed through it.
discountCodeNoThe partner’s discount code, for customers who arrive without clicking a link. If both clickId and discountCode are sent, clickId wins.
customerNameNoCustomer name. If omitted, a random one is generated.
customerEmailNoCustomer email.
customerAvatarNoCustomer avatar URL.
modeNoasync (default) — don’t block the request; wait — block until the event is recorded; deferred — defer lead creation to a later request.
eventQuantityNoNumeric value of the event (e.g. seats provisioned in a trial): the lead is counted that many times.
metadataNoArbitrary event data, up to 10,000 characters when stringified.

Where to see conversions

Once everything is set up, tracked conversions show up in your workspace analytics in three views:

  • Time series — clicks, leads and sales over time: Analytics .
  • Funnel — conversion and drop-off at every step (clicks → leads → sales): Analytics in funnel mode .
  • Events stream — every conversion event across all links in the workspace, in real time: Events .
Last updated on