React
Track conversions in a React app.
Set up with the wizard
npx @specify-sh/wizard --advertiserThe wizard runs through the coding agent you already use (Claude Code, Codex etc.). It inspects your codebase, proposes which events to capture and implements them as a reviewable diff.
Prefer to do it by hand? The manual steps below are the same setup.
Manual setup
Runnable example
react-vite-typescript is this guide as a working Vite app.
1. Install the SDK
npm i @specify-sh/advertiser-sdk2. Create the client
You will need an advertiser key. See Advertiser keys.
Create the client in its own module, outside any component, so the whole app shares one client.
import Specify from '@specify-sh/advertiser-sdk';
export const specify = new Specify({
advertiserKey: import.meta.env.VITE_SPECIFY_ADVERTISER_KEY
});3. Connect consent
Wire your consent manager to the SDK high in the component tree. Pass the answer it already has, then every change. Nothing is sent until the first call.
import { useEffect } from 'react';
import { specify } from '../lib/specify';
// Adapt this to your consent manager
export function ConsentBridge() {
useEffect(() => {
specify.setCookieConsent(cmp.getConsent().advertising);
return cmp.onConsentChange((consent) => {
specify.setCookieConsent(consent.advertising);
});
}, []);
return null;
}4. Identify the visitor
Identify the signed-in user and any connected wallet. The effects run again on every page load, which is what the SDK needs.
import { useEffect } from 'react';
import { useAccount } from 'wagmi';
import { useUser } from '../auth';
import { specify } from '../lib/specify';
export function IdentityBridge() {
const user = useUser();
const { address } = useAccount();
useEffect(() => {
if (user) specify.identify(user.id);
}, [user]);
useEffect(() => {
if (address) specify.identify(address);
}, [address]);
return null;
}Use your own user ID, not an email address, and give each person their own. Call specify.reset() in your sign-out handler.
5. Capture conversions
Capture an event once the action has succeeded.
import { specify } from '../lib/specify';
async function onSubmit(form: SignupForm) {
await createAccount(form);
specify.capture('signup');
}If your server confirms the conversion, capture it there instead.
6. Check it works
Use a development key and open your browser's network panel. After the consent call, requests to spfsrv.com/v1/identify and spfsrv.com/v1/events should return 200. Development keys never record events.