# Goals and revenue

> Send custom events, define goals, record revenue and read conversions.

A goal tells Checkend what success looks like on a site, for example a signup or a purchase. Checkend then counts conversions and revenue, and the rules can find conversion drops.

## Send custom events

The tracking script defines `checkend.track(name, {props, revenue})`:

```html
<script>
  checkend.track("Signup");
  checkend.track("Purchase", { props: { plan: "pro" }, revenue: { amount: 49.9, currency: "USD" } });
</script>
```

To call `track` before the script loads, add this queue snippet before the script tag. The script sends the queued calls after the first page view:

```html
<script>window.checkend=window.checkend||{track:function(){(checkend.q=checkend.q||[]).push(arguments)}}</script>
```

You can also send an event with an HTML attribute. A click on the element sends the event:

```html
<button type="submit" data-checkend-event="Signup">Sign up</button>
```

### Automatic events

The script sends these events without code:

| Event name | When | Props |
|---|---|---|
| `Outbound Link` | A click (also a middle click) on a link to another host. | `url`: the link without query and fragment. |
| `File Download` | A click on a link on the same host to a file, for example `.pdf`, `.zip`, `.dmg` or `.csv`. | `url`, as above. |
| `404` | A page with `<meta name="checkend-404">` loads. Add the tag to your 404 page. | None |

### Limits

| Field | Rule |
|---|---|
| `name` | 1 to 120 characters: letters of any language, digits, spaces and `_ - . : /`. No space at the start or the end. `pageview` is reserved. |
| `props` | At most 20. A key has 1 to 64 letters, digits, `_`, `-` or `.`. A value has 1 to 256 characters and no control characters. |
| `revenue.amount` | More than 0 and less than 1,000,000,000, with at most 6 decimals. Checkend keeps the exact value. |
| `revenue.currency` | An ISO 4217 code, for example `USD`. Lower case is accepted. `XXX` and `XTS` are refused. |

Props and revenue need an event name.

> **Caution**
> Do not put personal data (email addresses, names, user IDs) in props. Checkend stores props as they are, for the full retention time.

## Send events from a server

A server can send a custom event, for example a purchase from a payment webhook. The token needs write access to the project of the site.

```sh
ce event send example.com --name Purchase --revenue 49.90 --currency USD --prop plan=pro
```

The REST route is `POST /v1/events`. The body is `{site, name, url?, ts?, vid?, props?, revenue?}`.

- The URL defaults to `https://<site>/`. It must be on the site domain or a subdomain.
- The event keeps no location and no IP-based visitor hash, because the request comes from your server.
- In extended privacy mode, `--vid` (or `vid`) gives a visitor ID.
- The bot filter does not apply. The rate limits and the daily event limit apply.

## Add goals

A goal belongs to one site. It has a name and one of two kinds:

| Kind | Flag | Matches |
|---|---|---|
| Event goal | `--event Signup` | The custom events with this name. |
| Page goal | `--page '/pricing*'` | The page views with a path that matches the pattern. `*` matches any characters. |

```sh
ce goals add example.com signup --event Signup
ce goals add example.com pricing --page '/pricing*'
ce goals list example.com
ce goals delete example.com <goal id> --yes
```

You can also use the **Add goal** form on the site page, or the MCP tool `manage_goals`. Add and delete need write access. A site has at most 50 goals. The goal name must be unique on the site.

Use the same event names in your code and in your goals.

## Read conversions and revenue

```sh
ce query example.com --period 7d
```

`ce query` shows the conversions, the conversion rate, the revenue for each currency, and a table of the goals. The MCP tools `query` and `timeseries` give the same numbers.

| Number | Meaning |
|---|---|
| Conversions | The visitors who completed at least one goal in the period. An event without a visitor ID counts as one conversion. |
| Conversion rate | Conversions divided by visitors. Each event without a visitor ID also counts as one more person, so the rate is at most 100 %. |
| Revenue | The revenue of all custom events in the period, one entry for each currency, with the event count. |
| Goals | The same numbers for each goal, and the numbers of the period before. Only event goals have revenue. |

An event has no visitor ID in strict mode, with Do Not Track or Global Privacy Control, and from a server without `vid`.

> **Note**
> Checkend never adds amounts of different currencies. Each currency has its own total.

### Strict mode

A site in strict mode has no visitor IDs. Each conversion event counts, and the conversion rate divides by page views. The answer says this rule in words.

## Goals and the closed loop

Goals feed the rules. Checkend makes a `conversion_drop` finding when the conversion rate of a goal falls, and a `traffic_without_conversions` finding for a page with traffic and no conversions. See [The closed loop](/docs/concepts/closed-loop.md).

A finding whose goal you delete gets the outcome `too_few_to_compare`.

## Prompt for a coding agent

```text
Add conversion tracking for example.com to this project. Replace example.com with my site.
1. Find the signup and purchase flows in the code.
2. After a successful signup, send a custom event: `window.checkend && checkend.track('Signup')`. Or put `data-checkend-event="Signup"` on the button.
3. After a successful purchase, send the revenue with the real amount and currency: `window.checkend && checkend.track('Purchase', { revenue: { amount: 49, currency: 'USD' } })`.
4. Add a goal for each event, for example `ce goals add example.com signup --event Signup`.
5. Use the same event names in the code and in the goals. Show me the events and goals that you added.
```
