React SPA (Vite)
Track referral clicks in a Vite React SPA and send leads and sales to your backend.
Prerequisites
- A React SPA built with Vite (or any bundler)
- A separate backend server (Node.js, Express, Next.js API routes, etc.)
- A Refport account and API key (Settings → API Keys)
Architecture overview
In a React SPA, the browser SDK captures the click ID client-side. Lead and sale tracking must always happen server-side — never call track.lead() or track.sale() directly from the browser, as that would expose your API key.
Browser Your Backend
────── ────────────
1. User arrives via referral link
refp_id stored in cookie
2. User signs up
→ POST /api/auth/register
{ clickId, email, ... } → refport.track.lead()
3. User purchases
→ POST /api/checkout
{ clickId, ... } → refport.track.sale()Install the React SDK and add <RefportTracker /> to your app root. It reads refp_id from the URL and persists it in a cookie for 90 days.
npm install @refport/reactimport { RefportTracker } from '@refport/react';
import { Router } from './Router';
export default function App() {
return (
<>
<RefportTracker />
<Router />
</>
);
}Use getClickId() from @refport/react to read the current value of the refp_id cookie and include it in your API request.
import { getClickId } from '@refport/react';
export async function registerUser(email: string, password: string) {
const clickId = getClickId();
const res = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password, clickId }),
});
return res.json();
}On your backend, receive the clickId from the request body and call track.lead().
import { Router } from 'express';
import { Refport } from 'refport';
const refport = new Refport({ apiKey: process.env.REFPORT_API_KEY! });
const router = Router();
router.post('/register', async (req, res) => {
const { email, password, clickId } = req.body;
const user = await createUser({ email, password });
try {
if (clickId) {
await refport.track.lead({
clickId,
eventName: 'Sign Up',
customerExternalId: user.id,
customerEmail: user.email,
customerName: user.name,
});
}
} catch {
/* tracking failure must never block sign-up */
}
res.json(user);
});
export default router;Do the same when a purchase is completed — send the click ID from the browser or read it from the Stripe webhook client_reference_id.
import { getClickId } from '@refport/react';
export async function startCheckout(priceId: string) {
const clickId = getClickId();
const res = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId, clickId }),
});
const { url } = await res.json();
window.location.href = url;
}import { Router } from 'express';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const router = Router();
router.post('/', async (req, res) => {
const { priceId, clickId } = req.body;
const session = await stripe.checkout.sessions.create({
line_items: [{ price: priceId, quantity: 1 }],
mode: 'subscription',
success_url: `${req.headers.origin}/success`,
cancel_url: `${req.headers.origin}/pricing`,
metadata: {
refportExternalCustomerId: req.user.id,
refportClickId: clickId ?? undefined,
},
});
res.json({ url: session.url });
});
export default router;Refport's webhook handler reads these metadata fields and attributes the sale automatically. See the Stripe integration guide for the full attribution fallback chain.
Server implementation guides
The backend shown above uses Express. For a full server-side walkthrough, see:
Express.js
Middleware patterns and route handlers for Express
Next.js App Router
Server Actions and Route Handlers for Next.js