# CDP Web SDK Single-file TypeScript tracker for browsers. No build step, no dependencies. ## Install Copy [`cdp.ts`](./cdp.ts) into your app. A common spot for Next.js: ``` your-app/ └── lib/ └── cdp.ts ← paste here ``` ## Init (Next.js App Router) `app/layout.tsx`: ```tsx 'use client'; import { useEffect } from 'react'; import { cdp } from '@/lib/cdp'; export default function RootLayout({ children }: { children: React.ReactNode }) { useEffect(() => { cdp.init({ writeKey: process.env.NEXT_PUBLIC_CDP_WRITE_KEY!, endpoint: process.env.NEXT_PUBLIC_CDP_ENDPOINT ?? 'http://localhost:3049', autoPage: true, // fire `page` on every SPA route change }); }, []); return {children}; } ``` `.env.local`: ``` NEXT_PUBLIC_CDP_WRITE_KEY=cdp_dev_writekey_1234567890 NEXT_PUBLIC_CDP_ENDPOINT=http://localhost:3049 ``` (The dev key above is the one seeded by `infra/migrations/000002_seed_dev.up.sql`.) ## Use ```tsx import { cdp } from '@/lib/cdp'; // On login cdp.identify(user.id, { email: user.email, plan: user.plan }); // On a meaningful action cdp.track('Checkout Completed', { revenue: 199, currency: 'USD' }); // Manual page call (skip if autoPage is on) cdp.page('Pricing'); // On logout cdp.reset(); ``` ## Vite / Create React App Identical — `cdp.init(...)` in your root component / `main.tsx`. ## What gets sent Every call POSTs to `${endpoint}/v1/` with this envelope: ```json { "type": "track", "messageId": "uuid-v4", "anonymousId": "uuid-v4 from localStorage", "userId": "from identify()", "sentAt": "2026-05-25T03:14:15Z", "context": { "library_name": "cdp-web", "user_agent": "..." }, "event": "Checkout Completed", "properties": { "revenue": 199, "currency": "USD" } } ``` Header: `Authorization: Basic base64(:)`. Payload is Segment-compatible: if you ever swap endpoints to Segment the same code works. ## Things to know - `anonymousId` is generated once and persisted in `localStorage` under `cdp_anon`. It survives across sessions. - `userId` is persisted in `cdp_uid` until you call `cdp.reset()`. - `fetch` uses `keepalive: true` so events fire even when the page is unloading. No `sendBeacon` because we need the `Authorization` header. - For SSR (Next.js Server Components, Remix loaders) skip the SDK — fire events from your API route or a server-side function instead. ## CORS The ingest service serves `Access-Control-Allow-Origin: *` so any origin works in dev. Lock this down for production (configure a reverse proxy or patch `internal/middleware/middleware.go`). ## Production checklist - [ ] Issue a per-workspace write key from the console (don't ship the dev key) - [ ] Restrict CORS to known origins - [ ] Front the ingest service with HTTPS (browser refuses mixed content) - [ ] Set `NEXT_PUBLIC_CDP_ENDPOINT` to the public URL