# Events and goals

Track what matters on your site, and see who does it: people, people sent by AI assistants, or agents. Every event joins the visit it belongs to and carries that visit's label, so "sign-ups by agents" never inflates "sign-ups by people".

## Sending events

**From the page** (browser snippet):

```html
<script>
  // Optional: queue calls made before the snippet loads.
  window.punchline = window.punchline || { q: [] };
  window.punchline.track = window.punchline.track || function () { window.punchline.q.push(["track", ...arguments]); };
</script>
<button onclick="punchline.track('book_demo', { plan: 'team' })">Book a demo</button>
```

The snippet also records two events automatically, with GA4's names: `click` for outbound links (`outbound: true`, `link_domain`) and `file_download` (`file_extension`, `link_domain`). Never query strings, link text or anything typed.

**From your servers** (`POST /v1/events`, a key with the `events:write` scope):

```bash
curl -s https://app.punchlineanalytics.com/v1/events \
  -H "Authorization: Bearer $PUNCHLINE_WRITE_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-8431" \
  -d '{
    "site_id": "<your site id>",
    "events": [
      { "name": "purchase", "value": 249, "props": { "plan": "team" },
        "visitor": { "ip": "203.0.113.7", "user_agent": "Mozilla/5.0 …" } }
    ]
  }'
```

Include `visitor` (the visitor's IP and user agent, as your server saw them) to tie the event to their visit; Punchline uses them in memory only, as it does for page views. Without it, the event counts as *not tied to a visit*. Add `company_domain` (the domain of a work email someone gave you) to connect that visit, and agents working in the same browser that day, to a company: see [Accounts](https://www.punchlineanalytics.com/docs/accounts). Up to 1,000 events per request, timestamps within the last 36 hours, and `Idempotency-Key` makes retries safe.

**Rules for every event:** names use letters, digits and `_` and start with a letter (GA4-compatible, max 40); up to 10 properties, each a string (max 100 characters), number or boolean. Email addresses are removed from properties and paths.

### GA4-compatible events

Punchline takes GA4 events as they are, so a goal can count the same conversion GA4 does:

- **Name:** use GA4's name, such as `purchase`, `generate_lead`, `sign_up` or `begin_checkout` (letters, digits and `_`, up to 40 characters, starting with a letter).
- **Value and currency:** GA4's `value` is the third argument and `currency` goes in the properties: `punchline.track('purchase', { currency: 'USD' }, 249)`.
- **Other GA4 parameters** (items, transaction IDs, user properties) aren't needed and shouldn't be sent: they can carry personal data.

With Google Tag Manager, the [Punchline template](https://www.punchlineanalytics.com/docs/gtm) does this without code. **Switch from GA4** in the dashboard lists your GA4 key events and creates the matching goals.

## Triggers: events without code

In **Site settings → Event triggers**, create events from what visitors do:

| Trigger | Works with | Example |
|---|---|---|
| Page view | Every install | `/pricing` (everything under it) or `=/pricing` (just that page) |
| URL match | Every install | `/blog/*/comments` |
| Click | Browser snippet | CSS selector `#book-demo` |
| Form submit | Browser snippet | CSS selector `form.newsletter` (only that it was submitted; never the fields) |

Changing a trigger gives it a new version, and each event records the version that produced it, so a count never silently changes meaning. Pause a trigger to stop it without losing its history.

## Goals and conversions

A goal is an event (`sign_up`) or a page (`/thank-you`). A **conversion** is a session that reaches it, credited to how that session arrived: its channel, campaign, referrer and landing page. The **Goals** report shows conversions and conversion rates for people, AI-referred people and agents, by channel. In [Agent content](https://www.punchlineanalytics.com/docs/agent-content), the funnel's last stage, *converted*, counts people sent by AI assistants who then completed a goal.

Query conversions yourself:

```json query
{
  "date_range": { "from": "2026-09-01", "to": "2026-09-30" },
  "metrics": ["conversions", "conversion_rate"],
  "dimensions": ["goal", "channel"],
  "filters": [["visitor_type", "neq", "agent"]]
}
```

or events:

```json query
{
  "date_range": { "from": "2026-09-01", "to": "2026-09-30" },
  "metrics": ["events", "event_value"],
  "dimensions": ["event_name", "visitor_type"]
}
```

Page, event and conversion metrics are separate families: one family per query.

## Did an AI assistant help? (survey)

Links show only part of AI's influence: someone asks ChatGPT for a shortlist, then types your address days later and arrives as "direct". One question after a conversion sizes the rest, the way podcast advertisers ask "how did you hear about us?".

**On your thank-you page,** with the browser snippet installed, add an element and ask:

```html
<div id="ai-question"></div>
<script>punchline.survey("#ai-question");</script>
```

It shows *Did an AI assistant (like ChatGPT, Claude or Perplexity) help you find or choose us?* with Yes, No and Not sure, sends one event for the answer and says thanks. It's plain markup with `pl-survey` class names for you to style; change the wording with `punchline.survey("#ai-question", { question: "…", thanks: "…" })`. Nothing is stored in the browser.

**From your server or email survey,** send the events yourself: `ai_assist_yes`, `ai_assist_no` or `ai_assist_not_sure`.

**What you see** on **Goals & events → Did an AI assistant help?**: the share who said yes (with a 95% interval), estimated AI-assisted conversions (people's conversions × that share), what Punchline connects directly (conversions by people sent from AI assistants, plus converted [handoff links](https://www.punchlineanalytics.com/docs/handoffs)), and the multiplier between the two. The estimate appears from 30 answers and is never mixed into observed numbers. Also at `GET /v1/ai-influence` and the MCP tool `get_ai_influence`.
