# Pulse Tower
> Privacy-first analytics dashboard combining pageviews, revenue, and GitHub activity in one place.
## About
Pulse Tower is a developer analytics dashboard that tracks website pageviews (no cookies, no IP storage), Stripe revenue, and GitHub code activity in a single real-time view. It features AI-powered chat for querying analytics data in plain English, smart filtering, and public shareable dashboards.
## Documentation
- [Install the Script](https://pulsetower.ai/docs/embed): Add the analytics embed script to your site
- [AI Chat](https://pulsetower.ai/docs/ai-chat): Ask questions about your analytics in plain English
- [Event Tracking](https://pulsetower.ai/docs/events): Track custom events, clicks, and form submissions
- [Connect Stripe](https://pulsetower.ai/docs/stripe): See MRR and revenue data
- [Connect GitHub](https://pulsetower.ai/docs/github): Track commits and PR activity
- [Invite Your Team](https://pulsetower.ai/docs/team): Share dashboards with teammates
- [Privacy & Data](https://pulsetower.ai/docs/privacy): What we collect and what we don't
---
## Install the Script
Add one script tag to your site. Under 2KB. No cookies. Takes two minutes.
### Step 1: Sign Up and Create a Site
Create an account at https://pulsetower.ai/signup, then add your site in the dashboard. You'll get a unique site token.
### Step 2: Add the Script
Replace YOUR_SITE_TOKEN with the token from your dashboard.
```html
```
### Step 3: See Your Data
Pageviews show up in your dashboard in real-time. No setup delay, no waiting for "yesterday's data."
### FAQ
**Does this add cookies to my site?**
No. Pulse Tower uses zero cookies, zero IP storage, and zero fingerprinting. No cookie banner needed.
**How big is the script?**
Under 2KB. It uses sendBeacon so it doesn't block page load.
**Can I use this with a single-page app?**
Yes. The script patches pushState/replaceState for SPA support. Route changes are tracked automatically. For hash-based routers, add `data-hash="true"`.
### Script Configuration
All behavior is controlled via `data-*` attributes on the script tag:
| Attribute | Purpose | Default |
| --- | --- | --- |
| data-site | Site token (required) | — |
| data-endpoint | Custom API endpoint for proxying | Auto |
| data-domain | Root domain for subdomain tracking | Auto |
| data-allowed-domains | Comma-separated domains for cross-domain tracking | None |
| data-allow-localhost | Track on localhost for dev testing | false |
| data-hash | Track URL hash changes as pageviews | false |
| data-exclude-paths | Comma-separated paths to ignore (supports * wildcard) | None |
| data-no-spa | Disable SPA route change detection | false |
### Excluding Traffic
Three methods to exclude unwanted visits:
1. **Path exclusion**: `data-exclude-paths="/admin/*,/internal/*"`
2. **Self-exclusion**: Run `localStorage.pulse_ignore = "true"` in browser console
3. **IP blocking**: Add CIDR rules in site Settings tab
### Cross-Domain Tracking
Track visitors across separate root domains by adding `data-allowed-domains`. The script passes a visitor ID via URL params automatically. Install the same script with the same token on both domains.
### Scroll & Click Goals
Track element visibility and clicks without JavaScript:
- `data-pulse-scroll="goal_name"` — fires event when element scrolls into view
- `data-pulse-scroll-threshold="0.8"` — visibility ratio (default 0.5)
- `data-pulse-scroll-delay="2000"` — ms to wait before firing
- `data-pulse-goal="click_name"` — fires event on click
- `data-pulse-goal-*` — custom properties on click goals
---
## AI Chat
Ask questions about your analytics in plain English. Get answers powered by your real data.
### How It Works
1. **Open the Chat Panel** — Click the sparkle icon in the top bar from any page. Press Esc to close it.
2. **Choose Your Scope** — Use the site selector to pick a specific site or "All Sites" to query across your workspace.
3. **Ask Anything** — Type a question in plain English. The AI fetches real data from your analytics, referrers, devices, UTM campaigns, events, and goals.
### Example Questions
- What are my top pages this week?
- Where is my traffic coming from?
- Which site gets the most visitors?
- How do pageviews compare to last month?
- What devices do my visitors use?
- What events are being tracked?
- Show me UTM campaign performance
- What are my goal conversion rates?
### Chat History
Conversations are saved automatically. Each chat thread is private to your account within your workspace. Click + to start a fresh conversation, or the chat icon to see previous threads.
### FAQ
**Does the AI make up numbers?**
No. Every answer is backed by real tool calls to your analytics data. If there's no data, it says so.
**Can other team members see my chats?**
No. Chat threads are private to your account.
**What data can the AI access?**
Pageviews, daily stats, top pages, referrers, UTM campaigns, device/browser breakdowns, custom events, and goal conversions.
---
## Event Tracking
Track custom events and user interactions. No extra setup needed for common events.
### Auto-Tracked Events
These events are captured automatically by the embed script:
- **outbound_click** — Fires when a visitor clicks a link to an external domain. Properties: url
- **file_download** — Fires when a visitor clicks a downloadable file (.pdf, .zip, .csv, .xlsx, .doc, .dmg, .exe). Properties: url, fileType
- **email_click** — Fires when a visitor clicks a mailto: link. Email address is NOT captured for privacy.
### Custom Events
Track any interaction with one line of JavaScript. The `pulse.track()` function is available globally after the embed script loads.
```javascript
// Track a simple event
pulse.track("signup_click");
// Track with properties
pulse.track("plan_selected", {
plan: "pro",
interval: "annual"
});
```
#### Button click example
```html
```
#### React / Next.js example
```typescript
declare global {
interface Window {
pulse?: { track: (name: string, props?: Record) => void };
}
}
function SignupButton() {
return (
);
}
```
#### Form submission example
```javascript
document.querySelector("form").addEventListener("submit", () => {
pulse.track("form_submit", { formId: "contact" });
});
```
### API Reference
```
pulse.track(name: string, properties?: object)
```
- **name** — Required. The event name. Use snake_case. Keep it short and descriptive.
- **properties** — Optional. A flat object of key-value pairs. Values should be strings or numbers. Avoid nested objects.
### Viewing Events
Go to your site dashboard and click the Events tab to see every event name with its total count for the selected date range.
### FAQ
**Do events count against pageviews?**
No. Events and pageviews are tracked separately.
**Is there a limit on event names?**
No hard limit. Keep the total number of distinct event names manageable (under 50 is ideal).
**Are event properties stored securely?**
Yes. Don't include sensitive user data like emails or passwords in properties.
**Do auto-tracked events work with SPAs?**
Yes. Outbound clicks and file downloads work regardless of routing.
---
## Connect Stripe
See MRR, daily revenue, and recent charges in your dashboard.
### Step 1: Create a Restricted Key
Click the pre-filled link in Settings to open Stripe with the exact permissions ready. Or create manually in Stripe Dashboard > Developers > API Keys > Restricted Keys:
| Resource | Permission |
| --- | --- |
| Charges | Read |
| Balance | Read |
| Everything else | None |
Never use your secret key. A restricted key limits what Pulse Tower can access.
### Step 2: Paste It in Site Settings
Open your site in the dashboard, click the Settings tab, and paste your restricted key. Each site has its own Stripe key — you can connect different Stripe accounts to different sites.
### Step 3: See Revenue Data
Revenue data appears on your site dashboard immediately. You'll see daily revenue, MRR, and recent charges.
### Security FAQ
**How is my key stored?**
Your Stripe key is encrypted with AES-256-GCM before it touches the database. It never appears in client-side code or logs.
**Can Pulse Tower charge my customers?**
No. With a read-only restricted key, Pulse Tower can only read charge and balance data.
**Can I revoke the key?**
Yes. Delete the restricted key in Stripe at any time and Pulse Tower will stop showing revenue data.
---
## Connect GitHub
Track commits, PRs, and repo activity alongside your traffic and revenue.
### Step 1: Authorize GitHub
Go to Dashboard > Settings > Integrations and click Connect GitHub. You'll be redirected to GitHub to authorize Pulse Tower.
### Step 2: Grant Read Access
Pulse Tower requests read-only access:
| Scope | What We Read |
| --- | --- |
| Repos | Commit count, recent activity |
| Pull Requests | Open/closed count |
| Profile | Username (for display) |
### Step 3: See Activity Data
Once connected, your site dashboard shows a GitHub activity chart with weekly commit history. Commit markers also appear as dashed lines on the main traffic chart so you can correlate deploys with traffic changes.
### FAQ
**Can I disconnect GitHub later?**
Yes. Revoke access from GitHub Settings > Applications at any time.
**Does this work with private repos?**
Yes. Pulse Tower only reads aggregate activity data — never code contents.
**How is my GitHub token stored?**
Your OAuth token is encrypted with AES-256-GCM before storage, just like Stripe keys.
---
## Invite Your Team
Share dashboards with your team. No per-seat pricing.
### How Workspaces Work
Every account gets a personal workspace automatically. Invite teammates to share the same dashboard, sites, and integrations. Everyone sees the same real-time data.
### Option 1: Invite by Email
Go to Dashboard > Settings > Team Members. Enter a teammate's email and click Invite.
### Option 2: Share a Link
Click "Copy Invite Link" to get a shareable URL. Links expire after 7 days.
### What Teammates See
All workspace members see: all sites and analytics, revenue data (if Stripe connected), GitHub activity (if connected), and team member list.
### FAQ
**Is there a limit on team members?**
No. Invite as many people as you want. No per-seat charges.
**Can I remove a team member?**
Coming soon. Contact us if you need to remove someone.
---
## Privacy & Data
What we collect, what we don't, and how your data is handled.
### What We Collect
When someone visits a site with the Pulse Tower embed script:
| Data | Example |
| --- | --- |
| Page path | /blog/hello-world |
| Referrer | google.com |
| Device type | mobile, desktop, tablet |
| Browser | chrome, safari |
| Screen width bucket | sm, md, lg |
| Country | US, GB (inferred from timezone, not IP) |
That's it. All data is aggregate and anonymous. Visitors can opt out by setting `localStorage.pulse_ignore = "true"` in their browser console.
### What We Don't Collect
- No cookies of any kind
- No IP addresses (never stored, hashed, or logged)
- No fingerprinting (no canvas, WebGL, or fingerprinting techniques)
- No personal data (no user IDs, session IDs, or tracking pixels)
- No cross-site tracking (each site is isolated)
### Do I Need a Cookie Banner?
No. Pulse Tower does not use cookies or collect personal data, so no consent banner is required under GDPR, CCPA, or PECR.
### Integration Data
When you connect Stripe or GitHub:
- API keys and OAuth tokens are encrypted with AES-256-GCM before storage
- Keys never appear in client-side code, URLs, or logs
- Revenue and activity data is fetched on-demand and displayed only to workspace members
- You can revoke access at any time
---
## Key Features
- One script tag, under 2KB, no cookies, no IP storage
- AI chat powered by real analytics queries (not hallucinated data)
- Smart filtering by referrer, device, browser, UTM source
- Public shareable dashboard URLs
- GitHub integration for commit and PR tracking
- Stripe integration for MRR and revenue tracking
- Custom event tracking with pulse.track() API
- Auto-tracked events: outbound clicks, file downloads, mailto clicks
- SPA support (Next.js, React Router, etc.)
- Real-time updates via Convex
- Multiple workspaces and team collaboration
- AES-256-GCM encrypted integration keys
## Technical Stack
- Next.js (App Router)
- Convex (real-time backend)
- TypeScript
- Tailwind CSS