# Goals

> Set up conversions in Kymo — automatic goals, key-page goals, custom goals with revenue, and Stripe purchases — and read first-touch and last-touch source in the dashboard.

Canonical: https://kymo.in/docs/guides/goals
Last updated: 2026-08-11

A goal is any conversion you want to count: a signup, a purchase, a contact. Kymo captures three kinds.

## Automatic goals

These need no code. `beat.js` watches for them:

- **WhatsApp** links (`wa.me`, `api.whatsapp.com`, `web.whatsapp.com`, `whatsapp:`).
- **Phone** links (`tel:`).
- **Email** links (`mailto:`).
- **Form submits** — any `<form>` submission, filed as `form:<name>` using the form's `name` or `id`.

## Key-page goals

To count a view of a specific path as a conversion, list it in `data-goals` on the script tag, comma-separated:

```html
<script defer src="https://kymo.in/beat.js"
        data-site="yoursite.com"
        data-goals="/signup,/onboarding/complete"></script>
```

A pageview of a listed path records a goal named `page:/signup`.

## Custom goals

Fire a goal from your own code with `window.kymo`:

```js
// a simple named goal
window.kymo('newsletter-signup');

// a goal with revenue attached
window.kymo('purchase', { revenue: 49 });
```

The name is capped at 100 characters. `revenue` must be a number; it feeds the revenue totals in the dashboard.

> **Note:** Only pass values you are comfortable storing. A goal name and revenue amount are stored as you send them. Do not put personal data in a goal name.

## Stripe purchases

A payment confirmed on your server is more reliable than one fired from a success page, which a visitor can close early or reload. Kymo takes a Stripe webhook for exactly that.

### 1. Add the endpoint

In the Stripe Dashboard, under Developers → Webhooks, add `https://kymo.in/api/stripe` and subscribe it to `checkout.session.completed`.

### 2. Tag the Checkout Session

Kymo reads three metadata fields. The first is required; the other two are attribution and are both optional.

```js
// in the browser, before you create the session
const { visitor } = await fetch('https://kymo.in/api/whoami').then(r => r.json());
const ft = JSON.stringify(window.kymo.attribution() || {});

// on your server
const session = await stripe.checkout.sessions.create({
  // … your line items and URLs …
  metadata: {
    kymo_site: 'yoursite.com',  // required
    kymo_visitor: visitor,      // same-day attribution
    kymo_ft: ft                 // first touch, capped at 500 characters
  }
});
```

`kymo_ft` is the value from `window.kymo.attribution()`. Stripe caps a metadata value at 500 characters, so trim it before sending — [First-touch attribution](https://kymo.in/docs/guides/attribution) has the snippet that does it, and the field-by-field detail.

Each completed checkout lands as a `purchase` goal with its revenue. If `kymo_ft` is missing or unreadable, the purchase is still recorded, without first-touch data.

## Reading goals

The Goals page shows, per goal: completions, conversion rate against site visitors, revenue where present, and two source columns.

- **First touch** — the source that first brought the converting visitors to your site, which can be months before they converted.
- **Last touch** — the source of the visit that converted.

A dash in the First touch column means Kymo has no first touch for those visitors: they converted before first-touch capture shipped, they cleared their browser storage, or the site has `data-attribution="off"`. See [First-touch attribution](https://kymo.in/docs/guides/attribution).

## Revenue

Attach revenue to a goal with `window.kymo('purchase', { revenue: 49 })` and it totals in the dashboard's revenue view. Fire it wherever you confirm a payment — your checkout's success page is the usual spot, and the Stripe webhook above is the sturdier one.
