Skip to main content
Glama
README.md
# Pulse

**Open-source, revenue-first analytics that runs on your own Cloudflare.**

[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/everyai-com/pulse)

Pulse tells you which post, search, referrer or AI answer brought the people who paid you, and how
AI assistants use your MCP tools. It's a self-hosted, drop-in alternative to DataFast: one Worker,
one D1 database, one script tag. Most sites fit in Cloudflare's free plan.

- **Revenue attribution**: Stripe, Polar and Dodo webhooks. Every sale, renewal and refund is credited to the visit that brought the buyer, even weeks later.
- **Projects home**: every site you run on one screen, each with its own pulse line, live visitors, revenue and MCP calls.
- **MCP usage**: see which tools your MCP servers get called with, by which AI client, how fast, and whether they fail.
- **Ask your AI**: Pulse is itself an MCP server, so Claude, ChatGPT or Cursor can answer "where did last week's revenue come from?"
- **Yours**: your Cloudflare account, your data, MIT licensed. No cookies needed if you don't want them.

## Features

| | |
|---|---|
| Traffic | Visitors, sessions, pageviews, bounce, engaged time, scroll depth; channels, referrers, UTM campaigns, pages (entry, exit), countries, cities, devices, browsers, OS. Click any row to filter everything. ChatGPT, Claude, Perplexity and Gemini get their own **AI** channel. |
| Revenue | Revenue, sales, average order, share attributed, refunds, revenue per visitor, per source, per page and per country. One-click Stripe history import. |
| Readable Direct | No-referrer traffic split into typed/bookmarked, shared links (WhatsApp, iMessage, Slack…), checkout returns and named in-app browsers (Instagram, Facebook, LinkedIn, X, TikTok). |
| Lifetime value | Customers grouped by the source that first brought them, with renewals and repeat buyers. |
| Funnels | Pages (with `*` wildcards), goals and payment as steps; drop-off, median time between steps, revenue from finishers. Saved per project. |
| Goals | `pulse("signup")`, `data-pulse-goal`, scroll goals (`data-pulse-scroll`), conversion rate and revenue per goal. Outbound link clicks are automatic. |
| Realtime | Live map and list of who's on the site, visitors per minute, sales in the last hour, and the full journey of any visitor. |
| Alerts | Telegram: every sale with its source, traffic spikes (a source at 4× its usual hour), daily digest. |
| Sharing | A read-only public dashboard link per project. |
| Workflow | ⌘K command palette, custom date ranges, CSV export everywhere, light and dark themes, works on phones. |

### Coming from DataFast?

Swap the script tag and keep your code: `window.datafast("signup")`, `data-fast-goal`,
`data-fast-scroll`, and checkouts that send `datafast_visitor_id` in metadata all keep working.
Then point your payment webhooks at Pulse.

## Install: one click

[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/everyai-com/pulse)

1. Click the button and sign in to Cloudflare (a free account is fine).
2. Pick two passwords when asked: `ADMIN_TOKEN` (your dashboard login) and `PULSE_MCP_KEY` (for AI assistants).
3. Click **Deploy**. Cloudflare copies Pulse to your GitHub, creates the database and deploys it.
4. Open your new `pulse.<you>.workers.dev` URL, sign in with `ADMIN_TOKEN`, add a project and paste the tag.

To use your own domain, add it under the Worker's **Settings → Domains & Routes** in Cloudflare.
Updates: pull changes from this repo into your copy; Cloudflare redeploys and runs new migrations.

<details><summary>Prefer the terminal?</summary>

```bash
git clone https://github.com/everyai-com/pulse && cd pulse
npm install
npx wrangler login
npm run setup                                     # or: npm run setup -- --domain analytics.example.com
```

`setup` creates the database, applies migrations, deploys, and generates `ADMIN_TOKEN` and
`PULSE_MCP_KEY` for you (saved in `pulse-credentials.txt`, which is gitignored).
Upgrade later with `git pull && npm run deploy`.
</details>

## Track a site

```html
<script defer data-website-id="my-app" data-domain="example.com" src="https://YOUR-PULSE-URL/js/script.js"></script>
```

Single-page apps need nothing extra. Options:

| Attribute | |
|---|---|
| `data-cookieless` | No cookie and no storage. The server derives a visitor hash from a daily-rotating secret + IP + user agent, so visitors can't be linked across days (checkout attribution is off). |
| `data-allowed-hostnames="app.example.com"` | Carry one visitor across your own domains. |
| `data-exclude="/admin*,/preview/*"` | Don't track these paths. |
| `data-hash` | Track `#/routes`. |
| `data-disable-outbound`, `data-disable-payments` | Turn off outbound-click tracking or payment-link tagging. |
| `data-allow-localhost` | Track localhost while developing. |

In the browser: `pulse("goal", props)`, `pulse.identify({ user_id, email, name })`,
`pulse("payment", { email | payment_id })`, `pulse.visitorId()`, `pulse.ignoreMe()`.

## Connect payments

In Settings → Payments, copy the webhook URL for Stripe, Polar or Dodo, add it in the provider, and
paste the signing secret back. Then pass the visitor id into checkout:

```js
// Stripe Checkout (server), with visitorId from window.pulse.visitorId() in the browser
await stripe.checkout.sessions.create({
  ...,
  client_reference_id: visitorId,
  metadata: { pulse_visitor_id: visitorId },
  subscription_data: { metadata: { pulse_visitor_id: visitorId } },
});
// Polar or Dodo: metadata: { pulse_visitor_id: visitorId }
```

Stripe Payment Links, Polar links and Dodo `/buy/` links need nothing: the script tags them on click.
Checkouts that can't carry metadata: call `pulse("payment", { email })` on the thank-you page.

## MCP tool-call analytics

For an MCP server on the same Cloudflare account, add a service binding and log after each response.
The `PulseIngest` entrypoint is only reachable through bindings, so there's no secret to manage and
nobody can post fake events:

```jsonc
// wrangler.jsonc of your MCP server
"services": [{ "binding": "PULSE", "service": "pulse", "entrypoint": "PulseIngest" }]
```

```ts
ctx.waitUntil(env.PULSE.track("my-mcp", {
  name: "mcp_call", path: "/mcp",
  props: { tool: params.name, client: "claude", status: res.status, ms: Date.now() - t0 },
}));
```

Anywhere else: `POST /api/track` with `Authorization: Bearer <project API key>` and
`{ "name": "mcp_call", "props": { "tool": "...", "client": "..." } }`. Never send tool arguments.

## Let your AI read your analytics

Pulse serves a read-only MCP server at `/mcp`. Tools: `list_sites`, `site_report`, `breakdown`,
`timeseries`, `revenue_report`, `pages_report`, `realtime`, `lifetime_value`, `mcp_usage`,
`run_funnel`, `visitor_journey`.

```bash
claude mcp add --transport http pulse https://YOUR-PULSE-URL/mcp --header "Authorization: Bearer $PULSE_MCP_KEY"
```

Cursor, VS Code, Windsurf and Claude Desktop take the same URL and header. Clients that can't send
headers (Claude.ai and ChatGPT custom connectors) can use `https://YOUR-PULSE-URL/mcp/<PULSE_MCP_KEY>`;
treat that URL like a password.

## Alerts

```bash
npx wrangler secret put TELEGRAM_BOT_TOKEN   # from @BotFather
npx wrangler secret put TELEGRAM_CHAT_ID     # default chat; each project can override it
```

## How it works

```
browser ── /js/script.js ──▶ POST /api/e ─┐
Stripe · Polar · Dodo ── /webhooks/* ─────┤
your Workers ── PULSE service binding ────┼──▶ Worker ──▶ D1 (sites, visitors, sessions, events, payments)
                                          │      │
dashboard (/), share links, /mcp ◀────────┘      └── cron every 15 min: spikes, daily digest → Telegram
```

- Visits are classified at ingest (referrer, UTM, in-app user agent, landing page) and stored per session, so filters are cheap.
- A payment is credited to the visitor's last non-direct session in the 30 days before it (else their first). Refunds and renewals inherit the original sale's credit; webhooks that arrive out of order are back-filled.
- Bots are dropped by user agent and headless-browser checks. IPs are never stored.
- Money is stored in minor units with its currency and summed as-is (no currency conversion).

## Develop

```bash
cp .dev.vars.example .dev.vars    # local secrets
npm run dev                       # local D1 + dashboard on http://localhost:8787
npm run check                     # typecheck + script syntax
```

Schema changes: add the next `migrations/NNNN_name.sql`. `npm run deploy` applies pending migrations before deploying.

## Security

Report vulnerabilities privately to support@magicteams.ai. The dashboard escapes every
visitor-supplied string; admin APIs need `ADMIN_TOKEN`; webhook signatures are verified; public
share links expose only overview, pages and realtime for one project.

## License

MIT