Zendesk Charges $200/mo For This. I Built It for $6.

Zendesk shipped a genuinely useful feature this year — AI Agent Journey Report — that visualizes exactly where your bot loses users, where they escalate, and where they rage-quit. The analytics tier that unlocks it starts around $200/agent/month, and it only observes bots that live inside Zendesk. If your agent runs on Telegram, WhatsApp, your own site, or Slack, you get nothing. Here's the exact stack I built for $6/month that does the same job for any agent, anywhere.
The blind spot that costs you leads
If you can't see the full path a user takes through your AI agent, every prompt tweak is a guess. A customer messages you saying "your bot was useless." You open the transcript. You still don't know if that was one bad conversation or thirty of them, or which step of the funnel is leaking — the qualifying question, the pricing answer, or the human handoff.
Three things people usually try, and why they don't work:
- Dumping logs into a Google Sheet. Fine for grep, useless for flow analysis. You can't see that 45% of users dead-end at
fallback_no_matchwhen it's spread across 400 rows. - Scrolling n8n execution history. One run at a time. No aggregation. No pairs of consecutive steps.
- A few
console.loglines. That's not observability, that's a diary.
A journey map needs three things: a shared conversation ID across events, a step name for each meaningful decision point, and a visualization that shows flow (Sankey) rather than rows. That's it. Everything else is decoration.
The $6 stack, in three pieces
One Hetzner CX11-class VPS (around $5.50/month in the EU, ~$6 in the US region) runs the whole thing. Docker Compose, two containers, one config file.
- Piece 1: Your AI agent — wherever it already lives. n8n, a Python service, a Zapier flow, a LangGraph app. No migration required.
- Piece 2: An n8n webhook that receives a log event after every meaningful step and inserts one row into Postgres.
- Piece 3: Metabase pointed at the same Postgres, rendering a funnel chart and a Sankey diagram.
Here's the entire docker-compose.yml:
version: "3.8"
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: journeys
POSTGRES_USER: journeys
POSTGRES_PASSWORD: ${PG_PASSWORD}
volumes:
- pg_data:/var/lib/postgresql/data
restart: unless-stopped
metabase:
image: metabase/metabase:latest
ports:
- "3000:3000"
environment:
MB_DB_TYPE: postgres
MB_DB_DBNAME: journeys
MB_DB_HOST: postgres
MB_DB_USER: journeys
MB_DB_PASS: ${PG_PASSWORD}
depends_on:
- postgres
restart: unless-stopped
volumes:
pg_data:
docker compose up -d, wait about four minutes for Metabase to initialize its internal H2-to-Postgres migration, and the UI is live on port 3000. Put Caddy or Cloudflare Tunnel in front for HTTPS. Total ongoing cost: one VPS.
The schema — five columns, don't over-design it
The whole thing is one table. Resist the urge to add ten columns upfront. You will guess wrong about what you need.
CREATE TABLE conversation_events (
id BIGSERIAL PRIMARY KEY,
conversation_id TEXT NOT NULL,
step_name TEXT NOT NULL,
agent_response TEXT,
escalated BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_conv_id ON conversation_events (conversation_id);
CREATE INDEX idx_step ON conversation_events (step_name);
CREATE INDEX idx_conv_time ON conversation_events (conversation_id, created_at);
What each column earns its place doing:
conversation_id— a UUID or the chat ID your platform already gives you (Telegramchat_id, WhatsAppwa_id, session token from your web widget). This is the join key for everything.step_name— short, snake_case, stable strings:intent_detected,pricing_asked,human_escalated,booking_confirmed,fallback_no_match. Treat this like an enum you version manually.agent_response— the actual text the bot sent. Not for analytics — for eyeballing. When the funnel shows a leak, you want to read the exact reply that lost the user.escalated— boolean. True when the bot handed off or the user said "talk to a human."created_at— timestamp withNOW()default. Combined withconversation_id, this is how you reconstruct ordering for the Sankey.
You'll want sentiment_score, model_used, latency_ms, token_cost eventually. Add them when you have a real question they answer, not before. Adding a nullable column to Postgres is one migration; guessing wrong upfront gets you a schema you don't understand three months later.
The logger — one webhook, five fields
In your agent, after every meaningful step, fire one HTTP POST at an n8n webhook. That's the entire integration.
import httpx, uuid
WEBHOOK = "https://n8n.yourdomain.com/webhook/log-event"
async def log_step(conversation_id: str, step: str,
response: str = "", escalated: bool = False):
payload = {
"conversation_id": conversation_id,
"step_name": step,
"agent_response": response[:2000], # cap it
"escalated": escalated,
}
try:
async with httpx.AsyncClient(timeout=2.0) as c:
await c.post(WEBHOOK, json=payload)
except Exception:
pass # never let logging break the agent
Inside n8n: Webhook node → Postgres node (Insert). Map the five fields. No code. About 30 lines of node configuration.
The only decision that matters is what counts as a step. My rule: every time the bot makes a decision or the user makes a choice, that's an event. For a typical support bot that's usually six to nine event types:
session_startedintent_detectedclarification_requestedanswer_providedpricing_askedbooking_confirmedhuman_escalatedfallback_no_matchsession_ended
Nine events, one webhook call each. On my setup I log about 400 events/day across three bots — Postgres uses roughly 40 MB after six months. This is not a data volume problem.
The dashboard — one funnel, one Sankey
Metabase, SQL editor, two questions. That's the entire dashboard.
Question 1 — the funnel. Distinct conversations that reached each step:
SELECT
step_name,
COUNT(DISTINCT conversation_id) AS conversations
FROM conversation_events
WHERE created_at >= NOW() - INTERVAL '7 days'
GROUP BY step_name
ORDER BY conversations DESC;
Set the visualization to Funnel, order the steps manually (session_started at the top, booking_confirmed at the bottom). You now have a drop-off chart that tells you which single step is bleeding users.
Question 2 — the Sankey. Pairs of consecutive steps per conversation, using a self-join with LEAD():
WITH ordered AS (
SELECT
conversation_id,
step_name,
LEAD(step_name) OVER (
PARTITION BY conversation_id
ORDER BY created_at
) AS next_step
FROM conversation_events
WHERE created_at >= NOW() - INTERVAL '7 days'
)
SELECT
step_name AS source,
next_step AS target,
COUNT(*) AS value
FROM ordered
WHERE next_step IS NOT NULL
GROUP BY step_name, next_step
ORDER BY value DESC;
Metabase 0.50+ ships a Sankey visualization natively. Feed it source, target, value. You now see every path a user took, weighted by volume.
Put both questions on one dashboard, set the refresh to five minutes, and you're done. Ten minutes on Monday morning is enough to catch every meaningful regression. Last month this dashboard caught something I would have missed for weeks manually: on one bot, roughly 40% of users hitting pricing_asked dead-ended at fallback_no_match right after — because a prompt change I'd shipped stopped matching the follow-up "is there a monthly plan?" phrasing. Fifteen-minute fix. Would have cost me weeks of quiet lead-loss without the Sankey.
Zendesk vs. this stack — honest comparison
Zendesk's AI Agent Journey Report is a good product. It's polished, requires zero SQL, and comes with role-based access, audit trails, and a real support team. If you're already on Zendesk Suite Enterprise and the analytics add-on is priced into your contract, use it.
| Zendesk AI Journey Report | This stack | |
|---|---|---|
| Cost | ~$200/agent/month (analytics tier) | ~$6/month total |
| Setup time | Minutes (if already on Zendesk) | 2–3 hours first time |
| Bots covered | Only agents inside Zendesk | Any agent, any channel |
| Custom event schema | Fixed | Fully custom |
| SQL access to raw events | No | Yes |
| Maintenance | Zero | You own the VPS |
| RBAC, audit, SSO | Built in | You configure it |
Where Zendesk genuinely wins: zero maintenance, enterprise auth, no VPS to patch. Where this stack wins: cost, portability across channels, and you own the raw events forever. For a solo founder or a small agency running two or three bots across mixed channels, the $6 path is not just cheaper — it's the only one that actually observes all your agents.
Why bizflowai.io helps with this
Observability is one of the first things I wire up when I build support and lead-qualification agents for clients at bizflowai.io — the Postgres event table, the n8n logger node, and a Metabase dashboard land on day one, before any prompt tuning starts. That way when we iterate on the agent's behavior in week two, every change is measured against a real funnel instead of a client's gut feeling. Same $6 stack, deployed per client on their own VPS so they own their data.
Want more like this?
I publish practical AI automation, GenAI engineering, and faceless content workflows on YouTube every week.
Subscribe to bizflowai.io on YouTube — never miss a new tutorial.
Planning an AI automation project or need a second opinion on your architecture?
Connect with me on LinkedIn — Lazar Milicevic, GenAI Engineer & bizflowai.io Founder.
Visit bizflowai.io for our services, case studies, and AI consulting.
Frequently asked questions
What is an AI agent journey map and why does it matter?
An AI agent journey map is a visualization showing the full path users take through a conversational bot, built from structured events with a shared conversation ID, step name, and outcome. It matters because without it you can't see where users abandon — at qualifying, pricing, or human handoff — leaving you to rewrite prompts on gut feeling and silently losing leads.
How do I build AI agent observability without Zendesk?
Use a three-piece stack: your AI agent, an n8n workflow that receives log events via webhook and inserts them into a Postgres table called conversation_events, and a free Metabase instance pointed at that Postgres to render Sankey diagrams and drop-off funnels. The entire setup runs on a single six-dollar Hetzner VPS with Postgres and Metabase in Docker.
What schema should I use to log AI agent conversation events?
Create one Postgres table called conversation_events with five columns: conversation_id (UUID or chat ID to group a session), step_name (short string like intent_detected or human_escalated), agent_response (the bot's text), escalated (boolean for human handoff), and created_at (timestamp defaulting to now). Add indexes on conversation_id and step_name. Don't over-design — add columns like sentiment_score later.
When should I log an event from my AI agent?
Log an event every time the bot makes a decision or the user makes a choice. That includes intent classified, question answered, clarification requested, escalation triggered, booking completed, or dead end reached. If your bot has six decision points, you'll log six event types — enough to produce a real funnel showing where conversations drop off.
Why not just use Zendesk's AI Agent Journey Report?
Zendesk's AI Agent Journey Report is a solid enterprise feature, but the analytics tier that unlocks it starts around $200 per agent per month and requires the full support suite. It also only observes agents living inside Zendesk — so bots running on Telegram, WhatsApp, your own site, or as internal Slack agents get no visibility at all.