# Ekx Posthog

> Product analytics with PostHog — client and server capture, identifying users, feature flags, and what not to send. Use when adding event tracking, wiring a feature flag, or reviewing whether analytics is capturing something it should not.

- Skill: `ekinoxis-evm/ekx-posthog` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ekinoxis-evm/ekx-posthog`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ekinoxis-evm/ekx-posthog/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: Ekinoxis-evm (https://skillmd.com/u/ekinoxis-evm)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/ekinoxis-evm/ekx-posthog

---


# PostHog

Our product analytics (`posthog-js` + `posthog-node`).

---

## Environment

```bash
VITE_POSTHOG_KEY=       # project API key — public by design
VITE_POSTHOG_HOST=      # https://us.i.posthog.com
```

The project key is meant to be public — it can only write events. It is not a secret,
but it *is* a spam vector, so keep the ingestion endpoint behind a reverse proxy if
event volume ever becomes a cost problem.

---

## Client

```ts
posthog.init(import.meta.env.VITE_POSTHOG_KEY, {
  api_host: import.meta.env.VITE_POSTHOG_HOST,
  person_profiles: "identified_only",     // don't bill for anonymous profiles
  capture_pageview: true,
});
```

## Server

```ts
import { PostHog } from "posthog-node";
const posthog = new PostHog(key, { host });

posthog.capture({ distinctId: userId, event: "loan_requested", properties: { amount_usdc: 5000 } });
await posthog.shutdown();     // serverless: REQUIRED, or events are lost
```

**`await posthog.shutdown()` before a serverless function returns.** `posthog-node`
batches, and the function freezes with the batch unsent otherwise. This is the single
most common reason server events "don't appear".

---

## Identifying

```ts
posthog.identify(userId, { email: user.email });   // on login
posthog.reset();                                    // on logout — or the next user inherits the session
```

Use a **stable internal id** as `distinctId`. Not a wallet address (users have
several, and they rotate), not an email (it changes).

---

## What not to capture

In a portfolio that touches lending, real-estate purchases and payments, this matters
more than the tracking itself:

- ❌ Private keys, seed phrases, session tokens, API keys — obviously
- ❌ Full wallet addresses as properties. They are a permanent pseudonymous identifier that links a person to their entire onchain history. Hash them, or capture only that a wallet exists.
- ❌ Identity documents, KYC fields, credit-score inputs
- ❌ Exact loan amounts tied to an identifiable individual — bucket them
- ✅ Event names, funnel steps, bucketed values, feature-flag exposure

Enable session recording only with masking on, and never on a KYC or payment form.

---

## Feature flags

```ts
if (await posthog.isFeatureEnabled("new-auction-ui", userId)) { … }
```

Server-side flag checks are a network call — cache per request, and always have a
default for when PostHog is unreachable. A flag service outage must not take the
product down.

---

## Gotchas

1. **Missing `shutdown()`** in serverless → lost events.
2. **No `reset()` on logout** → merged user identities, permanently.
3. **Wallet addresses as properties** → de-anonymisation.
4. **`person_profiles: "always"`** bills for every anonymous visitor.
5. **Flag check with no fallback** couples your uptime to theirs.

