> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Defining and reading Conversion Goals (Insights → Goals)

> Designate a CDP event as the outcome you care about — checkout completed, demo booked, positive reply — and let Orbit attribute each conversion back to the message, campaign, or AI agent that preceded it. Create goals from the dashboard or the API, fire conversions with a signed pixel or webhook, and read the counts, values, and attribution breakdowns.

# Defining and reading Conversion Goals

A conversion goal answers the question delivery metrics can't: **of everything you sent, what actually drove the outcome?** Opening **Insights → Goals** gives you the list of outcomes you're tracking; opening one gives you the drill-down — how many conversions, how much attributed value, and which messages, campaigns, and AI agents touched the converting contacts first.

The dashboard sits on a goals API, so everything here works from your own tooling too — the endpoints are on the [analytics API reference](/api-reference/endpoints/analytics), and the event vocabulary goals match against is the [CDP event model](/concepts/cdp-event-model).

## 1. What a conversion goal is

A goal is a named conversion definition: *when this event happens for a contact, count it, and attribute it to the outreach that touched the contact within the lookback window.* The event can be anything you emit — a `checkout_completed` event posted through CDP ingestion, a webhook your back end fires when a demo is booked, a tag applied to the contact, or a pixel firing on your pricing page. Orbit records each conversion with a timestamp, an optional monetary value, and the attributed message, campaign, and AI agent it could tie to the contact.

Attribution is per-goal and re-read under the model you pick:

| Model         | How it credits                                                                                                                      |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `last_touch`  | 100% to the most recent touchpoint inside the lookback window. The default — the closer's view.                                     |
| `first_touch` | 100% to the oldest touchpoint in the window. Surfaces what introduced the contact.                                                  |
| `linear`      | Credit split evenly across every touchpoint the contact had in the window.                                                          |
| `time_decay`  | Credit weighted by recency with a 7-day half-life — a touch 7 days before the conversion earns half the weight of a same-day touch. |

Only touchpoints **inside the goal's lookback window** (1–180 days) are considered; the join scans at most the 100 most recent touchpoints per conversion, so an extreme send cadence in the window truncates — keep the lookback proportional to your send rate. The join is over outbound messages and AI-agent conversations only; an ad click or an organic page visit leaves no row to join to, so an unattributed conversion is a signal the touchpoints happened outside your sending channels. For the anonymous-traffic antecedent view across your CDP event stream, use the [multi-touch attribution](/guides/multi-touch-attribution) report on the same conversion event.

A conversion whose contact saw no message or agent interaction in the window records with no attribution rather than inventing one. Conversions recorded without a contact id have nothing to join against — use them for counting, not attribution.

## 2. Creating a goal

Open **Insights → Goals** and choose **Create goal**. The same dialog covers create and edit.

* **Name** (required, up to 160 characters) — the human label: "Checkout completed", "Demo booked".
* **Type** (required) — what fires a conversion:
  * **Pixel fire** — your site loads the goal's signed pixel URL.
  * **Webhook hit** — your back end fires the goal's record endpoint over the server-to-server webhook.
  * **Manual** — conversions are recorded only when you explicitly fire the record endpoint (no automatic trigger).
  * **Tag added** — fires when the named tag is applied to a contact; the dialog adds a required **Tag name** field.
  * **Custom event** — matches events posted to the ingestion API under an exact name; the dialog adds a required **Event name** field and stores it on the goal's config.
* **Value** (optional) — the revenue assigned to each conversion, in your tenant's billing currency. Leave it blank for non-monetary goals; set it when you want each conversion to carry a value into the revenue rollups.
* **Lookback (days)** — how far back from each conversion Orbit looks for touchpoints, between 1 and 180 (default 7). This is the reach of the attribution join, not a reporting range.
* **Attribution model** — one of the four in the table above (default `last_touch`).
* **Config (JSON)** — optional extra fields stored verbatim with the goal — for example an allowed referrer domain on a pixel goal.
* **Enabled** — turn it off to stop recording new conversions; historical data is kept.

Over the API the same object is a `POST /api/v1/analytics/goals`:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/analytics/goals" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Checkout completed",
    "type": "custom_event",
    "config": { "event_name": "checkout_completed" },
    "value_cents": 4999,
    "lookback_days": 14,
    "attribution_model": "last_touch",
    "enabled": true
  }'
```

`value_cents` is integer minor units of your billing currency, or `null` for a non-monetary goal.

## 3. Reading the goals list

The list shows one row per goal with the columns you scan first:

| Column          | What it tells you                                                                                          |
| --------------- | ---------------------------------------------------------------------------------------------------------- |
| **Name**        | The goal label, with an **Enabled / Paused** badge beside it. Click the name to open the detail page.      |
| **Type**        | What fires the goal (pixel fire, webhook hit, manual, tag added, custom event).                            |
| **Attribution** | The model the goal credits by.                                                                             |
| **Lookback**    | The attribution window, in days.                                                                           |
| **Value**       | The per-conversion value you set, rendered in your billing currency, or an em dash when the goal has none. |
| **Conversions** | All-time recorded conversions for the goal.                                                                |
| **Revenue**     | All-time attributed monetary value across the goal's conversions.                                          |

A search box filters the loaded rows by name, type, or attribution model; the list pages 50 goals at a time with a **Load more** button, so a large library never arrives in one payload. Each row carries edit and delete actions — delete is permanent and removes the goal's recorded conversions with it.

## 4. Goal detail — the drill-down

Open a goal from the list. The header restates the definition — type, attribution model, lookback, value — and a window selector scopes every statistic below to the trailing **7, 14, 30, 60, or 90 days**.

* **Conversions** — how many conversions the goal recorded in the selected window.
* **Total value** — summed conversion value in the window, in your billing currency.
* **Lookback window** — the definition's attribution reach, with its model.
* **Attribution panels** — the top attributed **messages**, **campaigns**, and **agents**, each as a count with a proportional bar — the drill-down that answers *who* drove the number.
* **Recent conversions** — the latest conversion rows with the timestamp (rendered in your timezone), contact, value, and the attributed message / campaign / agent each one joined to. Rows with no join show a dash instead of an id.

A pixel-goal detail page also shows the **pixel URL** with a copy button — the signed 1×1 image URL your site loads to fire the goal.

## 5. Firing conversions

Three paths record a conversion; all land in the same table and attribution join.

**Pixel fire.** Each pixel goal gets a public URL the detail page hands you: `https://api.orbit.devotel.io/g/<goal_id>/<contact_id>.gif?sig=<signature>&schema=<tenant_schema>`. It returns a 1×1 transparent GIF with no-cache headers — whether it recorded a conversion or rejected the call — so a third-party page never renders a broken image. Compute the signature as `HMAC-SHA256(secret, "goalId:contactId:tenantSchema")` with your API secret; the tenant schema is part of the signed payload, so a signature valid for one workspace can't be replayed against another.

**Webhook hit.** Your back end fires the same record endpoint with a token header:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/analytics/goals/<goal_id>/record" \
  -H "Authorization: HMAC <contact_id>:<signature>" \
  -H "Content-Type: application/json" \
  -d '{ "value_cents": 4999 }'
```

The signature here binds `goalId:contactId`; a mismatch returns 401.

**Direct record.** With a contact you can reach from your session or an API key, post the conversion directly — this is also how manual-type goals are driven:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/analytics/goals/<goal_id>/record" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contact_id": "ct_…", "value_cents": 4999 }'
```

'Pass `value_cents` when the conversion carries real revenue — it overrides the goal's static value for that row, so a checkout records the order total, not the goal's placeholder. Anything you put in `metadata` stores verbatim on the conversion row.

## 6. Where goals feed downstream

The numbers you see on the goal pages reappear in three places:

* **The attribution surface** (`GET /api/v1/analytics/attribution` and the attribution dashboard) re-credits the conversions your goals already recorded across channels and campaigns under a model you pick per report — first-touch, last-touch, or linear — without changing the goal itself. The [multi-touch attribution guide](/guides/multi-touch-attribution) walks the models and guardrails.
* **The agent ROI dashboard** reads the goal-attributed revenue agents earned — see [Agent ROI attribution](/guides/agent-roi-attribution).
* **The Insights overview tiles and dashboards** roll goal conversions and attributed value up alongside delivery KPIs.

Three goal-shaped surfaces that are **not** this one, in case you land on them by name: **Outbound → Goals** is the campaign-approvals governance surface where a goal gates whether a campaign may send — the [outbound goals and approvals guide](/guides/outbound-goals-approvals-direct-send) covers it. A journey's conversion-goal tab (set on a campaign's journey) defines one outcome event per journey and reports per-step conversion — it feeds the [campaign ROAS view](/guides/campaign-roas-attribution). **Insights → Satisfaction goals** is the CSAT/NPS target tracker, unrelated to conversion attribution.

## 7. Worked example: a WhatsApp campaign that checks out

1. Emit purchases into the CDP. When your store collects payment, post the event with its value:
   ```bash theme={null}
   curl -X POST "https://api.orbit.devotel.io/api/v1/cdp/events" \
     -H "X-API-Key: $ORBIT_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{ "event": "checkout_completed", "contact_id": "ct_…", "value_cents": 4999 }'
   ```
2. Create the goal — **Custom event** with event name `checkout_completed`, lookback 14 days, `last_touch`, value left blank (the event carries its own).
3. Send the WhatsApp campaign. Recipients who check out within 14 days record conversions; each conversion joins to the campaign message the contact most recently received.
4. Read it back. The goal's detail page shows the conversion count and summed order value for the window; the **Top attributed campaigns** panel ranks your WhatsApp campaign by conversions; the recent-conversions table shows each purchase with the exact message and campaign it joined to. Compare the attributed value against the campaign's send spend on [ROAS and revenue attribution](/guides/campaign-roas-attribution) to read the revenue delta.

## 8. Role gating

Goals pages render for the **owner, admin, developer, and viewer** roles — the same read gate as the other Insights analytics surfaces. Creating, editing, deleting, and disabling goals is restricted to **owner, admin, and developer**; over the API the list, detail, stats, and conversions reads need an API key with the `contacts:read` scope, and writes need the role gate as well. The public pixel endpoint is unauthenticated by design — the signed URL is the access control.

## 9. Troubleshooting

| Symptom                                   | Cause                                                                                                                                                  | Fix                                                                                                                                                                                                                                                                                         |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Goal records nothing                      | The event never fired under the exact name the goal matches.                                                                                           | Check the ingestion stream for the event name byte-for-byte; `checkout_completed` and `checkout-completed` are different events. For pixel goals, load the pixel URL in a browser and confirm the signature before embedding. For tag goals, confirm the tag actually applies to a contact. |
| Conversions record but value shows a dash | The goal and the conversion both lack a monetary value.                                                                                                | Set a static **Value** on the goal, or pass `value_cents` on the record call / event.                                                                                                                                                                                                       |
| Attribution columns are all empty         | Every conversion fell outside the lookback window, or conversions record without a contact id, so no touchpoint can join.                              | Widen the lookback days on the goal; make sure the record call carries a `contact_id` for the contact you messaged.                                                                                                                                                                         |
| Webhook record returns 401                | The HMAC signature doesn't match.                                                                                                                      | Recompute `HMAC-SHA256(secret, "goalId:contactId")` with your API secret; the contact id in the header must match the signed one.                                                                                                                                                           |
| Pixel seems to do nothing                 | The endpoint returns the 1×1 GIF even on rejection, by design — a missing signature, an unsafe schema name, or a mismatched HMAC all still answer 200. | Verify the URL's `sig` was computed over `goalId:contactId:tenantSchema` and that `schema` matches your workspace exactly.                                                                                                                                                                  |
