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/
DashboardMarketplace 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.tsGET /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.