Skip to main content

PostHog analytics for agents

This guide shows how to track, quantify and analyse user behaviour across every agent in the marketplace. The design has two halves:

  • Each agent captures events into PostHog, tagged with its own agent slug.
  • The marketplace admin has one Behavior dashboard that queries PostHog and shows the Purchased → Activated → Used funnel for all agents at once, with a per-agent breakdown.

So an agent author only wires up capture; the moment events start flowing they appear in the central dashboard — no per-agent UI to build.

The reference capture implementation is Convrox (agent-poc); the central dashboard lives in the marketplace service + marketplace frontend.

Two different PostHog uses

The marketplace service also uses PostHog for request-level audit logging (api_request events — see Observability). This guide is about product analytics: lifecycle funnels and engagement. Same PostHog project, different events.

Architecture at a glance​

ConcernCredentialWhere it runsFiles
Capture (server)Project API key phc_…Each agent backendagent-poc/backend/posthog.js
Capture (browser)Project API key phc_…Each agent frontendagent-poc/frontend/src/auth.jsx
Query (read)Personal API key phx_…Marketplace service, admin-onlymarketplace-fleapoai-service/src/analytics/
Dashboard—Marketplace adminfleapo-marketplace/src/pages/admin/AdminBehavior.tsx

The read side is admin-gated (PlatformAdminGuard) and lives only in the marketplace — individual agents never query PostHog.

1. The lifecycle event taxonomy​

Every agent emits the same three events, keyed to the marketplace user id, so one set of dashboards works for the whole platform:

Funnel stepEventWhen
Purchasedsubscription_activesubscription resolves as active/trialing
Activateddashboard_openedfirst authenticated dashboard load
Usedagent_conversationfirst real product use (e.g. a widget conversation)

Every event must carry an agent property = the agent's marketplace slug. That property is what the central dashboard breaks down by.

2. Capture — server side (per agent)​

agent-poc/backend/posthog.js wraps posthog-node and stamps the agent slug onto every event centrally, so individual track() calls don't have to:

const agentSlug = process.env.MARKETPLACE_AGENT_SLUG || "unknown";

export function track(distinctId, event, properties = {}) {
if (!posthog) return;
posthog.capture({
distinctId: distinctId || "anonymous",
event,
properties: { agent: agentSlug, ...properties },
});
}

Emit the lifecycle events at the right moments (Convrox, server.js):

// /api/me/tier — first authed call per dashboard load
track(userId, "dashboard_opened", { tier, status });
if (tierName && ["active", "trialing"].includes(status)) {
track(userId, "subscription_active", { tier, status });
}

// /api/chat — first real widget message, attributed to the KB owner (customer)
track(kb.ownerId, "agent_conversation", { domain, channel: "widget" });
Attribute "Used" to the customer, not the visitor

The person chatting with a deployed widget is an anonymous end-visitor. Keep the funnel about customers by attributing the "Used" event to the agent's owner (the marketplace subscriber), not the visitor's session.

3. Capture — browser side (per agent)​

agent-poc/frontend/src/auth.jsx initialises posthog-js once and identifies the user after sign-in (autocapture handles pageviews/clicks):

posthog.init(POSTHOG_KEY, { api_host: POSTHOG_HOST, person_profiles: "identified_only" });
posthog.identify(user.id, { email, name, marketplace_tier, marketplace_status });
// on sign-out: posthog.reset();

Use the marketplace user id as the distinct id on both sides so browser and server events stitch into one person.

Agent env vars​

POSTHOG_API_KEY=phc_xxx                 # backend capture
POSTHOG_HOST=https://us.i.posthog.com
MARKETPLACE_AGENT_SLUG=your-agent-slug # tags every event (already set for OAuth)
VITE_POSTHOG_KEY=phc_xxx # frontend capture
VITE_POSTHOG_HOST=https://us.i.posthog.com

4. The central read side (marketplace service)​

marketplace-fleapoai-service/src/analytics/ adds an admin module that queries PostHog's HogQL query API with a personal API key:

POST {POSTHOG_QUERY_HOST}/api/projects/{POSTHOG_PROJECT_ID}/query
Authorization: Bearer {POSTHOG_PERSONAL_API_KEY}
{ "query": { "kind": "HogQLQuery", "query": "<SQL>" } }
  • behavior-analytics.service.ts — runs the queries (axios, per repo convention).
  • admin-analytics.controller.ts — GET /admin/analytics/behavior?days=&agent=, guarded by PlatformAdminGuard.
  • Config getters live in app-config.service.ts (posthogPersonalApiKey, posthogProjectId, posthogQueryHost, posthogQueryEnabled) — never read process.env directly.

The funnel is a strict subset (each step requires all prior steps) so it's always monotonic, even when subscription_active and dashboard_opened land in the same request. The per-agent breakdown groups by the agent property:

SELECT agent,
countIf(has_purchase) AS purchased,
countIf(has_purchase AND has_activate) AS activated,
countIf(has_purchase AND has_activate AND has_used) AS used
FROM (
SELECT person_id, properties.agent AS agent,
max(event = 'subscription_active') AS has_purchase,
max(event = 'dashboard_opened') AS has_activate,
max(event = 'agent_conversation') AS has_used
FROM events
WHERE timestamp >= now() - INTERVAL 30 DAY
AND event IN ('subscription_active','dashboard_opened','agent_conversation')
GROUP BY person_id, properties.agent
)
GROUP BY agent ORDER BY purchased DESC

When the personal key / project id are unset the endpoint returns { enabled: false } so the dashboard shows a setup hint instead of erroring.

Marketplace service env vars​

POSTHOG_PERSONAL_API_KEY=phx_xxx   # Settings → Personal API keys, scoped to read Query/Insights
POSTHOG_PROJECT_ID=12345 # Settings → Project (numeric id)
POSTHOG_QUERY_HOST=https://us.posthog.com # app host (no `i.`); defaults to POSTHOG_HOST

5. The Behavior dashboard (marketplace frontend)​

fleapo-marketplace/src/pages/admin/AdminBehavior.tsx renders the funnel, daily trends, headline stats, and the per-agent breakdown table with recharts. It's routed at /admin/behavior (under the RequirePlatformAdmin admin shell in App.tsx) and linked from the admin sidebar nav in AdminLayout.tsx. Data comes from the hand-rolled useBehaviorAnalytics hook (src/hooks/), which calls the endpoint via customFetch. A range toggle (7/30/90d) and an agent filter drive the query params.

Adding analytics to a new agent — checklist​

Because the dashboard is central and breaks down by the agent property, a new agent only needs the capture side:

  1. Add the agent env vars (step 3): POSTHOG_API_KEY, POSTHOG_HOST, MARKETPLACE_AGENT_SLUG, VITE_POSTHOG_KEY, VITE_POSTHOG_HOST.
  2. Copy posthog.js (with the agent slug tagging) into the agent backend and init posthog-js + identify() in the frontend auth gate.
  3. Emit subscription_active, dashboard_opened, agent_conversation at your equivalents of subscribe / sign-in / first product use, keyed to the marketplace user id.
  4. That's it — the agent's slug shows up automatically in the marketplace Behavior dashboard's per-agent breakdown.

Keep the event names and the agent property identical across agents — the shared taxonomy is what makes one dashboard cover the whole marketplace.