# Goals

A goal is an action that matters: `signup`, `trial_started`, `checkout_started`, `invite_sent`. Goals power conversion rates, funnels and journeys. You can send them from the browser, from HTML attributes, or from your server.

## From JavaScript

~~~js
window.twelvebit?.("signup", { plan: "pro", method: "google" });
~~~

- Use lowercase snake_case. twelvebit stores other names in that form ("Sign Up!" becomes `sign_up`, `signupCompleted` becomes `signup_completed`) and drops a name with no letters.
- Props are flat: strings, numbers or booleans. Up to 30 props; string values are cut to 255 characters.
- Send the goal when the action succeeds (after the API call returns), not when the button is clicked.

With a [tracking plan](/docs/tracking-plan), `twelvebit codegen` writes typed functions, so the names and props always match the plan:

~~~ts
import { track } from "./analytics.gen";

track.signup({ plan: "pro", method: "google" });
~~~

## From HTML attributes

No JavaScript needed. Good for links and buttons on marketing pages.

~~~html
<a href="/signup" data-tb-goal="signup_clicked" data-tb-goal-location="hero">Get started</a>
<button data-tb-goal="newsletter_joined" data-tb-goal-list="weekly">Subscribe</button>
~~~

`data-tb-goal-<name>="value"` adds a prop. Hyphens in the name become underscores: `data-tb-goal-billing-cycle` → `billing_cycle`.

## From your server

Server events cannot be blocked and are counted even when the browser closes. Send them with a site key:

~~~sh
curl -sS -X POST https://twelvebit.app/api/v1/events -H "Authorization: Bearer $TWELVEBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "subscription_started", "visitor_id": "VISITOR_ID", "props": {"plan": "pro"}}'
~~~

| Field | Type | Notes |
|---|---|---|
| `name` | string | Required. Lowercase snake_case, at most 64 characters. |
| `visitor_id` | string | The value of the `_tb_vid` cookie. |
| `user_id` | string | Your user id. Used when there is no `visitor_id`: twelvebit finds the visitor that [identified](/docs/identify) with this id. |
| `props` | object | Flat strings, numbers or booleans. |
| `url` | string | The page the action belongs to. Optional. |

Send `visitor_id` or `user_id`. The visitor must be known: the browser script must have sent at least one event for it. Otherwise the API answers `404` with the code `visitor_not_found`. Read the cookie in your request handler:

~~~ts
// app/api/projects/route.ts (Next.js)
import { cookies } from "next/headers";

export async function POST(req: Request) {
  // … create the project …
  const c = await cookies();
  void fetch("https://twelvebit.app/api/v1/events", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.TWELVEBIT_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ name: "project_created", visitor_id: c.get("_tb_vid")?.value, props: { template: "blank" } }),
  }).catch(() => {});
  return Response.json({ ok: true });
}
~~~

Use a site key (`tbk_…`) on the server only. With an account token (`tbt_…`), add `?site=<site id>`.

## Naming

- Past tense or a clear state: `signup`, `trial_started`, `invite_sent`. Not `click_button_3`.
- One event per action, with props for variants: `signup` with `method: "google"`, not `signup_google`.
- 3 to 8 goals are enough to start.
- Do not use the reserved names `pageview`, `identify`, `outbound`, `ping` or `payment`.
- Never put personal data (email, name, phone, free text) in props.

## See goals

The dashboard shows each goal with visitors, completions and attributed revenue. The API has `GET /api/v1/goals` and the MCP server has `list_goals`. Funnels can mix pages and goals: `GET /api/v1/funnels?steps=page:/pricing,goal:signup`.

---

Source: https://twelvebit.app/docs/goals · All docs: https://twelvebit.app/llms.txt
