Appwrite
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 Appwrite 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:
- Open the browser console and type
_revRouteAnalytics— if the script is installed, the object shows up in the console. - Add the
?rr_id=testquery parameter to your site URL and check that therr_idcookie 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 dubimport { 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 Appwrite
Next, configure Appwrite to track lead conversion events during the sign up process.
Create an Appwrite Cloud project
Head to Appwrite Cloud and create a new project.

Create a new API key with the sessions.write scope enabled and save the API key for later use. You can also copy your project ID and endpoint from the project’s Settings page.

Then, in your Next.js app, install the Appwrite Node.js SDK.
npm i node-appwriteAdd environment variables
Add the following environment variables to your app.
NEXT_PUBLIC_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
NEXT_PUBLIC_APPWRITE_PROJECT=<APPWRITE_PROJECT_ID>
NEXT_APPWRITE_KEY=<APPWRITE_API_KEY>
NEXT_REVROUTE_API_KEY=<REVROUTE_API_KEY>Integrate RevRoute Analytics
Add the RevRoute client script to your app’s root layout — no npm package needed, the script is served from our domain.
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Appwrite RevRoute Leads Example',
description: 'Appwrite RevRoute Leads Tracking example app with Next.js'
};
export default function RootLayout({
children
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>{children}</body>
<script
defer
src="https://app.revroute.ru/analytics/script.js"
data-api-host="https://api.revroute.ru"
/>
</html>
);
}Prepare the Appwrite client and auth library
Create the Appwrite Session and Admin client (necessary for SSR apps, as explained in the Appwrite docs ). Additionally, create a function to verify user login.
'use server';
import { Client, Account } from 'node-appwrite';
import { cookies } from 'next/headers';
export async function createSessionClient() {
const client = new Client()
.setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT as string)
.setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT as string);
const session = (await cookies()).get('my-custom-session');
if (!session || !session.value) {
throw new Error('No session');
}
client.setSession(session.value);
return {
get account() {
return new Account(client);
}
};
}
export async function createAdminClient() {
const client = new Client()
.setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT as string)
.setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT as string)
.setKey(process.env.NEXT_APPWRITE_KEY as string);
return {
get account() {
return new Account(client);
}
};
}Set up RevRoute SDK
Create the RevRoute client and send leads to RevRoute using the revroute.track.lead() function.
import type { Models } from 'node-appwrite';
import { RevRoute } from 'dub';
const revroute = new RevRoute({
token: process.env.NEXT_REVROUTE_API_KEY
});
export function addRevrouteLead(user: Models.User<Models.Preferences>, rr_id: string) {
revroute.track.lead({
clickId: rr_id,
eventName: 'Sign Up',
customerExternalId: user.$id,
customerName: user.name,
customerEmail: user.email
});
}Send leads to RevRoute on user signup
In the /auth page, use the Appwrite Admin client to allow users to sign up. Post sign up, check if the rr_id cookie is present, send a lead event to RevRoute if found, and delete the rr_id cookie.
import { ID } from 'node-appwrite';
import { createAdminClient, getLoggedInUser } from '@/lib/server/appwrite';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
import { addRevrouteLead } from '@/lib/server/revroute';
async function signUpWithEmail(formData: any) {
'use server';
// Get sign up info from form
const email = formData.get('email');
const password = formData.get('password');
const name = formData.get('name');
// Create account and session using Appwrite
const { account } = await createAdminClient();
const user = await account.create(ID.unique(), email, password, name);
const session = await account.createEmailPasswordSession(email, password);
(await cookies()).set('my-custom-session', session.secret, {
path: '/',
httpOnly: true,
sameSite: 'strict',
secure: true
});
// Check if RevRoute ID is present in cookies and track lead if found
const rr_id = (await cookies()).get('rr_id')?.value;
if (rr_id) {
addRevrouteLead(user, rr_id);
(await cookies()).delete('rr_id');
}
// Redirect to success page
redirect('/auth/success');
}
export default async function SignUpPage() {
// Verify active user session and redirect to success page if found
const user = await getLoggedInUser();
if (user) redirect('/auth/success');
return (
<>
<form action={signUpWithEmail}>
<input id="email" name="email" placeholder="Email" type="email" required />
<input id="password" name="password" placeholder="Password" minLength={8} type="password" required />
<input id="name" name="name" placeholder="Name" type="text" required />
<button type="submit">Sign up</button>
</form>
</>
);
}Request fields for POST /track/lead:
| Field | Required | Description |
|---|---|---|
clickId | Yes | ID 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. |
eventName | Yes | Name of the event (e.g. Sign up). It also ties the lead to a later sale through leadEventName in /track/sale. |
customerExternalId | Yes | The customer’s ID in your system. All future events for this customer are attributed through it. |
discountCode | No | The partner’s discount code, for customers who arrive without clicking a link. If both clickId and discountCode are sent, clickId wins. |
customerName | No | Customer name. If omitted, a random one is generated. |
customerEmail | No | Customer email. |
customerAvatar | No | Customer avatar URL. |
mode | No | async (default) — don’t block the request; wait — block until the event is recorded; deferred — defer lead creation to a later request. |
eventQuantity | No | Numeric value of the event (e.g. seats provisioned in a trial): the lead is counted that many times. |
metadata | No | Arbitrary 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 .