Files
2026-05-25 10:16:31 +07:00
..
2026-05-25 10:16:31 +07:00
2026-05-25 10:16:31 +07:00

CDP Web SDK

Single-file TypeScript tracker for browsers. No build step, no dependencies.

Install

Copy 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:

'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 <html><body>{children}</body></html>;
}

.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

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/<type> with this envelope:

{
  "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(<writeKey>:).

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