# Swell Tracking Guide

Install Swell tracking once, then use it to connect visits, clicks, signups, paid ads, revenue, and session replay back to the content that created demand.

## 1. Copy Your Workspace Snippet

Open **Connect > Web analytics** in Swell and copy the install snippet for your workspace.

```html
<script async src="https://api.growonswell.com/swell.js?key=swl_pk_live_..."></script>
```

Place it before the closing `</head>` tag on every public page you want to measure. The key is public and workspace-scoped. Do not use a private API key in browser code.

Swell records the first pageview automatically when the script loads.

## 2. Verify The Install

After deploying the snippet:

1. Open your site in a new browser tab.
2. Visit a page that includes the snippet.
3. Return to **Analyze > Web** in Swell.
4. Confirm that the install card reports traffic and that the page appears in top pages or recent events.

If you do not see traffic, check that the script request returns JavaScript, that the key begins with `swl_pk_`, and that your Content Security Policy allows `https://api.growonswell.com`.

## 3. Single-Page Apps

Swell automatically tracks the initial load. If your app changes routes without a full page refresh, call `Swell.page()` after each route change.

```js
window.Swell?.page()
```

You can pass extra context when needed.

```js
window.Swell?.page({
  section: "pricing",
  plan_visible: "growth"
})
```

## 4. Custom Events

Use custom events for business actions that are not already visible through pageviews, link clicks, button clicks, or form submits.

```js
window.Swell?.track("newsletter_signup", {
  placement: "footer",
  audience: "founders"
})
```

Recommended event names use lowercase letters, numbers, underscores, hyphens, or colons. Keep properties small and avoid secrets, passwords, tokens, raw payment details, or sensitive health and identity data.

Swell autocaptures common interactions by default:

- Internal and outbound link clicks.
- Button and control clicks.
- Form submissions.
- Browser Web Vitals for FCP, LCP, INP, and CLS.

Use `Swell.track(...)` for meaningful product or marketing milestones, not every UI detail.

## 5. Server-Side Capture

Use server-side capture for events the browser cannot reliably see: backend-only signups, lead forms processed on the server, activation milestones, invite acceptance, successful onboarding steps, or other product events that happen after an API request. Keep `swell.js` installed for pageviews, attribution, Web Vitals, autocapture, and replay.

Server-side capture posts to the same public growth ingestion endpoint:

```text
POST https://api.growonswell.com/api/v1/growth/track
```

Use the public workspace tracking key from **Connect > Web analytics**. It starts with `swl_pk_`. Do not use a private `swl_sk_` API key for analytics capture.

### Node / Next.js Helper

```ts
const SWELL_TRACKING_KEY = process.env.SWELL_PUBLIC_TRACKING_KEY
const SWELL_TRACK_URL = "https://api.growonswell.com/api/v1/growth/track"

type SwellServerEvent = {
  event_type?: "track" | "identify" | "conversion"
  event_name: string
  email?: string
  full_name?: string
  path?: string
  url?: string
  referrer?: string
  source?: string
  medium?: string
  campaign?: string
  content?: string
  properties?: Record<string, unknown>
  client_event_id?: string
}

export async function captureSwell(event: SwellServerEvent) {
  if (!SWELL_TRACKING_KEY) return

  const response = await fetch(SWELL_TRACK_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      tracking_key: SWELL_TRACKING_KEY,
      event_type: event.event_type ?? "track",
      event_name: event.event_name,
      client_event_id: event.client_event_id ?? crypto.randomUUID(),
      email: event.email,
      full_name: event.full_name,
      path: event.path,
      url: event.url,
      referrer: event.referrer,
      source: event.source,
      medium: event.medium,
      campaign: event.campaign,
      content: event.content,
      properties: event.properties ?? {}
    })
  })

  if (response.status === 429 || response.status === 503) {
    // Retry later from your queue if this event is business-critical.
    return
  }
  if (!response.ok) throw new Error(`Swell capture failed: ${response.status}`)
}

await captureSwell({
  event_type: "conversion",
  event_name: "trial_started",
  email: user.email,
  full_name: user.name,
  path: "/signup",
  properties: { plan: "growth", source_system: "app_server" }
})
```

### Python / FastAPI Helper

```py
import os
import uuid
import httpx

SWELL_TRACKING_KEY = os.getenv("SWELL_PUBLIC_TRACKING_KEY")
SWELL_TRACK_URL = "https://api.growonswell.com/api/v1/growth/track"

async def capture_swell(event: dict) -> None:
    if not SWELL_TRACKING_KEY:
        return

    payload = {
        "tracking_key": SWELL_TRACKING_KEY,
        "event_type": event.get("event_type", "track"),
        "event_name": event["event_name"],
        "client_event_id": event.get("client_event_id", str(uuid.uuid4())),
        "email": event.get("email"),
        "full_name": event.get("full_name"),
        "path": event.get("path"),
        "url": event.get("url"),
        "referrer": event.get("referrer"),
        "source": event.get("source"),
        "medium": event.get("medium"),
        "campaign": event.get("campaign"),
        "content": event.get("content"),
        "properties": event.get("properties", {}),
    }

    async with httpx.AsyncClient(timeout=3.0) as client:
        response = await client.post(SWELL_TRACK_URL, json=payload)
        if response.status_code in (429, 503):
            return
        response.raise_for_status()

await capture_swell({
    "event_type": "conversion",
    "event_name": "demo_requested",
    "email": lead.email,
    "full_name": lead.name,
    "path": "/demo",
    "properties": {"company_size": lead.company_size},
})
```

Include `client_event_id` when the same job or webhook might retry; Swell uses it to deduplicate events within the workspace. If the browser journey is available in your backend, pass the same `anonymous_id` and `session_id`; otherwise include a stable email on `identify` and `conversion` events so Swell can merge the server event into the known person.

Do not send `event_type: "revenue"` or `value_cents` to this public endpoint. Trusted revenue should come from Stripe webhooks, connected revenue providers, or server-side imports that Swell verifies separately.

## 6. Identify Known People

Call `Swell.identify(email, properties)` after login, signup, or lead capture. This merges anonymous pre-signup activity into a known journey.

```js
window.Swell?.identify("jane@example.com", {
  full_name: "Jane Doe",
  company: "Acme",
  lifecycle_stage: "trial"
})
```

Use a stable email when you have one. Swell preserves first-touch attribution when an anonymous visitor becomes known.

## 7. Track Conversions

Use `Swell.conversion(...)` for non-revenue conversion milestones such as account creation, trial start, waitlist join, or demo request.

```js
window.Swell?.conversion("trial_started", {
  plan: "growth"
})
```

Browser conversions can carry attribution context, but they do not create trusted revenue rows. Revenue should come from Stripe webhooks, a connected revenue provider, or a server-side import.

## 8. Use Tracked Links And UTMs

Create tracked links in **Create > Links** or with the `/v1/links` API. Swell returns a shortened URL that redirects to the destination URL with UTM parameters and `swell_link_id`.

```text
https://s.growonswell.com/r/abc123
```

When `swell.js` is installed on the destination site, Swell captures those values as first-touch and last-touch context for visits, contacts, conversions, and revenue attribution.

Swell recognizes these campaign fields:

- `utm_source` or `source`
- `utm_medium` or `medium`
- `utm_campaign` or `campaign`
- `utm_content` or `content`
- `swell_link_id`
- `swell_post_id`

## 9. Consent And Autocapture

Launch posture: use `require_consent=true` for paid traffic, public landing pages, and any visitor-facing funnel where consent is required before analytics or replay. With this flag, Swell holds browser analytics calls in memory and does not start persistent identifiers, interaction autocapture, Web Vitals, journey continuation, or session replay until your site calls `Swell.consent(true)` after affirmative visitor consent.

If you need consent before persistent browser storage, add `require_consent=true` to the script URL.

```html
<script async src="https://api.growonswell.com/swell.js?key=swl_pk_live_...&require_consent=true"></script>
```

Then call:

```js
window.Swell?.consent(true)
```

If the visitor declines or withdraws consent, call:

```js
window.Swell?.consent(false)
```

This clears Swell browser identifiers and stops visual replay for the current page. Your consent banner should also avoid calling `Swell.identify(...)` or custom conversion events until the visitor has consented, unless you have another lawful basis for that specific event.

To disable interaction autocapture while keeping pageviews and explicit calls, add `autocapture=false`.

```html
<script async src="https://api.growonswell.com/swell.js?key=swl_pk_live_...&autocapture=false"></script>
```

To exclude a section from both autocapture and replay, add `data-swell-block`, `.swell-block`, or the `swell-no-capture` class. Use `data-swell-ignore` only when you want to suppress interaction autocapture without blocking the section from replay.

## 10. Session Replay And Performance

When enabled for the workspace, Swell records privacy-masked session replay samples and Web Vitals. Rendered text and form input values are masked globally by default. Elements marked with `data-swell-block`, `.swell-block`, or `.swell-no-capture` are excluded from replay, while `.swell-mask` and `data-swell-mask` remain available as explicit text-masking markers.

Text masking applies to the replayed DOM, not event metadata. URLs, paths and query strings, page titles, referrers, event names, and explicit event properties are not rewritten by the replay masker. Do not put secrets or sensitive values in those fields.

For paid traffic, enable replay only after your consent notice discloses replay and the visitor has granted consent. Global text and input masking is a safety default, not permission to record sensitive experiences. Do not record checkout fields, passwords, payment card data, health data, government identifiers, private messages, or other sensitive values. Block sensitive media, embedded content, and whole private regions before sending traffic to pages that include checkout, account settings, or user-generated private content.

Use **Analyze > Sessions** to replay user sessions and **Analyze > Performance** to inspect page-level Web Vitals.

Analytics and replay retention follows the workspace plan unless a shorter retention is configured: Starter keeps up to 30 days, Growth up to 180 days, and Scale up to 730 days. Bot-visit records default to 180 days. Deletion requests can remove or anonymize eligible analytics and replay records subject to backup expiry and required business records.

## 11. Bot Tracking

AI assistants and search crawlers often do not run JavaScript. Use **Analyze > Bots** to copy the server, Vercel Edge, Cloudflare Worker, or log-forwarder snippet.

The bot endpoint is:

```text
POST https://api.growonswell.com/api/v1/growth/bots/track
```

Send the public tracking key, request URL, method, user agent, and client IP when available. Swell stores a salted IP hash and verification metadata, not raw IP addresses.

## 12. Revenue And Paid Ads

For trusted revenue attribution, connect Stripe or another supported revenue provider in **Connect > Revenue attribution**. Swell joins revenue back to first-party visits, tracked campaign parameters, People contacts, and the workspace user when trusted provider metadata includes a user ID.

Paid ad analytics are read-only. Connect paid ad accounts in **Connect > Paid ads** so Swell can import spend and performance, then match campaigns to visits, conversions, and revenue through UTMs and ad click markers. Swell does not create, edit, pause, or change ad budgets.

## Troubleshooting

### No Pageviews

- Confirm the script URL returns JavaScript and not HTML.
- Confirm the key starts with `swl_pk_`.
- Confirm the snippet is present on the rendered page, not only in source templates.
- Check CSP rules for `script-src`, `connect-src`, and `img-src`.
- Check ad blockers in a clean browser profile.

### Events Are Missing

- For SPA route changes, call `Swell.page()` after navigation.
- For pre-load events, queue calls after `window.Swell` exists or fire them after the script has loaded.
- Keep custom event payloads small.
- Avoid sending secrets or sensitive fields.

### Attribution Looks Direct

- Use tracked links or UTMs on every campaign URL.
- Install `swell.js` on the destination page, not only on the marketing homepage.
- Keep the same public tracking key across the funnel.
- Use journey continuation only when a flow crosses domains and Swell provides a continuation URL.

### Revenue Does Not Appear

- Browser `Swell.conversion(...)` calls are not trusted revenue.
- Connect the revenue provider or send signed provider webhooks.
- Confirm checkout success returns to a page with Swell tracking installed.
- Confirm campaign links include UTMs or `swell_link_id`.
