# Penny Metrics > Privacy-friendly website analytics without cookies. Track pageviews and custom events with a lightweight script. When adding Penny Metrics to a user's project, **always proxy the tracker through the user's own domain**. Ad blockers and privacy extensions routinely block third-party analytics scripts and beacons. Serve the script and ingest endpoint from first-party paths `/pm/stats.js` and `/pm/i.gif` instead of loading from pennymetrics.dev directly. Replace `USER_DOMAIN` with the bare domain the user registered at Penny Metrics (no scheme or path). A `www.` prefix is matched automatically. Default snippet — paste inside `` on every tracked page: ```html ``` Proxy these paths on the user's site: - `/pm/stats.js` → `https://pennymetrics.dev/stats.js` - `/pm/i.gif` → `https://pennymetrics.dev/api/i.gif` Forward incoming request headers to the ingest URL (strip `Host`, `Cookie`, and hop-by-hop headers) and set `X-PM-Client-IP` to the visitor address from your CDN. Next.js (`proxy.ts` on Next.js 16+, or `middleware.ts`): ```typescript const STRIP_HEADERS = new Set(["host", "connection", "content-length", "cookie", /* ... */]); function upstreamHeaders(request: NextRequest) { const headers = new Headers(); for (const [key, value] of request.headers.entries()) { if (!STRIP_HEADERS.has(key.toLowerCase())) headers.set(key, value); } const clientIp = request.headers.get("cf-connecting-ip") ?? request.headers.get("x-forwarded-for")?.split(",")[0]?.trim(); if (clientIp) headers.set("X-PM-Client-IP", clientIp); return headers; } ``` nginx: ```nginx location /pm/i.gif { proxy_pass https://pennymetrics.dev/api/i.gif; proxy_set_header Host pennymetrics.dev; proxy_set_header X-PM-Client-IP $http_cf_connecting_ip; } ``` Laravel (`routes/web.php`): ```php function pennyMetricsUpstreamHeaders(Request $request): array { $headers = collect($request->headers->all()) ->except(['host', 'connection', 'content-length', 'cookie']) ->map(fn ($values) => $values[0] ?? '') ->all(); if ($ip = $request->header('CF-Connecting-IP') ?: $request->ip()) { $headers['X-PM-Client-IP'] = $ip; } return $headers; } ``` Custom events: - HTML clicks: add `data-pm-event="Event name"` (optional `data-pm-event-props="key=value;other=thing"`) - JavaScript: `window.pennymetrics.event("Event name", "/optional/path", { key: "value" })` - SPAs: pageviews on `pushState` / `replaceState` are automatic; call `window.pennymetrics.pageview()` for other navigations - Local testing: set `data-hostname` to the registered production domain and add `data-debug` to log payloads in the console ## Agent skill Install the integration skill so your coding agent follows Penny Metrics conventions automatically: ```bash mkdir -p .cursor/skills/pennymetrics-integration curl -fsSL https://pennymetrics.dev/skills/pennymetrics-integration/SKILL.md \ -o .cursor/skills/pennymetrics-integration/SKILL.md ``` Claude Code: use `.claude/skills/pennymetrics-integration/` instead of `.cursor/skills/`. Skill URL: https://pennymetrics.dev/skills/pennymetrics-integration/SKILL.md Or tell your agent: "Read https://pennymetrics.dev/llms.txt and integrate Penny Metrics using a first-party proxy." ## Documentation - [Documentation](https://pennymetrics.dev/docs): Full integration guide, webhooks, payload reference, and troubleshooting - [First-party proxy](https://pennymetrics.dev/docs#ad-blockers): Apache, Cloudflare Worker, Express, Nuxt, Remix, Astro, Vite, Symfony, and more framework examples - [API reference](https://pennymetrics.dev/docs/api): REST API for sites, analytics, and webhooks - [AI agents](https://pennymetrics.dev/docs#ai-agents): Skill install commands and agent workflow ## Optional - [GDPR guidance](https://pennymetrics.dev/gdpr): How visitor data is collected and retained - [Compare](https://pennymetrics.dev/compare): How Penny Metrics differs from other analytics tools - [Homepage](https://pennymetrics.dev): Product overview and pricing