# Atribu API — Complete Documentation

> Marketing attribution API. Base URL: https://www.atribu.app/api/v1
> Auth: Bearer token (API key starting with atb_live_)

---

All API requests require a Bearer token in the `Authorization` header.

```bash title="Authorization header"
Authorization: Bearer atb_live_your_key_here
```

## Getting a key

Create API keys from **Settings > Developer** in the Atribu dashboard. Each key is scoped to a single profile.

<Callout type="warn" title="Show once">
Keys are shown exactly once at creation. Store them securely — you cannot retrieve them later.
</Callout>

## Scopes

Each API key has granular scopes that control what data it can access.

| Scope | Access |
|-------|--------|
| `analytics:read` | Overview, timeseries, breakdowns (channels, pages, countries, devices, browsers, OS, referrers), quality, keywords |
| `campaigns:read` | Campaign performance, ROAS, ad set/ad breakdowns, daily trends |
| `conversions:read` | Conversion counts, daily conversion timeseries, revenue, cash collected |
| `customers:read` | Customer list with PII (name, email), individual journey timelines |
| `visitors:read` | Visitor list with PII (name, email), session history |
| `realtime:read` | Live visitor count |

**Default scopes** when creating a key: `analytics:read`, `campaigns:read`, `conversions:read`, `realtime:read`.

<Callout type="error" title="PII scopes">
`customers:read` and `visitors:read` expose personal data (names, emails, journey details). Only grant these when your integration specifically needs customer-level data.
</Callout>

## Rate limits

Default: **60 requests per minute** per key.

Heavy endpoints (customers, visitors, journey detail) cost 3x — meaning they consume 3 units of your rate limit per call.

| Header | Description |
|--------|-------------|
| `X-RateLimit-Limit` | Your total limit per minute |
| `X-RateLimit-Remaining` | Remaining requests in current window |
| `X-Request-Id` | Unique request ID for debugging |

When rate limited, you'll get a `429` response with a `Retry-After` header (seconds).

## Key rotation

Rotate keys with zero downtime:

<Steps>
<Step>
Call the **Rotate** endpoint — creates a new key
</Step>
<Step>
Both old and new keys work for **48 hours**
</Step>
<Step>
Update your integration with the new key
</Step>
<Step>
The old key automatically expires after the grace period
</Step>
</Steps>

## Security best practices

<Callout type="info" title="Server-to-server only">
This is a server-to-server API. Never expose keys in client-side JavaScript, mobile apps, or browser code.
</Callout>

- **Use the minimum scopes needed** — don't grant `customers:read` if you only need analytics
- **Rotate keys regularly** — the rotation endpoint makes this seamless
- **Revoke compromised keys immediately** — revocation is instant

## Error responses

```json title="Error response"
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or expired API key",
    "status": 401,
    "request_id": "req_a1b2c3d4"
  }
}
```

| Code | Status | Meaning |
|------|--------|---------|
| `unauthorized` | 401 | Missing or invalid API key |
| `insufficient_scope` | 403 | Key doesn't have the required scope |
| `rate_limit_exceeded` | 429 | Too many requests |
| `invalid_parameter` | 400 | Bad query parameter |
| `invalid_date_range` | 400 | Date range exceeds 366 days or dates are invalid |
| `internal_error` | 500 | Server error — include the `request_id` when reporting |

## Related

<Cards>
  <Card title="API Quickstart" href="/docs/api/quickstart">
    Make your first API call in 2 minutes
  </Card>
  <Card title="API Reference" href="/docs/api/overview">
    Full endpoint documentation
  </Card>
  <Card title="Team & Workspace" href="/docs/settings/team">
    Manage team access and roles
  </Card>
</Cards>

---

# Breakdowns

Seven breakdown endpoints share the same response shape. Each returns dimension
values ranked by visitors, along with traffic and conversion metrics.

**Scope:** `analytics:read`

## Endpoints

<Cards>
  <Card title="GET /api/v1/channels" href="#request">
    Traffic source — Paid Social, Organic Search, Direct, etc.
  </Card>
  <Card title="GET /api/v1/pages" href="#request">
    Page path — top landing and content pages.
  </Card>
  <Card title="GET /api/v1/countries" href="#request">
    Country code — ISO 3166-1 alpha-2.
  </Card>
  <Card title="GET /api/v1/devices" href="#request">
    Device type — desktop, mobile, tablet.
  </Card>
  <Card title="GET /api/v1/browsers" href="#request">
    Browser name — Chrome, Safari, Firefox, etc.
  </Card>
  <Card title="GET /api/v1/os" href="#request">
    Operating system — Windows, macOS, iOS, Android, etc.
  </Card>
  <Card title="GET /api/v1/referrers" href="#request">
    Referrer domain — the site that sent the visitor.
  </Card>
</Cards>

## Parameters

| Parameter           | Type   | Required | Description                                                              |
| ------------------- | ------ | -------- | ------------------------------------------------------------------------ |
| `date_from`         | string | Yes      | Start date in `YYYY-MM-DD` format.                                       |
| `date_to`           | string | Yes      | End date in `YYYY-MM-DD` format.                                         |
| `limit`             | number | No       | Maximum results to return. Default `10`, max `100`.                      |
| `filter[dimension]` | string | No       | Filter by any supported dimension. See [Filtering](#filtering) below.    |

## Request

All seven endpoints accept the same parameters and return the same shape. The
examples below use `/api/v1/channels`.

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/channels?date_from=2026-03-01&date_to=2026-03-25&limit=5"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/channels?date_from=2026-03-01&date_to=2026-03-25&limit=5",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data, meta } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/channels",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={"date_from": "2026-03-01", "date_to": "2026-03-25", "limit": 5},
)
data = res.json()["data"]
```
</Tab>
</Tabs>

## Response

```json title="Success response (200 OK)"
{
  "data": [
    {
      "value": "Paid Social",
      "visitors": 3200,
      "visits": 4100,
      "pageviews": 12500,
      "bounce_rate": 38.5,
      "conversions": 85,
      "conversion_rate": 2.07,
      "revenue": 8500.00
    },
    {
      "value": "Organic Search",
      "visitors": 2100,
      "visits": 2800,
      "pageviews": 7200,
      "bounce_rate": 48.2,
      "conversions": 32,
      "conversion_rate": 1.14,
      "revenue": 3200.00
    }
  ],
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Response fields

| Field             | Type   | Description                                                              |
| ----------------- | ------ | ------------------------------------------------------------------------ |
| `value`           | string | The dimension value (channel name, page path, country code, etc.).       |
| `visitors`        | number | Unique visitors.                                                         |
| `visits`          | number | Total sessions (visits).                                                 |
| `pageviews`       | number | Total page views.                                                        |
| `bounce_rate`     | number | Percentage of single-page sessions.                                      |
| `conversions`     | number | Total conversions attributed to this dimension value.                    |
| `conversion_rate` | number | Conversions divided by visitors, as a percentage.                        |
| `revenue`         | number | Attributed cash revenue in your reporting currency.                      |

## Filtering

Apply filters to any breakdown endpoint using the `filter[dimension]=operator:value`
query parameter syntax. Multiple filters can be combined.

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL — filter countries by Paid Social traffic"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/countries?date_from=2026-03-01&date_to=2026-03-25&filter[channel]=is:Paid+Social"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript — filter countries by Paid Social traffic"
const url = new URL("https://www.atribu.app/api/v1/countries");
url.searchParams.set("date_from", "2026-03-01");
url.searchParams.set("date_to", "2026-03-25");
url.searchParams.set("filter[channel]", "is:Paid Social");

const res = await fetch(url, {
  headers: { Authorization: "Bearer atb_live_YOUR_KEY" },
});
```
</Tab>
<Tab value="Python">
```python title="Python — filter countries by Paid Social traffic"
res = requests.get(
    "https://www.atribu.app/api/v1/countries",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "filter[channel]": "is:Paid Social",
    },
)
```
</Tab>
</Tabs>

### Supported operators

| Operator       | Description                                  | Example                              |
| -------------- | -------------------------------------------- | ------------------------------------ |
| `is`           | Exact match.                                 | `filter[country]=is:US`              |
| `is_not`       | Exclude exact match.                         | `filter[device]=is_not:desktop`      |
| `contains`     | Substring match (case-insensitive).          | `filter[page]=contains:/pricing`     |
| `not_contains` | Exclude substring match (case-insensitive).  | `filter[referrer]=not_contains:spam` |

### Available filter dimensions

`channel`, `referrer`, `campaign`, `page`, `entry_page`, `country`, `region`,
`city`, `browser`, `os`, `device`, `goal`.

<Callout type="info" title="Filters apply across endpoints">
  Filters work identically on all seven breakdown endpoints as well as on
  the [Timeseries](/docs/api/timeseries) endpoint. For example,
  `filter[channel]=is:Direct` on `/api/v1/pages` shows only pages visited
  by direct traffic.
</Callout>

## Related

<Cards>
  <Card title="Authentication" href="/docs/api/authentication">
    API key setup and scopes
  </Card>
  <Card title="Channel Classification" href="/docs/concepts/channels">
    How channels are detected from UTMs, referrers, and click IDs
  </Card>
</Cards>

---

# Campaigns

## Top Campaigns

```http title="Endpoint"
GET /api/v1/campaigns
```

Returns top-performing campaigns ranked by attributed outcome value, with ROAS
and spend data. Pass `level` to drill down to ad sets or individual ads.

**Scope:** `campaigns:read`

### Parameters

| Parameter   | Type   | Required | Description                                                                 |
| ----------- | ------ | -------- | --------------------------------------------------------------------------- |
| `date_from` | string | Yes      | Start date in `YYYY-MM-DD` format.                                          |
| `date_to`   | string | Yes      | End date in `YYYY-MM-DD` format.                                            |
| `model`     | string | No       | Attribution model. Default `last_touch`. Options: `first_touch`, `linear`, `position_based`, `time_decay`, `last_non_direct`. |
| `limit`     | number | No       | Maximum results. Default `10`, max `100`.                                   |
| `level`     | string | No       | Hierarchy level: `campaign`, `ad_set`, or `ad`. When set, returns detailed performance fields. |

### Request

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/campaigns?date_from=2026-03-01&date_to=2026-03-25&model=last_touch&limit=5"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/campaigns?date_from=2026-03-01&date_to=2026-03-25&model=last_touch&limit=5",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data, meta } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/campaigns",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "model": "last_touch",
        "limit": 5,
    },
)
data = res.json()["data"]
```
</Tab>
</Tabs>

### Response

```json title="Success response (200 OK)"
{
  "data": [
    {
      "campaign_id": "uuid",
      "campaign_name": "Spring Sale - Conversions",
      "spend": 1800.00,
      "outcome_count": 45,
      "outcome_value": 6200.00,
      "roas": 3.44
    }
  ],
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Response fields

| Field           | Type   | Description                                                          |
| --------------- | ------ | -------------------------------------------------------------------- |
| `campaign_id`   | string | Internal campaign UUID.                                              |
| `campaign_name` | string | Human-readable campaign name from the ad platform.                   |
| `spend`         | number | Total ad spend in your reporting currency.                           |
| `outcome_count` | number | Number of attributed conversions.                                    |
| `outcome_value` | number | Total attributed cash revenue.                                       |
| `roas`          | number | Return on ad spend (`outcome_value / spend`).                        |

### Ad-level detail

Pass `level=ad` to get granular performance per ad creative. This adds
additional fields to each row.

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL — ad-level breakdown"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/campaigns?date_from=2026-03-01&date_to=2026-03-25&level=ad"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript — ad-level breakdown"
const res = await fetch(
  "https://www.atribu.app/api/v1/campaigns?date_from=2026-03-01&date_to=2026-03-25&level=ad",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python — ad-level breakdown"
res = requests.get(
    "https://www.atribu.app/api/v1/campaigns",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={"date_from": "2026-03-01", "date_to": "2026-03-25", "level": "ad"},
)
data = res.json()["data"]
```
</Tab>
</Tabs>

#### Additional fields when `level` is set

| Field          | Type   | Description                                          |
| -------------- | ------ | ---------------------------------------------------- |
| `entity_level` | string | The level of this row (`campaign`, `ad_set`, `ad`).  |
| `parent_name`  | string | Name of the parent entity (campaign or ad set).      |
| `impressions`  | number | Total impressions served.                            |
| `clicks`       | number | Total clicks.                                        |
| `reach`        | number | Unique accounts reached.                             |
| `ctr`          | number | Click-through rate (%).                              |
| `avg_cpm`      | number | Average cost per 1,000 impressions.                  |
| `avg_cpc`      | number | Average cost per click.                              |
| `cac`          | number | Customer acquisition cost (`spend / outcome_count`). |
| `status`       | string | Entity status (`ACTIVE`, `PAUSED`, etc.).            |
| `objective`    | string | Campaign objective.                                  |
| `daily_budget` | number | Daily budget if set on the platform.                 |

---

## Campaign Trend

```http title="Endpoint"
GET /api/v1/campaigns/trend
```

Returns daily spend, impressions, and clicks for up to 10 specific campaigns,
ad sets, or ads. Use this to render sparklines or compare entity performance
over time.

**Scope:** `campaigns:read`

### Parameters

| Parameter    | Type   | Required | Description                                                              |
| ------------ | ------ | -------- | ------------------------------------------------------------------------ |
| `date_from`  | string | Yes      | Start date in `YYYY-MM-DD` format.                                       |
| `date_to`    | string | Yes      | End date in `YYYY-MM-DD` format.                                         |
| `entity_ids` | string | Yes      | Comma-separated list of entity UUIDs (max 10).                           |
| `level`      | string | No       | `campaign`, `ad_set`, or `ad`. Default `campaign`.                       |

### Request

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/campaigns/trend?date_from=2026-03-20&date_to=2026-03-25&entity_ids=uuid1,uuid2&level=campaign"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const ids = ["uuid1", "uuid2"].join(",");
const res = await fetch(
  `https://www.atribu.app/api/v1/campaigns/trend?date_from=2026-03-20&date_to=2026-03-25&entity_ids=${ids}&level=campaign`,
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/campaigns/trend",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-20",
        "date_to": "2026-03-25",
        "entity_ids": "uuid1,uuid2",
        "level": "campaign",
    },
)
data = res.json()["data"]
```
</Tab>
</Tabs>

### Response

```json title="Success response (200 OK)"
{
  "data": [
    { "entity_id": "uuid1", "date": "2026-03-20", "spend": 120.00, "impressions": 15000, "clicks": 340 },
    { "entity_id": "uuid1", "date": "2026-03-21", "spend": 135.00, "impressions": 16200, "clicks": 380 },
    { "entity_id": "uuid2", "date": "2026-03-20", "spend": 95.00, "impressions": 11800, "clicks": 275 }
  ],
  "meta": {
    "date_from": "2026-03-20",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Response fields

| Field         | Type   | Description                          |
| ------------- | ------ | ------------------------------------ |
| `entity_id`   | string | The campaign, ad set, or ad UUID.    |
| `date`        | string | Calendar date (`YYYY-MM-DD`).        |
| `spend`       | number | Ad spend for that entity on that day.|
| `impressions` | number | Impressions served.                  |
| `clicks`      | number | Clicks recorded.                     |

<Callout type="info" title="Entity ID format">
  `entity_ids` expects Atribu internal UUIDs (from the `campaign_id` field in
  the Top Campaigns response), not platform-specific IDs. You can pass up to
  10 IDs in a single request.
</Callout>

## Related

<Cards>
  <Card title="Authentication" href="/docs/api/authentication">
    API key setup and scopes
  </Card>
  <Card title="Ads Performance" href="/docs/features/ads-performance">
    The dashboard view that uses the same data
  </Card>
  <Card title="Attribution Models" href="/docs/concepts/attribution-models">
    How the model parameter affects ROAS rankings
  </Card>
</Cards>

---

# Conversions & Revenue

Four endpoints for querying conversion activity and revenue attribution. All
require the `conversions:read` scope.

<Callout type="warn" title="Revenue means cash only">
  Revenue in all endpoints below refers exclusively to confirmed payments from
  Stripe and MercadoPago. GHL pipeline values (deal stages, closed-won
  projections) are **never** included in revenue or ROAS calculations -- they
  are projections, not confirmed cash.
</Callout>

---

## Conversion Counts

```http title="Endpoint"
GET /api/v1/conversions
```

Returns total conversions grouped by event type for the requested period.

**Scope:** `conversions:read`

### Parameters

| Parameter   | Type   | Required | Description                        |
| ----------- | ------ | -------- | ---------------------------------- |
| `date_from` | string | Yes      | Start date in `YYYY-MM-DD` format. |
| `date_to`   | string | Yes      | End date in `YYYY-MM-DD` format.   |

### Request

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/conversions?date_from=2026-03-01&date_to=2026-03-25"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/conversions?date_from=2026-03-01&date_to=2026-03-25",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/conversions",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={"date_from": "2026-03-01", "date_to": "2026-03-25"},
)
data = res.json()["data"]
```
</Tab>
</Tabs>

### Response

```json title="Success response (200 OK)"
{
  "data": [
    { "event_type": "lead_created", "count": 85 },
    { "event_type": "appointment_booked", "count": 42 },
    { "event_type": "payment_received", "count": 28 },
    { "event_type": "closed_won", "count": 15 }
  ],
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Response fields

| Field        | Type   | Description                                      |
| ------------ | ------ | ------------------------------------------------ |
| `event_type` | string | Conversion type (e.g., `lead_created`, `payment_received`). |
| `count`      | number | Total conversions of this type in the period.    |

---

## Daily Conversions

```http title="Endpoint"
GET /api/v1/conversions/timeseries
```

Returns daily conversion counts broken down by event type. Use this to chart
conversion trends over time.

**Scope:** `conversions:read`

### Parameters

| Parameter   | Type   | Required | Description                        |
| ----------- | ------ | -------- | ---------------------------------- |
| `date_from` | string | Yes      | Start date in `YYYY-MM-DD` format. |
| `date_to`   | string | Yes      | End date in `YYYY-MM-DD` format.   |

### Request

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/conversions/timeseries?date_from=2026-03-20&date_to=2026-03-25"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/conversions/timeseries?date_from=2026-03-20&date_to=2026-03-25",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/conversions/timeseries",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={"date_from": "2026-03-20", "date_to": "2026-03-25"},
)
data = res.json()["data"]
```
</Tab>
</Tabs>

### Response

```json title="Success response (200 OK)"
{
  "data": [
    { "date": "2026-03-20", "event_type": "lead_created", "count": 12 },
    { "date": "2026-03-20", "event_type": "payment_received", "count": 3 },
    { "date": "2026-03-21", "event_type": "lead_created", "count": 15 },
    { "date": "2026-03-21", "event_type": "appointment_booked", "count": 7 }
  ],
  "meta": {
    "date_from": "2026-03-20",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Response fields

| Field        | Type   | Description                                    |
| ------------ | ------ | ---------------------------------------------- |
| `date`       | string | Calendar date (`YYYY-MM-DD`).                  |
| `event_type` | string | Conversion type.                               |
| `count`      | number | Number of conversions of this type on this day.|

---

## Revenue by Day

```http title="Endpoint"
GET /api/v1/revenue
```

Returns daily attributed revenue and ad spend. Revenue is computed using the
selected attribution model and includes cash payments only.

**Scope:** `conversions:read`

### Parameters

| Parameter   | Type   | Required | Description                                                                 |
| ----------- | ------ | -------- | --------------------------------------------------------------------------- |
| `date_from` | string | Yes      | Start date in `YYYY-MM-DD` format.                                          |
| `date_to`   | string | Yes      | End date in `YYYY-MM-DD` format.                                            |
| `model`     | string | No       | Attribution model. Default `last_touch`. Options: `first_touch`, `linear`, `position_based`, `time_decay`, `last_non_direct`. |

### Request

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/revenue?date_from=2026-03-20&date_to=2026-03-25&model=last_touch"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/revenue?date_from=2026-03-20&date_to=2026-03-25&model=last_touch",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/revenue",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-20",
        "date_to": "2026-03-25",
        "model": "last_touch",
    },
)
data = res.json()["data"]
```
</Tab>
</Tabs>

### Response

```json title="Success response (200 OK)"
{
  "data": [
    { "date": "2026-03-20", "revenue": 2400.00, "spend": 680.00 },
    { "date": "2026-03-21", "revenue": 1800.00, "spend": 720.00 },
    { "date": "2026-03-22", "revenue": 3200.00, "spend": 650.00 }
  ],
  "meta": {
    "date_from": "2026-03-20",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Response fields

| Field     | Type   | Description                                      |
| --------- | ------ | ------------------------------------------------ |
| `date`    | string | Calendar date (`YYYY-MM-DD`).                    |
| `revenue` | number | Attributed cash revenue in reporting currency.   |
| `spend`   | number | Total ad spend across all platforms.              |

---

## Cash Collected

```http title="Endpoint"
GET /api/v1/revenue/cash
```

Returns total cash payments grouped by payment source and currency. Unlike the
attributed revenue endpoint above, this shows raw payment totals regardless of
attribution model.

**Scope:** `conversions:read`

### Parameters

| Parameter   | Type   | Required | Description                        |
| ----------- | ------ | -------- | ---------------------------------- |
| `date_from` | string | Yes      | Start date in `YYYY-MM-DD` format. |
| `date_to`   | string | Yes      | End date in `YYYY-MM-DD` format.   |

### Request

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/revenue/cash?date_from=2026-03-01&date_to=2026-03-25"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/revenue/cash?date_from=2026-03-01&date_to=2026-03-25",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/revenue/cash",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={"date_from": "2026-03-01", "date_to": "2026-03-25"},
)
data = res.json()["data"]
```
</Tab>
</Tabs>

### Response

```json title="Success response (200 OK)"
{
  "data": [
    { "source": "stripe", "currency": "USD", "total": 9800.00, "count": 35 },
    { "source": "mercadopago", "currency": "MXN", "total": 45000.00, "count": 12 }
  ],
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Response fields

| Field      | Type   | Description                                          |
| ---------- | ------ | ---------------------------------------------------- |
| `source`   | string | Payment provider (`stripe`, `mercadopago`).          |
| `currency` | string | ISO 4217 currency code.                              |
| `total`    | number | Sum of payments in the original currency.            |
| `count`    | number | Number of payments.                                  |

<Callout type="info" title="Cash collected vs. attributed revenue">
  **Cash collected** (`/revenue/cash`) is the raw sum of payments, independent
  of any attribution model. **Attributed revenue** (`/revenue`) distributes
  payment value across the marketing touchpoints that contributed to the
  conversion, based on the selected model. Use cash collected for financial
  reconciliation and attributed revenue for marketing performance analysis.
</Callout>

## Related

<Cards>
  <Card title="Authentication" href="/docs/api/authentication">
    API key setup and scopes
  </Card>
  <Card title="Revenue Types" href="/docs/concepts/revenue-types">
    Why only cash payments count for revenue and ROAS
  </Card>
  <Card title="Conversion Goals" href="/docs/settings/goals">
    Configure which events count as conversions
  </Card>
</Cards>

---

# Customers & Journey

<Callout type="error" title="PII scope required">
  Both endpoints on this page return personally identifiable information (names,
  emails). Your API key must have the `customers:read` scope explicitly granted.
  Keys without this scope will receive a `403 Forbidden` response.
</Callout>

---

## Customer List

```http title="Endpoint"
GET /api/v1/customers
```

Returns a paginated list of customers who achieved a specific conversion goal
within the date range. Each row includes the customer's identity, the conversion
details, channel attribution, and lifetime metrics.

**Scope:** `customers:read`

### Parameters

| Parameter     | Type   | Required | Description                                                        |
| ------------- | ------ | -------- | ------------------------------------------------------------------ |
| `date_from`   | string | Yes      | Start date in `YYYY-MM-DD` format.                                 |
| `date_to`     | string | Yes      | End date in `YYYY-MM-DD` format.                                   |
| `goal`        | string | Yes      | Conversion type: `payment_received`, `lead_created`, `appointment_booked`, `closed_won`, etc. |
| `search`      | string | No       | Search by customer name or email.                                  |
| `limit`       | number | No       | Results per page. Default `10`, max `100`.                         |
| `cursor_time` | string | No       | Pagination cursor timestamp (from previous response).              |
| `cursor_id`   | string | No       | Pagination cursor ID (from previous response).                     |

### Request

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/customers?date_from=2026-03-01&date_to=2026-03-25&goal=payment_received&limit=5"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/customers?date_from=2026-03-01&date_to=2026-03-25&goal=payment_received&limit=5",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data, pagination } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/customers",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "goal": "payment_received",
        "limit": 5,
    },
)
body = res.json()
data = body["data"]
has_next = body["pagination"]["has_next"]
```
</Tab>
</Tabs>

### Response

```json title="Success response (200 OK)"
{
  "data": [
    {
      "conversion_id": "uuid",
      "customer_profile_id": "uuid",
      "name": "Jane Smith",
      "email": "jane@example.com",
      "country": "US",
      "device": "mobile",
      "channel": "Paid Social",
      "source": "ig",
      "revenue": 299.00,
      "revenue_type": "cash",
      "conversion_time": "2026-03-22T14:30:00Z",
      "time_to_complete_seconds": 172800,
      "touch_count": 3,
      "touch_channels": ["Paid Social", "Direct"],
      "conversion_count": 2,
      "total_revenue": 598.00
    }
  ],
  "pagination": {
    "has_next": true,
    "cursor": "2026-03-22T14:30:00Z|uuid"
  },
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Response fields

| Field                      | Type     | Description                                                     |
| -------------------------- | -------- | --------------------------------------------------------------- |
| `conversion_id`            | string   | Unique ID of this conversion event.                             |
| `customer_profile_id`      | string   | Customer's profile UUID. Use this for the Journey endpoint.     |
| `name`                     | string   | Customer's full name (may be `null` for anonymous visitors).    |
| `email`                    | string   | Customer's email address (may be `null`).                       |
| `country`                  | string   | ISO country code from the converting session.                   |
| `device`                   | string   | Device type: `desktop`, `mobile`, `tablet`.                     |
| `channel`                  | string   | Marketing channel of the last touch before conversion.          |
| `source`                   | string   | Traffic source (e.g., `ig`, `fb`, `google`).                    |
| `revenue`                  | number   | Revenue attributed to this specific conversion.                 |
| `revenue_type`             | string   | `cash`, `pipeline`, or `gross`.                                 |
| `conversion_time`          | string   | ISO 8601 timestamp of the conversion.                           |
| `time_to_complete_seconds` | number   | Seconds between first touch and conversion.                     |
| `touch_count`              | number   | Number of marketing touchpoints before conversion.              |
| `touch_channels`           | string[] | Distinct channels across all touchpoints.                       |
| `conversion_count`         | number   | Total conversions by this customer (lifetime).                  |
| `total_revenue`            | number   | Total revenue from this customer (lifetime).                    |

### Pagination

This endpoint uses cursor-based pagination. The `pagination.cursor` field in the
response contains the values needed to fetch the next page.

<Steps>
<Step>
Fetch the first page with your desired `limit`.
</Step>
<Step>
Check `pagination.has_next`. If `true`, split `pagination.cursor` by `|` to get
`cursor_time` and `cursor_id`.
</Step>
<Step>
Pass both values in the next request.
</Step>
</Steps>

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL — fetch page 2"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/customers?date_from=2026-03-01&date_to=2026-03-25&goal=payment_received&cursor_time=2026-03-22T14:30:00Z&cursor_id=uuid"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript — paginate through all results"
let allCustomers = [];
let cursorTime = undefined;
let cursorId = undefined;

while (true) {
  const url = new URL("https://www.atribu.app/api/v1/customers");
  url.searchParams.set("date_from", "2026-03-01");
  url.searchParams.set("date_to", "2026-03-25");
  url.searchParams.set("goal", "payment_received");
  url.searchParams.set("limit", "50");
  if (cursorTime) url.searchParams.set("cursor_time", cursorTime);
  if (cursorId) url.searchParams.set("cursor_id", cursorId);

  const res = await fetch(url, {
    headers: { Authorization: "Bearer atb_live_YOUR_KEY" },
  });
  const body = await res.json();
  allCustomers.push(...body.data);

  if (!body.pagination.has_next) break;
  [cursorTime, cursorId] = body.pagination.cursor.split("|");
}
```
</Tab>
<Tab value="Python">
```python title="Python — paginate through all results"
import requests

all_customers = []
cursor_time = None
cursor_id = None

while True:
    params = {
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "goal": "payment_received",
        "limit": 50,
    }
    if cursor_time:
        params["cursor_time"] = cursor_time
        params["cursor_id"] = cursor_id

    body = requests.get(
        "https://www.atribu.app/api/v1/customers",
        headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
        params=params,
    ).json()

    all_customers.extend(body["data"])

    if not body["pagination"]["has_next"]:
        break
    cursor_time, cursor_id = body["pagination"]["cursor"].split("|")
```
</Tab>
</Tabs>

---

## Customer Journey

```http title="Endpoint"
GET /api/v1/customers/{id}/journey
```

Returns the full event timeline for a single customer -- every page view, form
submission, booking, payment, and marketing touchpoint in chronological order.

**Scope:** `customers:read`

### Parameters

| Parameter | Type   | Required | Description                                   |
| --------- | ------ | -------- | --------------------------------------------- |
| `id`      | string | Yes      | Customer profile UUID (path parameter).       |
| `offset`  | number | No       | Skip the first N events. Default `0`.         |
| `limit`   | number | No       | Maximum events to return. Default `50`, max `100`. |

<Callout type="info" title="BOLA protection">
  This endpoint enforces object-level authorization. If the customer ID does not
  belong to your profile, it returns empty data (not an error). This prevents
  information disclosure about whether specific customer IDs exist in the system.
</Callout>

### Request

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/customers/customer-uuid/journey?limit=20"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const customerId = "customer-uuid";
const res = await fetch(
  `https://www.atribu.app/api/v1/customers/${customerId}/journey?limit=20`,
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();
console.log(`${data.total_count} events in journey`);
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

customer_id = "customer-uuid"
res = requests.get(
    f"https://www.atribu.app/api/v1/customers/{customer_id}/journey",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={"limit": 20},
)
events = res.json()["data"]["events"]
```
</Tab>
</Tabs>

### Response

```json title="Success response (200 OK)"
{
  "data": {
    "events": [
      {
        "event_type": "page_view",
        "event_name": "page_view",
        "event_time": "2026-03-18T10:15:00Z",
        "url": "https://example.com/pricing",
        "path": "/pricing",
        "channel": "Paid Social",
        "source": "ig",
        "medium": "paid",
        "campaign": "Spring Sale",
        "value_amount": null,
        "currency": null,
        "device": "mobile",
        "browser": "Safari",
        "os": "iOS",
        "country": "US",
        "city": "New York",
        "is_synthetic": false
      },
      {
        "event_type": "payment_received",
        "event_name": "payment_received",
        "event_time": "2026-03-20T16:45:00Z",
        "url": null,
        "path": null,
        "channel": null,
        "source": "stripe",
        "medium": null,
        "campaign": null,
        "value_amount": 299.00,
        "currency": "USD",
        "device": null,
        "browser": null,
        "os": null,
        "country": null,
        "city": null,
        "is_synthetic": false
      }
    ],
    "total_count": 12
  },
  "meta": {
    "profile_id": "uuid"
  }
}
```

### Response fields

| Field          | Type    | Description                                                   |
| -------------- | ------- | ------------------------------------------------------------- |
| `event_type`   | string  | Event category (`page_view`, `lead_created`, `payment_received`, etc.). |
| `event_name`   | string  | Specific event name.                                          |
| `event_time`   | string  | ISO 8601 timestamp.                                           |
| `url`          | string  | Full page URL (web events only).                              |
| `path`         | string  | URL path component.                                           |
| `channel`      | string  | Classified marketing channel.                                 |
| `source`       | string  | Traffic source or payment provider.                           |
| `medium`       | string  | Marketing medium (`paid`, `organic`, `referral`, etc.).       |
| `campaign`     | string  | Campaign name (resolved from platform ID).                    |
| `value_amount` | number  | Monetary value (for payment and deal events).                 |
| `currency`     | string  | ISO 4217 currency code.                                       |
| `device`       | string  | Device type.                                                  |
| `browser`      | string  | Browser name.                                                 |
| `os`           | string  | Operating system.                                             |
| `country`      | string  | ISO country code.                                             |
| `city`         | string  | City name.                                                    |
| `is_synthetic` | boolean | `true` for off-site conversions that had no website visit.    |
| `total_count`  | number  | Total events in the journey (use with `offset`/`limit` for paging). |

<Callout type="info" title="Synthetic touchpoints">
  Events with `is_synthetic: true` represent off-site conversions (e.g., Meta
  lead forms submitted via Instagram that flow into GoHighLevel without a
  website visit). Atribu creates synthetic touchpoints for these so they can
  still be attributed to the originating ad campaign. See [Synthetic Touches](/docs/concepts/synthetic-touches) for details.
</Callout>

## Related

<Cards>
  <Card title="Authentication" href="/docs/api/authentication">
    API key setup, scopes (customers:read requires explicit grant)
  </Card>
  <Card title="Customer Journeys" href="/docs/features/customer-journey">
    The dashboard view for exploring customer paths
  </Card>
  <Card title="Identity Resolution" href="/docs/concepts/identity-resolution">
    How customer profiles are built from multiple identifiers
  </Card>
</Cards>

---

```http title="Endpoint"
GET /api/v1/overview
```

Returns KPIs for the current period and an automatically-computed previous period of equal length for comparison.

**Scope:** `analytics:read`

## Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `date_from` | string | Yes | Start date (YYYY-MM-DD) |
| `date_to` | string | Yes | End date (YYYY-MM-DD) |
| `model` | string | No | Attribution model (default: `last_touch`) |
| `filter[dimension]` | string | No | Filter by dimension, e.g., `filter[channel]=is:Paid Social` |

<Callout type="info" title="Attribution models">
`last_touch` · `first_touch` · `split_50_50` · `linear` · `position_based` · `time_decay` · `last_non_direct` · `custom_weighted`
</Callout>

## Request

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/overview?date_from=2026-03-01&date_to=2026-03-25&model=last_touch"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/overview?date_from=2026-03-01&date_to=2026-03-25&model=last_touch",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/overview",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "model": "last_touch",
    },
)
data = resp.json()
```
</Tab>
</Tabs>

## Response

```json title="Success response (200 OK)"
{
  "data": {
    "current": {
      "spend": 4250.00,
      "revenue": 12800.00,
      "roas": 3.01,
      "outcomes": 145,
      "attributed_outcomes": 132,
      "coverage_percent": 91.03,
      "visitors": 8420,
      "pageviews": 24100,
      "bounce_rate": 42.5,
      "avg_engaged_seconds": 185,
      "cash_revenue": 12800.00,
      "cash_payments": 48
    },
    "previous": {
      "spend": 3900.00,
      "revenue": 10200.00,
      "roas": 2.62,
      "outcomes": 128,
      "attributed_outcomes": 115,
      "coverage_percent": 89.84,
      "visitors": 7200,
      "pageviews": 20500,
      "bounce_rate": 45.2,
      "avg_engaged_seconds": 165,
      "cash_revenue": 10200.00,
      "cash_payments": 38
    }
  },
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

## Response fields

| Field | Type | Description |
|-------|------|-------------|
| `spend` | number | Total ad spend in reporting currency |
| `revenue` | number | Attributed cash revenue (ROAS numerator) |
| `roas` | number | Return on ad spend (revenue / spend) |
| `outcomes` | number | Total conversions |
| `attributed_outcomes` | number | Conversions with at least one attributed touchpoint |
| `coverage_percent` | number | Attribution coverage (attributed / total x 100) |
| `visitors` | number | Unique visitors (distinct anonymous_id) |
| `pageviews` | number | Total page views |
| `bounce_rate` | number | Single-page session percentage |
| `avg_engaged_seconds` | number | Average engaged time per session |
| `cash_revenue` | number | Total cash payments (Stripe + MercadoPago) |
| `cash_payments` | number | Count of cash payment events |

<Callout type="info" title="Previous period">
The previous period is automatically computed to be the same length as the current period, ending the day before `date_from`. For example, if you query March 1-25 (25 days), the previous period will be February 4-28.
</Callout>

## Related

<Cards>
  <Card title="Authentication" href="/docs/api/authentication">
    API key setup, scopes, and rate limits
  </Card>
  <Card title="Attribution Models" href="/docs/concepts/attribution-models">
    How the model parameter affects revenue and ROAS
  </Card>
  <Card title="Revenue Types" href="/docs/concepts/revenue-types">
    Why revenue fields only include cash payments
  </Card>
</Cards>

---

<Steps>
<Step>

## Get your API key

1. Log in to [Atribu](https://www.atribu.app)
2. Go to **Settings > Developer**
3. Click **Create API Key**
4. Select your scopes and copy the key

<Callout type="warn" title="Save your key">
The key starts with `atb_live_` and is shown only once. Store it somewhere secure.
</Callout>

</Step>
<Step>

## Make your first request

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY_HERE" \
  "https://www.atribu.app/api/v1/overview?date_from=2026-03-01&date_to=2026-03-25"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/overview?date_from=2026-03-01&date_to=2026-03-25",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY_HERE",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/overview",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY_HERE"},
    params={"date_from": "2026-03-01", "date_to": "2026-03-25"},
)
data = resp.json()
```
</Tab>
</Tabs>

</Step>
<Step>

## Example response

```json title="Success response (200 OK)"
{
  "data": {
    "current": {
      "spend": 4250.00,
      "revenue": 12800.00,
      "roas": 3.01,
      "outcomes": 145,
      "attributed_outcomes": 132,
      "coverage_percent": 91.03,
      "visitors": 8420,
      "pageviews": 24100,
      "bounce_rate": 42.5,
      "avg_engaged_seconds": 185,
      "cash_revenue": 12800.00,
      "cash_payments": 48
    },
    "previous": { "..." : "..." }
  },
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "your-profile-id"
  }
}
```

</Step>
</Steps>

## Next steps

<Cards>
  <Card title="Authentication" href="/docs/api/authentication">
    API key scopes, rate limits, and security best practices
  </Card>
  <Card title="API Reference" href="/docs/api/overview">
    Full endpoint documentation
  </Card>
  <Card title="Attribution Models" href="/docs/concepts/attribution-models">
    How credit is assigned
  </Card>
</Cards>

---

```http title="Endpoint"
GET /api/v1/timeseries
```

Returns daily data points for visitors, pageviews, bounces, cash revenue, and ad spend across
the requested date range. Use this endpoint to power line charts, bar charts, and trend
comparisons in your dashboards.

**Scope:** `analytics:read`

## Parameters

| Parameter            | Type   | Required | Description                                                       |
| -------------------- | ------ | -------- | ----------------------------------------------------------------- |
| `date_from`          | string | Yes      | Start date in `YYYY-MM-DD` format.                                |
| `date_to`            | string | Yes      | End date in `YYYY-MM-DD` format.                                  |
| `filter[dimension]`  | string | No       | Filter results by a dimension. See [Breakdowns](/docs/api/breakdowns#filtering) for syntax. |

## Request

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/timeseries?date_from=2026-03-20&date_to=2026-03-25"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/timeseries?date_from=2026-03-20&date_to=2026-03-25",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data, meta } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/timeseries",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={"date_from": "2026-03-20", "date_to": "2026-03-25"},
)
data = res.json()["data"]
```
</Tab>
</Tabs>

## Response

```json title="Success response (200 OK)"
{
  "data": [
    {
      "date": "2026-03-20",
      "visitors": 1250,
      "pageviews": 3600,
      "bounces": 520,
      "cash_revenue": 2400.00,
      "spend": 680.00
    },
    {
      "date": "2026-03-21",
      "visitors": 1180,
      "pageviews": 3400,
      "bounces": 490,
      "cash_revenue": 1800.00,
      "spend": 720.00
    },
    {
      "date": "2026-03-22",
      "visitors": 980,
      "pageviews": 2800,
      "bounces": 410,
      "cash_revenue": 3200.00,
      "spend": 650.00
    }
  ],
  "meta": {
    "date_from": "2026-03-20",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Response fields

| Field          | Type   | Description                                          |
| -------------- | ------ | ---------------------------------------------------- |
| `date`         | string | Calendar date (`YYYY-MM-DD`).                        |
| `visitors`     | number | Unique visitors (distinct `anonymous_id`) that day.  |
| `pageviews`    | number | Total page views recorded.                           |
| `bounces`      | number | Sessions with a single page view.                    |
| `cash_revenue` | number | Attributed cash revenue (Stripe / MercadoPago) in your reporting currency. |
| `spend`        | number | Total ad spend across all connected platforms.        |

<Callout type="info" title="Revenue is cash only">
  `cash_revenue` includes confirmed payments from Stripe and MercadoPago only.
  GHL pipeline values (deal stages, closed-won projections) are excluded because
  they are not confirmed cash. See [Conversions & Revenue](/docs/api/conversions) for
  the full breakdown.
</Callout>

## Related

<Cards>
  <Card title="Authentication" href="/docs/api/authentication">
    API key setup and scopes
  </Card>
  <Card title="Dashboard" href="/docs/features/dashboard">
    The main chart uses the same data this endpoint returns
  </Card>
</Cards>

---

# Visitors & Realtime

Four endpoints for visitor data, realtime presence, search keywords, and attribution quality metrics.

---

## Visitor List

```http title="Endpoint"
GET /api/v1/visitors
```

Paginated list of all visitors (identified and anonymous). Returns PII fields like name and email.

**Scope:** `visitors:read` (must be explicitly granted)

<Callout type="error" title="PII access required">
This endpoint returns personally identifiable information (name, email). Your API key must have the `visitors:read` scope explicitly granted. Keys with only `analytics:read` will receive a `403 Forbidden` response.
</Callout>

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `date_from` | string | Yes | Start date (YYYY-MM-DD) |
| `date_to` | string | Yes | End date (YYYY-MM-DD) |
| `search` | string | No | Search by name or email |
| `limit` | number | No | Results per page (default 10, max 100) |
| `cursor_time` | string | No | Pagination cursor (ISO timestamp) |
| `cursor_id` | string | No | Pagination cursor ID |

### Request

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/visitors?date_from=2026-03-01&date_to=2026-03-25&limit=5"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/visitors?date_from=2026-03-01&date_to=2026-03-25&limit=5",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/visitors",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "limit": 5,
    },
)
data = resp.json()
```
</Tab>
</Tabs>

### Response

```json title="Success response (200 OK)"
{
  "data": [
    {
      "visitor_id": "uuid",
      "customer_profile_id": "uuid",
      "name": "John Doe",
      "email": "john@example.com",
      "country": "US",
      "device": "desktop",
      "browser": "Chrome",
      "os": "macOS",
      "channel": "Organic Search",
      "source": "google",
      "total_revenue": 450.00,
      "last_seen_at": "2026-03-25T08:30:00Z",
      "session_count": 5,
      "total_pageviews": 18,
      "touch_channels": ["Organic Search", "Direct", "Paid Social"]
    }
  ],
  "pagination": {
    "has_next": true,
    "cursor": "2026-03-25T08:30:00Z|uuid"
  },
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Response fields

| Field | Type | Description |
|-------|------|-------------|
| `visitor_id` | string | Unique visitor identifier (anonymous_id) |
| `customer_profile_id` | string \| null | Linked customer profile (null for anonymous visitors) |
| `name` | string | Display name or deterministic anonymous name (e.g. "Teal Falcon") |
| `email` | string \| null | Email address if identified |
| `country` | string | Two-letter country code |
| `device` | string | Device type: `desktop`, `mobile`, `tablet` |
| `browser` | string | Browser name |
| `os` | string | Operating system |
| `channel` | string | Classified traffic channel (see [Channels](/docs/concepts/channels)) |
| `source` | string | Traffic source |
| `total_revenue` | number | Total attributed cash revenue |
| `last_seen_at` | string | ISO timestamp of last activity |
| `session_count` | number | Total sessions in the date range |
| `total_pageviews` | number | Total page views in the date range |
| `touch_channels` | string[] | All channels this visitor has interacted through |

---

## Realtime

```http title="Endpoint"
GET /api/v1/realtime
```

Returns the number of visitors currently on your site (active in the last 5 minutes).

**Scope:** `realtime:read`

<Callout type="info" title="No date parameters">
This endpoint returns a live snapshot. No `date_from` or `date_to` parameters are needed.
</Callout>

### Request

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/realtime"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/realtime",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/realtime",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
)
data = resp.json()
```
</Tab>
</Tabs>

### Response

```json title="Success response (200 OK)"
{
  "data": {
    "visitors_online": 24
  },
  "meta": {
    "profile_id": "uuid"
  }
}
```

---

## Keywords

```http title="Endpoint"
GET /api/v1/keywords
```

Returns Google Search Console keyword performance data. Requires an active GSC integration.

**Scope:** `analytics:read`

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `date_from` | string | Yes | Start date (YYYY-MM-DD) |
| `date_to` | string | Yes | End date (YYYY-MM-DD) |
| `limit` | number | No | Max results (default 50, max 100) |

### Request

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/keywords?date_from=2026-03-01&date_to=2026-03-25&limit=10"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/keywords?date_from=2026-03-01&date_to=2026-03-25&limit=10",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/keywords",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "limit": 10,
    },
)
data = resp.json()
```
</Tab>
</Tabs>

### Response

```json title="Success response (200 OK)"
{
  "data": [
    {
      "keyword": "marketing attribution tool",
      "impressions": 2400,
      "clicks": 180,
      "ctr": 7.5,
      "avg_position": 4.2
    },
    {
      "keyword": "roas calculator",
      "impressions": 1800,
      "clicks": 95,
      "ctr": 5.3,
      "avg_position": 6.8
    }
  ],
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Response fields

| Field | Type | Description |
|-------|------|-------------|
| `keyword` | string | Search query term |
| `impressions` | number | Times your site appeared in search results |
| `clicks` | number | Times users clicked through to your site |
| `ctr` | number | Click-through rate (clicks / impressions x 100) |
| `avg_position` | number | Average ranking position in search results |

---

## Quality

```http title="Endpoint"
GET /api/v1/quality
```

Returns attribution data quality metrics -- how well your tracking captures marketing source data.

**Scope:** `analytics:read`

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `date_from` | string | Yes | Start date (YYYY-MM-DD) |
| `date_to` | string | Yes | End date (YYYY-MM-DD) |

### Request

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/quality?date_from=2026-03-01&date_to=2026-03-25"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/quality?date_from=2026-03-01&date_to=2026-03-25",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/quality",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
    },
)
data = resp.json()
```
</Tab>
</Tabs>

### Response

```json title="Success response (200 OK)"
{
  "data": {
    "total_events": 1450,
    "with_full_utms": 890,
    "with_fbclid_only": 320,
    "with_no_tracking": 240,
    "coverage_percent": 83.45
  },
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Response fields

| Field | Type | Description |
|-------|------|-------------|
| `total_events` | number | Total tracked events in the period |
| `with_full_utms` | number | Events with complete UTM parameters |
| `with_fbclid_only` | number | Events with only a click ID (fbclid/gclid) but no UTMs |
| `with_no_tracking` | number | Events with no marketing source data at all |
| `coverage_percent` | number | Percentage of events that have at least some attribution data |

<Callout type="info" title="Improving coverage">
A low `coverage_percent` usually means UTM parameters are missing from your ad URLs. Ensure all campaigns include `utm_source`, `utm_medium`, and `utm_campaign` parameters. Click IDs (`fbclid`, `gclid`) are added automatically by ad platforms.
</Callout>

## Related

<Cards>
  <Card title="Authentication" href="/docs/api/authentication">
    API key setup, scopes (visitors:read requires explicit grant)
  </Card>
  <Card title="Data Quality" href="/docs/features/data-quality">
    Monitor and improve your tracking coverage
  </Card>
  <Card title="Customer Journeys" href="/docs/features/customer-journey">
    The dashboard equivalent of the journey endpoint
  </Card>
</Cards>

---

# Attribution Models

Atribu supports 8 attribution models that determine how conversion credit is distributed across marketing touchpoints. Each model answers a different question about your customer journey.

<VideoEmbed src="/docs/videos/how-attribution-works.mp4" title="How attribution works — all 8 models explained" />

## How attribution works

```mermaid
flowchart LR
    A["Ad Click"] --> B["Page Views"]
    B --> C["Form Fill"]
    C --> D["identify()"]
    D --> E["Customer Profile"]
    E --> F["Payment"]
    F --> G["Attribution Engine"]
    G --> H["Credit to Campaign"]
```

## Models at a glance

<Cards>
<Card title="Last Touch" href="#last-touch">
All credit to the final touchpoint before conversion
</Card>
<Card title="First Touch" href="#first-touch">
All credit to the first touchpoint that started the journey
</Card>
<Card title="Split 50/50" href="#split-5050">
Equal split between first and last touchpoints
</Card>
<Card title="Linear" href="#linear">
Equal credit across every touchpoint in the path
</Card>
<Card title="Position-Based (U-Shaped)" href="#position-based-u-shaped">
40% first, 40% last, 20% split among middle touches
</Card>
<Card title="Time Decay" href="#time-decay">
More credit to touches closer to conversion
</Card>
<Card title="Last Non-Direct" href="#last-non-direct">
Ignores Direct visits to find the real driver
</Card>
<Card title="Custom Weighted" href="#custom-weighted">
Configurable weights per touchpoint position
</Card>
</Cards>

---

## Last Touch

All credit goes to the **last touchpoint** before conversion.

**Best for:** Understanding what closes deals and drives the final decision.

**API value:** `last_touch`

```
Ad Click -> Email -> Blog Visit -> [Purchase]
  0%          0%        100%
```

---

## First Touch

All credit goes to the **first touchpoint** that started the customer journey.

**Best for:** Understanding what drives initial awareness and brings people in.

**API value:** `first_touch`

```
Ad Click -> Email -> Blog Visit -> [Purchase]
  100%        0%        0%
```

---

## Split 50/50

Credit split equally between the **first** and **last** touchpoints. Middle touches receive no credit.

**Best for:** Balancing awareness and closing without overcomplicating analysis.

**API value:** `split_50_50`

```
Ad Click -> Email -> Blog Visit -> [Purchase]
  50%          0%        50%
```

---

## Linear

Credit distributed **equally** across all touchpoints in the conversion path.

**Best for:** Fair representation of every step in the customer journey.

**API value:** `linear`

```
Ad Click -> Email -> Blog Visit -> [Purchase]
  33.3%      33.3%     33.3%
```

---

## Position-Based (U-Shaped)

**40%** to the first touch, **40%** to the last touch, and the remaining **20%** split equally among middle touches.

**Best for:** Emphasizing discovery and closing while still acknowledging the nurture phase.

**API value:** `position_based`

```
Ad Click -> Email -> Blog Visit -> [Purchase]
  40%         20%       40%
```

---

## Time Decay

More credit to touchpoints **closer to the conversion**. Uses exponential decay from first to last touch.

**Best for:** Short sales cycles where recent interactions matter most.

**API value:** `time_decay`

```
Ad Click -> Email -> Blog Visit -> [Purchase]
  10%         30%       60%
```

---

## Last Non-Direct

All credit to the last touchpoint that **isn't "Direct" traffic**. If a customer clicks an ad, then later types your URL directly, the ad gets credit.

**Best for:** Removing noise from direct visits to identify the real marketing driver.

**API value:** `last_non_direct`

```
Ad Click -> Email -> Direct Visit -> [Purchase]
  0%          100%       0% (ignored)
```

---

## Custom Weighted

Custom weights per touchpoint position. Configured in your profile's attribution settings.

**Best for:** Businesses with a well-understood sales process that want full control.

**API value:** `custom_weighted`

---

## Using models in the API

Pass the `model` parameter to any endpoint that supports attribution:

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/overview?date_from=2026-03-01&date_to=2026-03-25&model=position_based"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/overview?date_from=2026-03-01&date_to=2026-03-25&model=position_based",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/overview",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "model": "position_based",
    },
)
data = resp.json()
```
</Tab>
</Tabs>

<Callout type="info" title="Valid model values">
`last_touch` (default) `first_touch` `split_50_50` `linear` `position_based` `time_decay` `last_non_direct` `custom_weighted`
</Callout>

---

## Attribution windows

Attribution windows define how far back in time a touchpoint can receive credit for a conversion. These are configurable per profile.

| Window | Default | Description |
|--------|---------|-------------|
| **Click window** | 30 days | How long after a click can a conversion be attributed |
| **View-through window** | 24 hours | How long after an ad impression (no click) |
| **First-touch window** | 90 days | Maximum lookback for first-touch attribution |

<Callout type="info" title="Window behavior">
If a customer clicks an ad on March 1 and converts on March 28, the click is within the default 30-day window and receives credit. If they convert on April 5 (35 days later), the click falls outside the window and is excluded from attribution.
</Callout>

---

## Revenue and ROAS

<Callout type="warn" title="ROAS uses cash revenue only">
Only `revenue_type = 'cash'` (confirmed Stripe and MercadoPago payments) is used for ROAS calculations. GHL pipeline values (closed_won, appointment_booked) are tracked separately as **pipeline value** -- they are projections, not confirmed revenue. See [Revenue Types](/docs/concepts/revenue-types) for details.
</Callout>

The `revenue` and `roas` fields in API responses always reflect attributed cash revenue. Pipeline values appear in conversion counts but are never included in ROAS math.

## Related

<Cards>
  <Card title="Attribution Windows" href="/docs/settings/attribution-windows">
    Configure click, view-through, and first-touch lookback windows
  </Card>
  <Card title="Ads Performance" href="/docs/features/ads-performance">
    See how different models change campaign rankings
  </Card>
  <Card title="Customer Journeys" href="/docs/features/customer-journey">
    View the actual touchpoint paths that models evaluate
  </Card>
</Cards>

---

# Channel Classification

Atribu automatically classifies every visit into a marketing channel based on UTM parameters, referrer URL, and click IDs. This classification drives the Traffic Sources breakdown in your dashboard and the channel dimension in API responses.

---

## Default channels

<Cards>
<Card title="Paid Social">
Meta, TikTok, and other social ads detected by click IDs or paid UTM parameters
</Card>
<Card title="Organic Social">
Unpaid traffic from social platforms like Facebook, Instagram, X, LinkedIn
</Card>
<Card title="Paid Search">
Google Ads, Microsoft Ads, and other search ads detected by click IDs or paid UTMs
</Card>
<Card title="Organic Search">
Unpaid search engine traffic from Google, Bing, Yahoo, DuckDuckGo
</Card>
<Card title="Email">
Traffic from email campaigns identified by utm_medium
</Card>
<Card title="Referral">
Inbound traffic from other websites (not search or social)
</Card>
<Card title="Direct">
No referrer, no UTMs, no click IDs -- typed URL or bookmark
</Card>
<Card title="Display">
Banner and display ad traffic identified by utm_medium
</Card>
<Card title="Affiliate">
Affiliate partner traffic identified by utm_medium
</Card>
</Cards>

---

## Classification rules

| Channel | Detection logic |
|---------|----------------|
| **Paid Social** | `fbclid` or `ttclid` present, OR `utm_medium` contains `paid`, `cpc`, or `ppc` with a social source |
| **Organic Social** | Referrer from a social platform (facebook.com, instagram.com, twitter.com, linkedin.com, etc.) without paid indicators |
| **Paid Search** | `gclid` or `msclkid` present, OR `utm_medium` is `cpc`/`ppc` with a search engine source |
| **Organic Search** | Referrer from a search engine (google, bing, yahoo, duckduckgo, baidu, etc.) |
| **Email** | `utm_medium` is `email` |
| **Referral** | Has a referrer that is not a known search engine or social platform |
| **Direct** | No referrer, no UTM parameters, no click IDs |
| **Display** | `utm_medium` is `display`, `banner`, or `cpm` |
| **Affiliate** | `utm_medium` is `affiliate` |

<Callout type="info" title="Classification priority">
Click IDs take precedence over UTM parameters. If a visit has `fbclid` in the URL, it is classified as **Paid Social** regardless of what `utm_medium` says. This prevents misclassification when UTM tags are missing or incorrect.
</Callout>

---

## Click ID detection

Click IDs are automatically appended to URLs by ad platforms. Atribu detects them to identify paid traffic even when UTM parameters are missing.

| Click ID | Platform | Channel |
|----------|----------|---------|
| `fbclid` | Meta (Facebook / Instagram) | Paid Social |
| `gclid` | Google Ads | Paid Search |
| `msclkid` | Microsoft Advertising (Bing) | Paid Search |
| `ttclid` | TikTok Ads | Paid Social |

<Callout type="warn" title="Always use UTMs alongside click IDs">
Click IDs identify the platform but not the specific campaign. For full attribution down to the ad level, always include `utm_campaign`, `utm_source`, `utm_medium`, and `utm_content` in your ad URLs.
</Callout>

---

## Using channels in the API

### Channel breakdown

Get visitor and revenue distribution across all channels:

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/breakdowns?date_from=2026-03-01&date_to=2026-03-25&dimensions=channel"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/breakdowns?date_from=2026-03-01&date_to=2026-03-25&dimensions=channel",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/breakdowns",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "dimensions": "channel",
    },
)
data = resp.json()
```
</Tab>
</Tabs>

```json title="Response"
{
  "data": {
    "channel": [
      { "value": "Paid Social", "visitors": 3200, "sessions": 4100, "pageviews": 9800, "bounce_rate": 38.2 },
      { "value": "Organic Search", "visitors": 2800, "sessions": 3400, "pageviews": 8200, "bounce_rate": 45.1 },
      { "value": "Direct", "visitors": 1500, "sessions": 1800, "pageviews": 4200, "bounce_rate": 52.3 }
    ]
  }
}
```

### Filtering by channel

Filter any endpoint to a specific channel using the `filter[channel]` parameter:

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/overview?date_from=2026-03-01&date_to=2026-03-25&filter[channel]=is:Paid+Social"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/overview?" + new URLSearchParams({
    date_from: "2026-03-01",
    date_to: "2026-03-25",
    "filter[channel]": "is:Paid Social",
  }),
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/overview",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "filter[channel]": "is:Paid Social",
    },
)
data = resp.json()
```
</Tab>
</Tabs>

<Callout type="info" title="Filter syntax">
Channel filters use the `is:` prefix followed by the exact channel name. Channel names are case-sensitive. Use `+` or `%20` for spaces in URLs.
</Callout>

## Related

<Cards>
  <Card title="UTM Parameters" href="/docs/tracking/utm-parameters">
    Tag your links for accurate channel classification
  </Card>
  <Card title="Traffic Sources" href="/docs/features/traffic-sources">
    See channel data in your dashboard breakdown boards
  </Card>
</Cards>

---

Identity resolution is how Atribu connects the dots between an anonymous website visitor and a known customer who pays you.

<VideoEmbed src="/docs/videos/identity-resolution.mp4" title="Identity resolution — from anonymous visitor to known customer" />

```mermaid
flowchart TD
    A["Anonymous Visitor\n(anonymous_id)"] -->|fills form| B["identify(email)"]
    B --> C["Customer Profile"]
    D["Stripe Payment\n(same email)"] --> C
    E["GHL Contact\n(same email)"] --> C
    F["GHL Opportunity\n(same contact)"] --> C
    C --> G["All events linked\nto one person"]
```

## The problem

Your customer's journey is fragmented:

1. They click a Facebook ad on their phone
2. They visit your site, browse pricing, and leave
3. Two days later, they search your brand name on Google from their laptop
4. They fill out your contact form
5. A week later, they pay via Stripe

Without identity resolution, these look like 5 separate events from 3 different people. With it, Atribu knows it's one person — and attributes the payment back to the original Facebook ad.

## How it works

<Steps>
<Step>

### Anonymous visitor arrives

When someone visits your site, Atribu assigns them a **Visitor ID** (called `anonymous_id`). This ID is stored in their browser and stays the same across all their visits on that device.

</Step>
<Step>

### Visitor identifies themselves

When the visitor fills out a form, books an appointment, or logs in, Atribu's `identify()` function fires with their email (or phone). This creates a **customer profile** that links their Visitor ID to their real identity.

</Step>
<Step>

### Server-side events match

When a payment arrives from Stripe with the same email, or a deal is created in GoHighLevel for the same contact, Atribu matches it to the existing customer profile. The full journey — from ad click to payment — is now connected.

</Step>
</Steps>

## The identity graph

Atribu builds an **identity graph** for each customer:

| Identifier Type | Example | Source |
|----------------|---------|--------|
| Email | jane@example.com | Form fill, Stripe payment |
| Phone | +1-555-0123 | Form fill, GHL contact |
| Visitor ID | `abc123-def456` | Browser cookie |
| Stripe Customer ID | `cus_abc123` | Stripe payment |
| GHL Contact ID | `contact_xyz` | GoHighLevel sync |

All identifiers point to the same customer profile. Any future event with any of these identifiers gets linked to the same person.

## Identifier priority

When resolving who a person is, Atribu uses this priority:

1. **Email** (strongest match)
2. **Phone number**
3. **External IDs** (Stripe customer ID, GHL contact ID)

Names are used for display only — never for matching (too many "John Smith"s).

<Callout type="info" title="Safe to call repeatedly">
The identity resolution system uses "COALESCE" logic — calling `identify()` multiple times with new data backfills missing fields without overwriting existing ones. If a customer first gives their email, then later their phone, both are stored.
</Callout>

## Why `identify()` is critical

<Callout type="error" title="Without identify(), attribution breaks">
If a visitor never identifies themselves (no form fill, no booking, no login), their ad click and page views are tracked — but they can never be connected to a Stripe payment or CRM event. The payment shows up as "unattributed."
</Callout>

Make sure every conversion point on your site triggers identification:

- **Forms**: Atribu auto-captures these (no code needed)
- **Booking widgets**: GHL, Calendly, Cal.com auto-captured
- **Custom flows**: Call `window.atribuTracker.identify({ email: "..." })` manually

See [Identify Users](/docs/tracking/identify-users) for implementation details.

## Related

<Cards>
  <Card title="Auto-Capture" href="/docs/tracking/auto-capture">
    How forms and booking widgets automatically trigger identification
  </Card>
  <Card title="Data Quality" href="/docs/features/data-quality">
    Monitor identity coverage and repair missing data
  </Card>
  <Card title="Customer Journeys" href="/docs/features/customer-journey">
    See the full path from anonymous visitor to identified customer
  </Card>
</Cards>

---

# Revenue Types

Atribu tracks three distinct types of monetary value. Each serves a different purpose, and mixing them produces meaningless metrics.

```mermaid
flowchart TD
    A["Stripe / MercadoPago\nPayment"] -->|"revenue_type = cash"| B["Cash Revenue"]
    C["GHL Pipeline\nDeal Value"] -->|"revenue_type = pipeline"| D["Pipeline Value"]
    E["Shopify Order"] -->|"revenue_type = gross"| F["Gross Value"]
    B --> G["ROAS = Cash / Spend"]
    D -.->|"NOT included"| G
    F -.->|"NOT included"| G
```

---

## The three types

<Cards>
<Card title="Cash">
Confirmed payments from Stripe and MercadoPago. The only value used for ROAS.
</Card>
<Card title="Pipeline">
Deal values from GoHighLevel opportunities. Projections, not confirmed revenue.
</Card>
<Card title="Gross">
Order values from e-commerce platforms. May include refunds or cancellations.
</Card>
</Cards>

---

## Cash (for ROAS)

`revenue_type = 'cash'`

Real payments confirmed by Stripe or MercadoPago. This is the **only** value used for ROAS calculations.

| Property | Detail |
|----------|--------|
| **Source** | `payment_received` events from Stripe/MercadoPago webhooks |
| **Reliability** | 100% -- these are actual bank transactions |
| **API field** | `revenue` in `/api/v1/overview` and `/api/v1/campaigns` |
| **Used in ROAS** | Yes |

---

## Pipeline

`revenue_type = 'pipeline'`

Deal values from GoHighLevel opportunities (lead_created, appointment_booked, closed_won). These are **projections**, not confirmed cash.

| Property | Detail |
|----------|--------|
| **Source** | GHL opportunity sync |
| **Reliability** | Variable -- deals can be lost, renegotiated, or never close |
| **API field** | Visible in conversion counts, excluded from `revenue` and `roas` |
| **Used in ROAS** | No |

---

## Gross

`revenue_type = 'gross'`

Order values from e-commerce platforms (e.g., order_placed events). May include orders that are later refunded, cancelled, or never fulfilled.

| Property | Detail |
|----------|--------|
| **Source** | E-commerce platforms (Shopify, etc.) |
| **Reliability** | Medium -- represents intent to pay, not confirmed collection |
| **API field** | Tracked separately from cash revenue |
| **Used in ROAS** | No |

---

## Why this matters

<Callout type="error" title="Never mix pipeline values into ROAS">
If pipeline values were included in ROAS, a single $50,000 deal projection could make a $500 ad campaign show **100x ROAS** -- completely misleading. That deal might never close, get renegotiated to $5,000, or be lost entirely. Only confirmed cash payments produce trustworthy ROAS numbers.
</Callout>

Consider this example:

| Metric | With cash only | With pipeline included |
|--------|---------------|----------------------|
| Ad spend | $2,000 | $2,000 |
| Revenue | $6,000 (3 payments) | $56,000 (3 payments + 1 deal) |
| ROAS | **3.0x** | **28.0x** |
| Accurate? | Yes | No -- $50K deal hasn't closed |

---

## How it appears in the API

### Overview endpoint

The `revenue` and `roas` fields in `/api/v1/overview` always use **cash only**:

```json title="Overview response (revenue = cash only)"
{
  "data": {
    "current": {
      "spend": 4250.00,
      "revenue": 12800.00,
      "roas": 3.01,
      "cash_revenue": 12800.00,
      "cash_payments": 48
    }
  }
}
```

### Campaigns endpoint

Campaign-level `outcome_value` and `roas` also reflect cash revenue only:

```json title="Campaign response"
{
  "data": [
    {
      "campaign_name": "Spring Sale - Conversions",
      "spend": 1800.00,
      "outcome_count": 45,
      "outcome_value": 6200.00,
      "roas": 3.44
    }
  ]
}
```

<Callout type="info" title="Conversion counts include all types">
The `outcome_count` field includes conversions of **all** revenue types (cash, pipeline, and gross). This gives you visibility into total conversion volume. Only the monetary fields (`revenue`, `outcome_value`, `roas`) are restricted to cash.
</Callout>

### Conversions endpoint

The `/api/v1/conversions` endpoint returns individual conversion events with their `revenue_type`, so you can filter and analyze each type independently:

```json title="Conversion with revenue_type"
{
  "conversion_type": "payment_received",
  "revenue_type": "cash",
  "value": 450.00,
  "customer_name": "John Doe"
}
```

```json title="Pipeline conversion"
{
  "conversion_type": "closed_won",
  "revenue_type": "pipeline",
  "value": 15000.00,
  "customer_name": "Acme Corp"
}
```

## Related

<Cards>
  <Card title="Stripe Integration" href="/docs/integrations/stripe">
    How cash payments are tracked from Stripe
  </Card>
  <Card title="GoHighLevel Integration" href="/docs/integrations/gohighlevel">
    How pipeline values are synced from GHL deals
  </Card>
  <Card title="Conversion Goals" href="/docs/settings/goals">
    Configure which events count as conversions and their revenue type
  </Card>
</Cards>

---

Not every lead visits your website. Some conversions happen entirely off-site — and Atribu still attributes them.

```mermaid
flowchart TD
    A["Meta Lead Form\n(no website visit)"] --> B["GHL Contact Created"]
    B --> C["Outcome Event\n(has campaign data)"]
    C --> D{"Web touches\nexist?"}
    D -->|No| E["Create Synthetic Touch"]
    D -->|Yes| F["Use existing touches"]
    E --> G["Attribution Engine"]
    F --> G
    G --> H["Credit assigned\nto campaign"]
```

## The problem

When someone fills out a **Meta lead form** (a form shown directly on Facebook or Instagram, without visiting your website), the lead goes straight to GoHighLevel. There's no website visit, no page view, no tracker event. But the lead DID come from a specific ad campaign.

Without synthetic touches, these leads would show up as "unattributed" — even though Meta knows exactly which campaign drove them.

<Callout type="warn" title="This affects ~60% of GHL leads">
For businesses using Meta lead ads with GoHighLevel, most leads arrive via native forms. Without synthetic touches, more than half of your leads can't be attributed to their campaigns.
</Callout>

## How synthetic touches work

When Atribu detects a conversion that has campaign data (from GHL's attribution) but no website visit, it creates a **synthetic touch** — an artificial touchpoint that represents the off-site interaction.

<Steps>
<Step>

### Lead fills out Meta form

A potential customer sees your Instagram ad and fills out the lead form directly on Instagram. They never visit your website.

</Step>
<Step>

### GHL receives the lead

GoHighLevel creates a contact and opportunity. The opportunity includes attribution data from Meta: campaign ID, ad set ID, ad ID.

</Step>
<Step>

### Atribu syncs the data

Atribu syncs the GHL opportunity and creates a conversion event. It sees the campaign data but finds no matching web touches.

</Step>
<Step>

### Synthetic touch created

Atribu creates a synthetic touch with the campaign data from GHL. This allows the attribution engine to assign credit to the correct campaign.

</Step>
</Steps>

## Rules

Synthetic touches follow strict rules to avoid double-counting:

- **Only created when there are no existing web touches** for that customer before the conversion
- **Idempotent**: running the process again doesn't create duplicates
- **No session data**: synthetic touches have no session, page views, or device info (the customer never visited the site)
- **Dashboard metrics unaffected**: visitor, session, and pageview counts come from actual web data, not synthetic touches

## Where they appear

In the customer journey timeline, synthetic touches are marked with `is_synthetic: true`. They show the campaign that drove the lead but don't have page/URL data.

<Callout type="info" title="Visibility in the dashboard">
Synthetic touches affect attribution data (ROAS, campaign revenue) but NOT traffic metrics (visitors, sessions, pageviews). Your visitor counts remain accurate — they only count real website visits.
</Callout>

## Impact on attribution

Without synthetic touches, a business running Meta lead ads might see:
- 100 leads in GHL
- Only 40 attributed to campaigns (the ones who visited the website)
- 60 "unattributed" leads

With synthetic touches:
- 100 leads in GHL
- 95+ attributed to campaigns
- Accurate ROAS showing the true return on your Meta ad spend

## Related

<Cards>
  <Card title="GoHighLevel Integration" href="/docs/integrations/gohighlevel">
    How GHL leads carry campaign data for synthetic touches
  </Card>
  <Card title="Identity Resolution" href="/docs/concepts/identity-resolution">
    How customer profiles are created for off-site leads
  </Card>
  <Card title="Data Quality" href="/docs/features/data-quality">
    Monitor attribution coverage to see the impact of synthetic touches
  </Card>
</Cards>

---

Ads Explorer lets you monitor the Meta Ad Library for ads from any competitor. See what creatives they are running, how long each ad has been active, and use AI to break down what makes their ads effective.

<VideoEmbed src="/docs/videos/ads-explorer.mp4" title="Ads Explorer — competitive intelligence" />

## What it does

Atribu scans the Meta Ad Library for ads from competitors you specify. It discovers new ads, tracks how long they run, and stores everything so you can browse and analyze the creative at any time.

## Adding competitors

<Steps>
<Step>

### Go to Ads Explorer

Navigate to **Ads Explorer** in the sidebar, then click the **Sources** tab.

</Step>
<Step>

### Click Add Source

Click the **Add Source** button to open the source configuration form.

</Step>
<Step>

### Enter the competitor's page

Provide the competitor's Facebook page name or URL. Atribu uses the advertiser name to search the Meta Ad Library.

</Step>
<Step>

### Select target countries

Choose which countries to scan for ads. Ads are often geo-targeted, so selecting the right countries ensures you see the relevant creatives.

</Step>
<Step>

### Start scanning

Save the source and Atribu begins scanning for their ads. New ads are discovered automatically on an ongoing basis.

</Step>
</Steps>

## Browsing discovered ads

The main Ads Explorer view shows a grid of cards for every discovered ad. Each card shows:

- **Thumbnail** — a preview of the ad creative (image or video frame)
- **Advertiser name** — who is running the ad
- **Platforms** — Facebook, Instagram, or both
- **Status** — whether the ad is currently active or has stopped running
- **Days running** — how long the ad has been live
- **Media type** — image or video

You can filter by source, status (active/inactive), media type, and sort by newest, oldest, or AI score.

<Callout type="info" title="Long-running ads are usually winners">
If a competitor has been running an ad for 90+ days, it is probably profitable. Advertisers do not keep spending money on ads that do not work. Study what makes long-running ads effective — the hook, the offer, the creative format.
</Callout>

## Ad detail view

Click any ad card to open the detail sheet. This shows:

- **Full creative preview** — the complete image or video
- **Primary caption** — the ad copy text
- **Landing page URL** — where the ad sends people
- **Call-to-action** — the CTA button text (Shop Now, Learn More, etc.)
- **Platforms** — which platforms the ad appears on (Facebook, Instagram)
- **Countries** — all countries where the ad runs
- **First and last shown dates** — when the ad was first detected and most recently seen

## AI creative analysis

Atribu can analyze any discovered ad using AI to extract structured insights:

- **Hook** — what grabs attention in the first 3 seconds (for video) or at first glance (for images)
- **CTA** — the call to action and how compelling it is
- **Offer** — what is being promoted and how it is framed
- **Script** — for video ads, the full spoken text extracted and transcribed
- **Score** — an overall quality score with explanation
- **Improvement suggestions** — specific recommendations for how the creative could be stronger

To run the analysis, click the **Enrich** button on any ad. The AI processes the creative and results appear in the detail view.

## Send to Ads Lab

Found a competitor ad you want to learn from? Click **Send to Ads Lab** to use it as inspiration for generating your own ad variations. The ad's creative analysis is passed to the AI pipeline so it can study what works and create something original for your brand.

<Callout type="info" title="Competitive intelligence, not copying">
The goal is not to copy competitor ads. It is to understand what patterns, hooks, and offers resonate in your market, then create your own original creative informed by those insights.
</Callout>

## Related

<Cards>
  <Card title="Ads Lab" href="/docs/features/ads-lab">
    Generate your own ad creative using insights from competitor analysis
  </Card>
  <Card title="Ads Performance" href="/docs/features/ads-performance">
    Compare your own campaign performance alongside competitor insights
  </Card>
</Cards>

---

Ads Lab is an AI-powered creative workspace that generates ad copy and video scripts using data from your actual ad performance. Instead of generic templates, every suggestion is backed by evidence from your best-performing campaigns.

<VideoEmbed src="/docs/videos/ads-lab-pipeline.mp4" title="Ads Lab — AI-powered ad creation pipeline" />

## The guided workflow

Ads Lab walks you through a structured process to generate high-quality ad creative.

<Steps>
<Step>

### Select top performers

Atribu recommends your best ads ranked by ROAS. Select which ones the AI should learn from. The more winning ads you select, the more patterns the AI can identify.

</Step>
<Step>

### Choose your strategy

Decide the creative direction:

- **Top performer remix** — new variations based on what already works
- **Competitor-inspired** — learn from competitor ads discovered in Ads Explorer
- **New angle** — explore fresh approaches informed by your data

</Step>
<Step>

### Configure the generation

Set your preferences:

- **Platforms** — which platforms the ads will run on (Facebook, Instagram, etc.)
- **Must-include keywords** — terms or phrases that must appear in the copy
- **Must-exclude keywords** — terms to avoid
- **Additional context** — any brand guidelines, tone requirements, or specific instructions

</Step>
<Step>

### AI pipeline generates your creative

Seven specialized AI agents work in sequence to produce your ads:

| Agent | What it does |
|-------|-------------|
| **Research** | Analyzes patterns from your winning ads — what hooks, CTAs, and formats perform best |
| **Psychology** | Identifies persuasion tactics and cognitive biases that resonate with your audience |
| **Copywriting** | Creates 3-4 ad copy variations with headlines, body text, and CTAs |
| **Video Script** | Writes structured video scripts with scene-by-scene hooks and dialogue |
| **Compliance** | Checks character limits and platform-specific rules, rewrites if needed |
| **Critic** | Scores everything on 5 quality dimensions and requests rewrites if the score is below threshold |

If the Critic finds quality issues, the Copywriting agent automatically revises and resubmits.

</Step>
<Step>

### Review and approve

Each generated draft appears for your review. For every draft you can:

- **Approve** — mark it as ready to use
- **Edit** — modify the text directly
- **Discard** — remove it from the batch
- **Regenerate** — ask the AI to create a new version

</Step>
</Steps>

## Freeform chat

Beyond the guided workflow, you can talk to the AI in natural language to:

- Refine a specific draft ("make the headline shorter and punchier")
- Request changes to the tone or angle
- Ask for entirely new variations
- Discard or approve multiple drafts at once ("discard the rest of the drafts")

The chat understands your session context — it knows which step you are on, what drafts exist, and what your brief says.

## Session management

Each Ads Lab conversation is saved as a session. You can:

- Start new sessions for different campaigns or briefs
- Switch between existing sessions from the history panel
- Each session preserves all messages, drafts, and decisions

<Callout type="info" title="Evidence-based generation">
Every suggestion from Ads Lab is backed by data from your actual ad performance — not generic marketing advice. The Research agent identifies specific patterns from your winning ads (hook styles, CTA formats, offer structures) and the Copywriting agent uses those patterns as building blocks.
</Callout>

## Cost tracking

Each AI pipeline run tracks token usage and cost. The pipeline has a configurable cost cap to prevent runaway spending. You can see the total cost of each generation in the session.

## Related

<Cards>
  <Card title="Ads Explorer" href="/docs/features/ads-explorer">
    Discover competitor ads to use as inspiration in Ads Lab
  </Card>
  <Card title="Ads Performance" href="/docs/features/ads-performance">
    See how your campaigns perform to select top performers for the AI
  </Card>
</Cards>

---

The Ads page shows every campaign, ad set, and ad from your connected ad platforms, ranked by performance. This is where you find out which ads are making money and which are wasting budget.

<VideoEmbed src="/docs/videos/ads-performance.mp4" title="Ads Performance — rank campaigns by what matters" />

## Ad hierarchy

Ad platforms organize ads in a three-level hierarchy. Use the level toggle at the top of the page to switch between them:

- **Campaign** — the top level. Groups ad sets under a shared objective and budget.
- **Ad Set** — the middle level. Controls targeting, placement, and schedule.
- **Ad** — the individual creative. The image, video, or text that people actually see.

Each level shows aggregated metrics. A campaign's spend is the sum of all its ad sets; an ad set's clicks are the sum of all its ads.

## Key metrics explained

| Metric | What it means | Formula |
|--------|--------------|---------|
| **Spend** | Total money spent on this campaign, ad set, or ad. | Sum of daily spend |
| **Impressions** | How many times your ad was shown to people. | Sum of daily impressions |
| **Clicks** | How many people clicked your ad to visit your site. | Sum of daily clicks |
| **CTR** | Click-Through Rate — what percentage of people who saw your ad clicked it. Higher is better. | Clicks / Impressions x 100 |
| **CPM** | Cost Per Mille — how much it costs to show your ad 1,000 times. | Spend / Impressions x 1,000 |
| **CPC** | Cost Per Click — how much each click costs you. | Spend / Clicks |
| **Reach** | Number of unique people who saw your ad (one person counted once). | Sum of daily reach |
| **Frequency** | Average number of times each person saw your ad. | Impressions / Reach |
| **ROAS** | Return on Ad Spend — how much revenue each dollar of ad spend generates. | Revenue / Spend |
| **CAC** | Customer Acquisition Cost — how much it costs to acquire one conversion. | Spend / Conversions |

<Callout type="info" title="Real-time computation">
All derived metrics (CTR, CPM, CPC, ROAS, CAC) are computed in real-time from raw data. They are never stored as stale values, so they always reflect the latest numbers including any currency normalization.
</Callout>

## Video metrics

For video ads, additional engagement metrics are available:

| Metric | What it means |
|--------|--------------|
| **Hook Rate** | Percentage of impressions that started watching the video. A high hook rate means the first frame grabs attention. |
| **Retention Rate** | Percentage of viewers who watched at least 75% of the video. Shows how engaging the content is. |
| **Completion Rate** | Percentage of viewers who watched the entire video. |
| **Thumb Stop Ratio** | Percentage of unique people reached who stopped scrolling to watch. |
| **Avg Watch Time** | Average time viewers spent watching the video. |

## Daily trend chart

Click on any campaign, ad set, or ad to see its performance over time. The daily trend chart shows spend, impressions, and clicks plotted day by day, making it easy to spot patterns, identify when performance changed, or see the impact of creative refreshes.

## Attribution model switching

The same ad can show very different ROAS numbers under different attribution models. Use the model selector to compare:

- **Last Touch** gives all credit to the last ad the customer interacted with before converting
- **First Touch** gives all credit to the first ad that introduced the customer
- **Linear** splits credit equally across all touchpoints
- **Position Based** gives 40% to first touch, 40% to last touch, and splits the remaining 20% among middle touches

Switching models helps you understand whether an ad is better at introducing new customers (strong first-touch ROAS) or closing sales (strong last-touch ROAS).

<Callout type="info" title="ROAS only counts real payments">
ROAS calculations only include revenue from confirmed payments (Stripe/MercadoPago). Pipeline values from CRM deals are never included, because they represent projections rather than cash in hand.
</Callout>

## Entity detail page

Click on any campaign, ad set, or ad name to open its detail page. The detail page shows:

- All metrics in card format with the exact numbers
- The daily trend chart for that specific entity
- For individual ads: the creative thumbnail, body text, and title
- For video ads: the full video player plus video engagement metrics
- Attribution data showing which conversions this entity contributed to

## Related

<Cards>
  <Card title="Attribution Models" href="/docs/concepts/attribution-models">
    How different models change ROAS and campaign rankings
  </Card>
  <Card title="Revenue Types" href="/docs/concepts/revenue-types">
    Why only cash payments appear in ROAS calculations
  </Card>
  <Card title="Attribution Windows" href="/docs/settings/attribution-windows">
    Configure how far back touchpoints can receive credit
  </Card>
</Cards>

---

The Calendar page shows your appointments, bookings, and conversion events on a visual timeline. When GoHighLevel is connected, it merges live CRM data with Atribu's attribution data so you can see which campaigns drove each appointment.

## What it shows

The calendar displays events from multiple sources, merged and deduplicated:

| Source | What it provides |
|--------|-----------------|
| **GHL live events** | Real-time appointment status, assigned rep, calendar name |
| **Tracked events** | Attribution data — which campaign, channel, and ad drove this appointment |
| **Merged events** | Both sources combined, showing live status alongside attribution |

When a GHL appointment matches a tracked conversion event in Atribu's database, the calendar merges them into a single entry with the best data from both sides.

## Two views

<Tabs items={["Grid view", "Agenda view"]}>
<Tab value="Grid view">

The grid view shows a weekly calendar layout. Events appear as blocks on their scheduled day and time. Overlapping events are arranged side by side.

The time range adjusts automatically based on your actual appointment times — if your earliest appointment is at 9 AM and your latest is at 6 PM, the grid shows that range rather than a full 24-hour day.

</Tab>
<Tab value="Agenda view">

The agenda view shows events as a simple chronological list. Each event displays the time, customer name, status, and attribution information in a compact row format. This view is better for quickly scanning a large number of appointments.

</Tab>
</Tabs>

## Filters

Narrow down which events appear on the calendar:

- **Assigned user** — filter by the team member assigned to the appointment
- **Calendar** — filter by specific GHL calendar (if you have multiple)
- **Source type** — show only GHL live events, only tracked events, or merged events

## Attribution on appointments

Each appointment on the calendar shows which marketing channel and campaign brought the customer. This means you can see at a glance:

- How many appointments came from Paid Social vs. Organic
- Which specific Meta or Google Ads campaign drove each booking
- Whether the customer had previous interactions before scheduling

The attribution data comes from Atribu's identity resolution — the customer's email or phone from the appointment is matched to their earlier website visits and ad clicks.

<Callout type="info" title="GHL not connected?">
If GoHighLevel is not connected, the calendar shows tracked events from Atribu's database only. You still see appointments captured via tracker auto-capture (booking widget detection) and any other tracked conversion events, but without live GHL status or assigned rep information.
</Callout>

## Data sources explained

Events can have different source labels:

- **merged** — both GHL live data and Atribu tracking data are available for this event
- **db_tracked** — the event exists in Atribu's database (from tracker auto-capture or CRM sync) but was not found in the current GHL live query
- **db_fallback** — GHL is unavailable, so the calendar falls back to showing only database events
- **ghl_live** — the event comes from GHL's live API but has no matching tracked event in Atribu

## Related

<Cards>
  <Card title="GoHighLevel Integration" href="/docs/integrations/gohighlevel">
    Connect GHL to sync appointments and pipeline data
  </Card>
  <Card title="Identity Resolution" href="/docs/concepts/identity-resolution">
    How appointment contacts are matched to website visitors
  </Card>
</Cards>

---

# Conversion Sync

Conversion Sync sends your real business outcomes back to ad platforms so they can optimize for what actually matters — revenue, not just clicks.

Most attribution tools stop at reporting. Conversion Sync closes the loop: when a customer pays, books an appointment, or becomes a qualified lead, Atribu tells Meta and Google about it so their algorithms learn which audiences and creatives drive real results.

## Why this matters

Ad platforms optimize based on the signals they receive. If you only send page views and form submissions, their algorithms optimize for volume. If you send actual payment events with real revenue amounts, they optimize for customers who spend money.

Conversion Sync is not a generic event forwarder. It is a **quality-gated feedback system** that only sends high-confidence events with verified identity context. Weak signals get filtered out before they reach the platform.

## How it works

```mermaid
flowchart LR
    A[Customer pays / books / converts] --> B[Atribu records outcome event]
    B --> C{Signal rules match?}
    C -->|Yes| D{Quality gates pass?}
    C -->|No| E[Event stays internal only]
    D -->|Yes| F{Privacy filters applied}
    D -->|No| G[Skipped — logged with reason]
    F --> H[Meta CAPI / Google Ads]
```

1. **A conversion happens** — a payment, lead submission, appointment, or pipeline change is recorded in Atribu
2. **Signal rules evaluate** — your configured rules determine which events should be sent and where
3. **Quality gates filter** — only events with sufficient identity context (email, phone, click IDs) pass through
4. **Privacy filters scrub** — PII is hashed, sensitive fields stripped, URLs sanitized
5. **Platform receives the signal** — Meta CAPI or Google Ads gets a clean, high-confidence conversion event

Events that don't pass are logged with a clear reason so you always know what was sent and what was filtered.

## Getting started

When you first visit the Conversion Sync page, a guided setup walks you through three steps:

<Steps>

<Step>
### Connect your destinations

Link at least one ad platform (Meta or Google Ads) in Settings > Integrations. Conversion Sync uses these existing connections — you don't need to set up new OAuth flows.
</Step>

<Step>
### Create a signal rule

A signal rule defines: what event triggers a signal, where it goes, and what value to attach. Click **New Signal** to open the rule builder.
</Step>

<Step>
### Activate and monitor

Once your rule is saved and a destination is configured, signals start flowing. Monitor delivery in the Deliveries tab.
</Step>

</Steps>

## Creating signal rules

The rule builder is a 4-step wizard.

### Step 1: Source — what triggers the signal

Choose what kind of event should create a signal:

| Source type | When to use | Example |
|------------|-------------|---------|
| **Tracked event** | An outcome from your website, payment provider, or CRM | Payment received, lead created, order placed |
| **Pipeline stage change** | A customer moves to a new stage in your CRM pipeline | Appointment booked, qualified, closed won |

For tracked events, select from your [conversion definitions](/docs/settings/goals). For pipeline changes, select the target stage and optionally filter by the previous stage.

**Value mode** controls the monetary value sent with the signal:

| Mode | Behavior |
|------|----------|
| **Use actual amount** | Sends the real payment/deal value from the event |
| **Fixed amount** | Sends a fixed value you choose (e.g., $50 per lead) |
| **No value** | No monetary value attached |

### Step 2: Destinations — where to send

Toggle Meta and/or Google for each rule. You can optionally override the event name sent to each platform (e.g., send "Purchase" to Meta and "purchase" to Google).

A single rule can send to both platforms simultaneously. Each platform independently evaluates whether it has enough match context — a payment from a Meta ad click goes to Meta, a payment from a Google ad click goes to Google, and a payment with both identifiers goes to both.

### Step 3: Privacy overrides

Each rule can add privacy restrictions on top of the profile-level settings. You can strip additional fields or block specific URL paths for this rule only. These overrides can only add restrictions — they cannot weaken the profile-level privacy settings.

In [Healthcare Mode](/docs/healthcare/overview), this step shows all toggles locked and enforced by the HIPAA privacy floor.

### Step 4: Review and save

Confirm your settings and save. The rule begins evaluating events immediately. You can edit or archive rules at any time.

## Configuring destinations

The Destinations tab is where you configure the technical details for each ad platform.

### Meta CAPI

| Setting | Description |
|---------|-------------|
| **Connection** | Select your Meta Business Account (connected in Settings) |
| **Pixel ID** | Your Meta Pixel identifier |
| **Quality preset** | Controls how strict the identity matching requirements are |
| **Test send** | Send a test event to verify your Pixel receives data |

**Quality presets** control which events are sent:

| Preset | Requires | Best for |
|--------|----------|----------|
| **Lenient** | Email/phone OR Meta click ID | Maximum signal volume, lower match rates |
| **Balanced** (default) | Strong identity — hashed email/phone + Meta identifiers when available | Recommended for most accounts |
| **Strict** | Both email/phone AND Meta click ID | Highest match confidence, fewer signals |

### Google Ads

| Setting | Description |
|---------|-------------|
| **Connection** | Select your Google Ads account (connected in Settings) |
| **Conversion action** | The Google Ads conversion action path |
| **Quality preset** | Balanced only — requires `gclid` for most events |

Google Ads uses a fixed balanced quality preset because Google's Enhanced Conversions require a Google Click ID (`gclid`) for reliable attribution.

## Monitoring deliveries

The Deliveries tab shows a real-time feed of every signal event — sent, skipped, failed, or queued.

### Filter and search

Filter the feed by:
- **Platform** — All, Meta, or Google
- **Status** — Eligible, Sent, Skipped, Failed, or Dead Letter
- **Rule** — Show events for a specific signal rule

### Event detail

Click any row to see the full detail:
- **Status and reason** — Why the event was sent, skipped, or failed
- **Quality signals** — Which identity fields were present (email, phone, click IDs)
- **Privacy filters applied** — What was stripped before sending (IP, UA, referrer, etc.)
- **Customer context** — Campaign name, channel, lead quality score
- **Payload** — The actual data sent to the platform

### Retry and replay

Failed events can be retried individually or in bulk. The replay feature re-evaluates events against your current rules and settings — useful after updating a destination configuration or fixing a connection issue.

Events that exhaust all retry attempts move to Dead Letter status and can be manually replayed.

## Understanding skip reasons

When an event is filtered out, the feed shows a clear reason. Here are the most common:

| Skip reason | What it means | What to do |
|-------------|---------------|------------|
| **Missing Meta match data** | No email, phone, or Meta identifiers available | Ensure your forms call [identify()](/docs/tracking/identify-users) before submission |
| **Missing Google click ID** | No `gclid` found for this customer | Add UTM parameters to your Google Ads — see [UTM setup](/docs/tracking/utm-parameters) |
| **Low confidence** | Identity stitching confidence too low | Improve form capture or CRM integration |
| **Stage transition missing identity** | Pipeline event not linked to a customer profile | Ensure GHL contacts have email/phone |
| **Closed won no payment** | Deal marked as won but no confirmed payment found | Connect Stripe or MercadoPago for payment verification |
| **Platform source mismatch** | Event came from the other platform's ad | By design — Meta events go to Meta, Google events go to Google |
| **Healthcare allowlist blocked** | Event type not on the [HIPAA export allowlist](/docs/healthcare/export-allowlist) | Add to the allowlist or keep it blocked |
| **Legal gate blocked** | [Healthcare Mode](/docs/healthcare/overview) legal requirements not met | Complete the DPA/BAA acceptance |

## Privacy and compliance

Conversion Sync includes a full privacy layer that controls what data leaves Atribu.

### Privacy modes

| Mode | Behavior |
|------|----------|
| **Standard** | Configurable field stripping with blocklists for URLs and parameters |
| **HIPAA** | Allowlist-only export — only approved event types are sent, all PII stripped, Meta LDU enabled. See [Healthcare Mode](/docs/healthcare/overview) |
| **Custom** | Reserved for future use |

In HIPAA mode, the privacy panel switches from blocklist fields to [allowlist checkboxes](/docs/healthcare/export-allowlist). The two approaches are mutually exclusive — you cannot have both.

### What gets stripped

In all modes, you can configure which fields are removed before sending:

| Field | Standard (default) | HIPAA |
|-------|-------------------|-------|
| Client IP | Optional | Always stripped |
| User agent | Optional | Always stripped |
| HTTP referrer | Optional | Always stripped |
| Page title | Optional | Always stripped |
| External IDs (fbclid, gclid) | Optional | Always stripped |
| URL path | Configurable | Origin only |
| Meta LDU flags | Optional | Always enabled |

### Legal compliance

Healthcare accounts must complete a legal workflow before any signals are exported:

1. Accept the [DPA and HIPAA BAA](/dpa) via click-wrap
2. Confirm compliance acknowledgements
3. Upload supporting legal documents (optional)

The legal gate blocks all exports until these requirements are met. See [Healthcare Onboarding](/docs/healthcare/onboarding) for the full setup guide.

## Change log

The Change Log tab records every action taken in Conversion Sync:

- Signal rules created, updated, or archived
- Destinations configured, enabled, or disabled
- Privacy settings changed
- Legal compliance updates
- Test sends and replay actions

Each entry shows who made the change, when, and what specifically changed.

## Platform differences

Meta and Google handle conversion data differently. Conversion Sync accounts for these differences:

| Aspect | Meta CAPI | Google Ads |
|--------|-----------|------------|
| Identity matching | Hashed email + phone + Meta identifiers (fbp, fbc) | `gclid` + hashed email/phone (Enhanced Conversions) |
| Quality presets | Lenient, Balanced (default), Strict | Balanced only (locked) |
| Event name | Customizable (Purchase, Lead, Schedule, etc.) | Customizable label |
| Test sends | Supported | Coming soon |
| Staleness limit | 7 days | 90 days |
| Stage transitions | Supported with quality gates | Supported with quality gates |
| Value currency | Inherited from event or fixed | Inherited from event or fixed |

## Related

- [Install the tracker](/docs/getting-started/install-tracker) — required for web-based identity stitching
- [Connect Meta Ads](/docs/integrations/meta) — set up the Meta connection used by Conversion Sync
- [Connect Google Ads](/docs/integrations/google-ads) — set up the Google Ads connection
- [Goals (Conversion Definitions)](/docs/settings/goals) — define which business outcomes Conversion Sync can use
- [Attribution Models](/docs/concepts/attribution-models) — how Atribu distributes conversion credit
- [Healthcare Mode](/docs/healthcare/overview) — HIPAA-compliant export controls
- [Identity Resolution](/docs/concepts/identity-resolution) — how anonymous visitors become known customers

---

Most customers do not buy on the first visit. They see an ad, browse your site, leave, come back from a Google search, and maybe pay a week later after clicking an email link. The Customer Journeys feature shows you this complete path for every single customer.

<VideoEmbed src="/docs/videos/customer-journey.mp4" title="Customer journey — from ad click to payment" />

## Goal selector

Start by choosing which conversion goal you want to analyze. Common goals include:

- **payment_received** — customers who made a payment (Stripe or MercadoPago)
- **lead_created** — contacts who became leads in your CRM
- **appointment_booked** — people who scheduled an appointment
- **closed_won** — deals that closed successfully

The goal selector shows all conversion types defined for your profile, along with how many conversions occurred in the selected date range.

## Customer list

After selecting a goal, you see a list of every customer who completed that conversion. Each row shows:

| Field | What it shows |
|-------|--------------|
| **Name & email** | The customer's name and email address (if identified) |
| **Channel & source** | How they first found you (e.g., Paid Social from Instagram) |
| **Revenue** | The cash amount from this conversion |
| **Time to convert** | How long it took from their first marketing touch to the conversion |
| **Touchpoints** | Number of marketing interactions before converting |
| **Touch channels** | The sequence of channels in their journey (e.g., Paid Social, Direct, Organic Search) |

You can search for specific customers by name or email, and the list paginates automatically as you scroll.

## Customer detail timeline

Click on any customer to see their full journey timeline. This shows every single interaction in chronological order:

- **Page views** — every page they visited, with URL and referrer
- **Form fills** — when they submitted a form (which triggered identification)
- **Bookings** — appointment scheduling events
- **Payments** — Stripe or MercadoPago transactions with amounts
- **CRM events** — lead status changes, opportunity updates from GoHighLevel

Each event in the timeline shows:

- Timestamp
- Channel and source
- Campaign name (if from a paid ad)
- Device type, browser, and country
- Event-specific details (page URL, payment amount, etc.)

<Callout type="info" title="Synthetic touches">
Some events are marked as "synthetic" — these represent off-site conversions like Meta lead forms where the customer interacted with your ad without visiting your website. Atribu creates these touchpoints automatically so they can be attributed to the correct campaign.
</Callout>

## Why multi-touch journeys matter

Looking at individual customer journeys reveals patterns that aggregate data cannot:

- **How many touches does it take?** If most customers need 4-5 interactions before paying, you know not to judge a campaign by its first-click performance alone.
- **Which channels play which role?** Paid Social might introduce customers (first touch), while Email closes the deal (last touch). Both deserve credit.
- **Where do people drop off?** If customers visit 3 times but never convert, you may need a better offer or retargeting strategy.
- **How long is the buying cycle?** A 2-day cycle means aggressive retargeting works. A 30-day cycle means nurture sequences are essential.

## Users tab

Switch to the Users tab to see all visitors — not just those who converted. This view shows:

- Both identified customers (with names and emails) and anonymous visitors
- Session count, total pageviews, and revenue per visitor
- Last seen timestamp
- The channels they came through

<Callout type="info" title="Anonymous visitor names">
Visitors who have not identified themselves show up with colorful animal names like "Coral Fox" or "Jade Penguin." The same visitor always gets the same name, so you can track them across sessions even before they provide their email.
</Callout>

## Related

<Cards>
  <Card title="Identity Resolution" href="/docs/concepts/identity-resolution">
    How anonymous visitors become known customers
  </Card>
  <Card title="Attribution Models" href="/docs/concepts/attribution-models">
    How credit is distributed across touchpoints in the journey
  </Card>
  <Card title="Synthetic Touches" href="/docs/concepts/synthetic-touches">
    How off-site conversions appear in customer timelines
  </Card>
</Cards>

---

The dashboard is your mission control. It brings together every important marketing metric into a single view so you can understand how your campaigns are performing right now and how that compares to the previous period.

## KPI metrics bar

The top of the dashboard shows your key performance indicators side by side. Each metric compares the current period to the previous period of equal length.

| Metric | What it means |
|--------|--------------|
| **Visitors** | Unique people who visited your website in this period. |
| **Revenue** | Cash that came in from ad-attributed conversions (Stripe or MercadoPago payments only). |
| **Spend** | Total amount spent on ads across all connected platforms. |
| **CAC** | Customer Acquisition Cost — your ad spend divided by the number of conversions. Lower is better. |
| **Bounce Rate** | Percentage of visitors who left after viewing only one page. |
| **Online Now** | People currently browsing your site (updates every 30 seconds). |

Each metric shows a percentage change compared to the previous period. Green means the number improved; red means it went in the wrong direction.

<Callout type="info" title="Revenue only counts real payments">
Revenue for ROAS calculations only includes confirmed cash payments from Stripe and MercadoPago. Pipeline values from your CRM (like deal estimates) are shown separately and never mixed into ROAS.
</Callout>

## Date range picker

Select any time period and Atribu automatically compares it to the previous period of the same length. For example, if you select the last 7 days, the comparison period is the 7 days before that.

Quick presets are available for common ranges: last 7 days, last 14 days, last 30 days, or a custom range.

## Attribution model selector

Choose how credit is assigned to your marketing touchpoints using the dropdown in the header. Switching models instantly recalculates revenue, ROAS, and campaign rankings across the entire dashboard.

<Callout type="info" title="Learn more about models">
See [Attribution Models](/docs/concepts/attribution-models) for a detailed explanation of each model and when to use it.
</Callout>

## Main chart

The main chart is a dual-axis visualization showing two metrics over time:

- **Blue line**: Visitors per day (left axis)
- **Amber bars**: Cash revenue per day (right axis)

Hover over any day to see exact numbers in a tooltip. The tooltip also shows ad spend for that day when available. You can toggle either series on or off by clicking the colored square next to its label in the metrics bar.

## Breakdown boards

Below the main chart, four boards break down your traffic by different dimensions. Each board has multiple tabs:

| Board | Tabs |
|-------|------|
| **Traffic Sources** | Channel, Referrer, Campaign, Keyword |
| **Geography** | Country, Region, City |
| **Content** | Page, Entry Page, Exit Page |
| **Technology** | Browser, OS, Device |

Each row shows visitors, visits, pageviews, bounce rate, conversions, and revenue. Click any row to see a detailed breakdown dialog with the full data.

The Traffic Sources board also includes a donut chart that visualizes channel distribution at a glance.

## Top campaigns

The bottom of the dashboard shows your highest-performing campaigns ranked by attributed revenue. Each campaign displays:

- Total spend and attributed revenue
- Number of conversion results
- ROAS (Return on Ad Spend)

Click **View All** to go to the full [Ads Performance](/docs/features/ads-performance) page.

## Dashboard filters

Use the filter bar below the date range picker to narrow down all dashboard data. Available filter dimensions include:

- **Channel** — Paid Social, Organic Search, Direct, etc.
- **Country** — filter by visitor country
- **Device** — Desktop, Mobile, Tablet
- **Campaign** — specific ad campaigns
- **Goal** — filter by conversion type (e.g., only show payment_received)

Filters apply to every section of the dashboard simultaneously — KPIs, charts, breakdowns, and top campaigns all update together.

## Connection health banner

When you have integrations connected (Meta, Google Ads, Stripe, GHL), a health banner shows next to your profile name. It indicates the sync status of each connection, letting you know if data is fresh or if any connection needs attention.

## Journey & Users card

At the bottom of the dashboard, a tabbed card lets you quickly browse customer journeys, visitor lists, and conversion funnels without leaving the page. See the [Customer Journeys](/docs/features/customer-journey) page for full details.

## Related

<Cards>
  <Card title="Attribution Models" href="/docs/concepts/attribution-models">
    How credit is distributed across touchpoints
  </Card>
  <Card title="Revenue Types" href="/docs/concepts/revenue-types">
    Why only cash payments appear in ROAS calculations
  </Card>
  <Card title="Reports" href="/docs/features/reports">
    Generate shareable performance reports from your dashboard data
  </Card>
</Cards>

---

Data quality directly affects attribution accuracy. If visitors are not identified, if click IDs are missing, or if UTM parameters are inconsistent, your ROAS numbers will be incomplete. The Data Quality page helps you monitor and fix these issues.

## Identity profiles overview

The Data Quality page shows a summary of your customer identity profiles:

| Metric | What it means |
|--------|--------------|
| **Total profiles** | Number of customer profiles in your identity graph |
| **Missing email** | Profiles that have no primary email — these are harder to match across touchpoints |
| **Missing phone** | Profiles that have no primary phone number |

Each profile is listed with its source (GoHighLevel, Stripe, MercadoPago, Meta), name, email, phone, and identifiers. You can filter to see only profiles with missing data.

## Identity repair

When Atribu detects that a customer profile is missing an email or phone number, but that information exists in the raw event data (like a GHL opportunity or Stripe charge), it suggests a repair operation.

<Steps>
<Step>

### Scan for issues

The Data Quality page automatically scans your profiles and identifies those with missing contact information that could be backfilled from existing data.

</Step>
<Step>

### Review suggestions

Each suggestion shows the profile, what data is missing, and the suggested value with its source. For example: "Profile John Doe is missing email. Suggested: john@example.com (from GoHighLevel contact)."

</Step>
<Step>

### Preview and apply

Preview the repair operations to see exactly what will change, then apply them in bulk. The repair process backfills missing data without overwriting any existing values.

</Step>
</Steps>

## What affects data quality

Several factors can reduce the accuracy of your attribution data:

### Ad blockers

Some visitors use ad blockers that prevent the Atribu tracker from loading. This means their visits are not recorded and cannot be attributed. **Solution**: Set up a [custom domain](/docs/tracking/custom-domain) for your tracker to avoid most ad blockers.

### Missing identify calls

If visitors browse your site but never submit a form, complete a booking, or otherwise identify themselves, their anonymous visit cannot be linked to a later payment or CRM event. **Solution**: Enable [form auto-capture](/docs/tracking/auto-capture) so identification happens automatically when visitors fill out any form.

### Missing UTM parameters

Traffic from email campaigns, social media posts, or partner links without UTM parameters gets classified as "Direct" — making it impossible to attribute conversions to those sources. **Solution**: Use consistent UTM parameters on all marketing links.

### Cookie expiration

Visitors who return after a long period (browser cookies cleared, different device) will appear as new visitors. **Solution**: This is a natural limitation. The identity graph still matches them if they provide the same email or phone number.

### Incomplete integrations

If your payment provider or CRM is not connected, those conversion events cannot be attributed to ad campaigns. **Solution**: Connect all your data sources.

## How to improve data quality

<Steps>
<Step>

### Install the tracker on all pages

Make sure the Atribu tracker script is on every page of your website, not just landing pages. Visitors often browse multiple pages before converting, and each page view provides attribution data.

</Step>
<Step>

### Enable form auto-capture

Turn on automatic form detection so the tracker captures email and phone from any form submission. This triggers the `identify()` call that bridges anonymous visitors to known customers.

</Step>
<Step>

### Set up a custom domain

Configure a custom tracking domain (like `track.yourdomain.com`) to bypass most ad blockers and increase data collection rates.

</Step>
<Step>

### Use consistent UTM parameters

Add UTMs to all your marketing links — email campaigns, social posts, partner referrals. Without UTMs, Atribu cannot distinguish these sources from direct traffic.

For Meta ads, use this URL parameter template:
```text title="Meta Ads UTM Template"
utm_source=facebook&utm_medium=paid&utm_campaign={{campaign.id}}&utm_content={{ad.id}}&utm_term={{adset.id}}
```

</Step>
<Step>

### Connect all payment providers

Connect both Stripe and MercadoPago (if you use both) so every payment is captured and attributed. Unconnected payment sources create gaps in your revenue data.

</Step>
<Step>

### Connect your CRM

Connect GoHighLevel for complete pipeline visibility. This lets Atribu track leads, appointments, and opportunities — not just final payments.

</Step>
</Steps>

<Callout type="info" title="What is a good attribution coverage?">
80% or higher attribution coverage is good — it means most of your conversions can be traced back to a marketing touchpoint. 90% or higher is excellent. Below 60% usually indicates missing tracker installation or a lack of form auto-capture.
</Callout>

## Related

<Cards>
  <Card title="Install the Tracker" href="/docs/getting-started/install-tracker">
    Set up the tracking script on your website
  </Card>
  <Card title="Identity Resolution" href="/docs/concepts/identity-resolution">
    How anonymous visitors become known customers
  </Card>
  <Card title="Synthetic Touches" href="/docs/concepts/synthetic-touches">
    How off-site conversions are attributed without web visits
  </Card>
</Cards>

---

Payment Links let you create Stripe payment URLs that include Atribu's tracking metadata. When a customer pays through one of these links, Atribu can trace the payment back to the exact ad campaign that brought them.

## The problem

Normal Stripe payments lose the connection to the original ad click. A customer might click your ad on Monday, browse your site, and then pay on Thursday after receiving a follow-up email. By the time the payment arrives, there is no direct link between the Stripe charge and the original ad click.

## How Payment Links solve this

Atribu creates Stripe Payment Links that include tracking metadata. This metadata connects the payment to the visitor's session, making attribution automatic.

### Three layers of attribution

Payment Links use three methods to ensure attribution works, even when one layer fails:

<Steps>
<Step>

### Cookie passthrough

The tracking cookies (visitor ID and session ID) are embedded in the Stripe checkout session metadata. When the customer completes payment, Atribu reads these cookies from the webhook payload and instantly links the payment to the original visit.

</Step>
<Step>

### Post-payment redirect

After payment, the customer is redirected back to your website with a Stripe session ID in the URL. The Atribu tracker on your site detects this and fires a `stripe_checkout_completed` event, creating a direct link between the payment and the browsing session.

</Step>
<Step>

### Identity graph fallback

Even without cookies or redirects, the customer's email from the Stripe payment is matched against their earlier form fill or booking through Atribu's identity graph. If the same email appeared in a form submission during a tracked session, the payment is attributed to the original ad click.

</Step>
</Steps>

## Creating a payment link

<Steps>
<Step>

### Navigate to Payment Links

Go to your profile settings or developer page where Payment Links are managed.

</Step>
<Step>

### Configure the payment

Set the required fields:

- **Amount** — the payment amount in cents (e.g., 5000 for $50.00)
- **Currency** — the currency code (USD, MXN, EUR, etc.)
- **Product name** — a description of what the customer is paying for

</Step>
<Step>

### Optional: link to a customer or campaign

You can associate the payment link with:

- A specific **customer profile** — pre-links the payment to a known customer
- A specific **campaign** — directly attributes the payment to a campaign

</Step>
<Step>

### Optional: set a redirect URL

Provide a URL on your tracked website where customers are sent after payment. This enables the post-payment redirect attribution layer. The URL automatically gets a `?session_id={CHECKOUT_SESSION_ID}` parameter appended.

</Step>
<Step>

### Copy and share

Copy the generated Stripe Payment Link URL and share it with your customer — via email, SMS, WhatsApp, or embed it on your website.

</Step>
</Steps>

<Callout type="info" title="Payment Link revenue counts as cash">
Payments made through Payment Links always count as "cash" revenue with `revenue_type = 'cash'`. This means they are included in ROAS calculations alongside your other Stripe and MercadoPago payments.
</Callout>

## How it works technically

When you create a Payment Link, Atribu:

1. Creates a Stripe Payment Link via the Stripe API using your connected Stripe account
2. Embeds `atribu_profile_id`, `atribu_customer_profile_id`, and `campaign_id` in the payment link metadata
3. If a redirect URL is provided, appends the Stripe checkout session ID for post-payment stitching
4. Stores the link in the `payment_links` table for audit and tracking

When the customer pays, the standard Stripe webhook flow handles the rest — the `checkout.session.completed` event carries all the metadata needed for attribution.

## Related

<Cards>
  <Card title="Stripe Integration" href="/docs/integrations/stripe">
    How Stripe payments are tracked and attributed
  </Card>
  <Card title="Revenue Types" href="/docs/concepts/revenue-types">
    Payment Link revenue counts as cash for ROAS
  </Card>
  <Card title="Identity Resolution" href="/docs/concepts/identity-resolution">
    How the identity graph provides fallback attribution
  </Card>
</Cards>

---

Reports let you create professional, shareable marketing performance summaries. They are ideal for client reporting, team updates, or monthly performance reviews.

## What is included in a report

Each generated report contains:

- **KPI summary** — spend, revenue, ROAS, visitors, and conversions for the period
- **Top campaigns** — your best-performing campaigns ranked by attributed revenue and ROAS
- **Channel breakdown** — traffic sources and how each channel contributed to results
- **Attribution analysis** — how credit is distributed across touchpoints under your chosen model
- **Conversion overview** — counts and values for each conversion type

## Creating a report

<Steps>
<Step>

### Go to the Reports page

Navigate to **Reports** in the sidebar. You will see any previously generated reports listed here.

</Step>
<Step>

### Click Generate Report

Click the **Generate** button in the top right corner to open the report configuration dialog.

</Step>
<Step>

### Select your date range

Choose a preset (Last 7 days, Last 14 days, Last 30 days) or pick a custom date range using the calendar.

</Step>
<Step>

### Choose an attribution model

Select which attribution model the report should use for revenue and ROAS calculations. The model you choose affects how credit is distributed across campaigns.

</Step>
<Step>

### Generate and review

Click **Generate** and wait a few seconds. Atribu pulls all your data, runs the calculations, and creates the report. Once ready, you are taken to the report detail page.

</Step>
</Steps>

## Viewing a report

Each report has its own detail page showing the full content. You can scroll through all sections — KPIs, campaign rankings, channel analysis — in a clean, print-ready format.

## Sharing reports

Every report gets a shareable public link. Anyone with the link can view the report without needing an Atribu account.

<Steps>
<Step>

### Copy the share link

On the report detail page, click the **Share** button. The public URL is copied to your clipboard.

</Step>
<Step>

### Send to your client or team

Paste the link in an email, Slack message, or anywhere else. The recipient sees a read-only version of the report.

</Step>
</Steps>

## Downloading as PDF

Click the **Download** button on any report to export it as a PDF file. The PDF preserves the report layout and can be attached to emails or printed.

## Managing reports

- Reports are listed on the Reports page with their date range, model, and creation date
- Click any report to view its full content
- Delete reports you no longer need using the trash icon

<Callout type="info" title="Reports reflect a point in time">
Reports capture your data at the moment they are generated. If new conversions come in or attribution data changes after generation, the report will not update automatically. Generate a new report to get the latest numbers.
</Callout>

## Related

<Cards>
  <Card title="Dashboard" href="/docs/features/dashboard">
    The live view that reports are generated from
  </Card>
  <Card title="Attribution Models" href="/docs/concepts/attribution-models">
    How the model you choose affects report numbers
  </Card>
</Cards>

---

Atribu automatically classifies every visitor into a traffic channel based on how they arrived at your site. This helps you understand which marketing efforts drive the most traffic and conversions.

## Channel types

Every visit to your website is classified into one of these channels:

| Channel | What it means |
|---------|--------------|
| **Paid Social** | Visitors who clicked a paid ad on Facebook, Instagram, TikTok, or other social platforms. Identified by click IDs like `fbclid` or `ttclid`, or by UTM parameters with `medium=paid`. |
| **Organic Social** | Visitors from unpaid social media posts — someone shared your link on Facebook or you posted on Instagram without paying for promotion. |
| **Paid Search** | Visitors from Google Ads, Bing Ads, or other paid search campaigns. Identified by `gclid`, `msclkid`, or UTM parameters with `medium=cpc`. |
| **Organic Search** | Visitors who found you through a Google, Bing, or other search engine result — without you paying for the click. |
| **Direct** | Visitors who typed your URL directly into their browser, used a bookmark, or came from a source that could not be identified. |
| **Referral** | Visitors who clicked a link to your site from another website (a blog post, directory listing, partner site, etc.). |
| **Email** | Visitors from email campaigns. Requires UTM parameters with `medium=email` on your email links. |
| **Display** | Visitors from banner ads or display advertising networks. |

<Callout type="info" title="Donut chart visitor count">
The Traffic Sources donut chart counts visitors using session-level data, which includes both web visitors and server-side events. This number may differ slightly from the KPI card visitor count, which only counts unique web visitors.
</Callout>

## Breakdown boards

The dashboard includes four breakdown boards that let you analyze your traffic from different angles. Each board has multiple tabs for deeper analysis.

### Traffic Sources board

| Tab | Shows |
|-----|-------|
| **Channel** | Visitors grouped by channel (Paid Social, Direct, Organic Search, etc.) |
| **Referrer** | The specific websites or domains sending visitors to you |
| **Campaign** | Traffic grouped by UTM campaign name |
| **Keyword** | Search keywords driving traffic (requires Google Search Console connection) |

### Geography board

| Tab | Shows |
|-----|-------|
| **Country** | Where your visitors are located by country |
| **Region** | State or province level breakdown |
| **City** | City-level breakdown |

### Content board

| Tab | Shows |
|-----|-------|
| **Page** | Which pages get the most views |
| **Entry Page** | The first page visitors land on |
| **Exit Page** | The last page visitors view before leaving |

### Technology board

| Tab | Shows |
|-----|-------|
| **Browser** | Chrome, Safari, Firefox, etc. |
| **OS** | Windows, macOS, iOS, Android, etc. |
| **Device** | Desktop, Mobile, or Tablet |

## Metrics in each breakdown

Every row in a breakdown board shows these metrics:

- **Visitors** — unique people
- **Visits** — total sessions (one person can have multiple visits)
- **Pageviews** — total pages viewed
- **Bounce Rate** — percentage of visits where the person left after one page
- **Conversions** — number of attributed conversions
- **Revenue** — attributed cash revenue

Click any row to expand a detail dialog showing the full metric breakdown and trend over time. You can also change the sort order to rank by any of these metrics.

## Filtering

Use dashboard filters to focus on specific traffic segments. For example, filter to **Channel = Paid Social** to see only visitors from your paid ads, then examine which countries or devices they use.

Filters apply across all four boards simultaneously, so you can combine dimensions — like seeing only mobile visitors from the United States who came through Paid Social.

<Callout type="info" title="Channel classification details">
For a technical deep-dive on how Atribu classifies channels (including click ID detection and UTM rules), see [Channels](/docs/concepts/channels).
</Callout>

## Related

<Cards>
  <Card title="UTM Parameters" href="/docs/tracking/utm-parameters">
    Tag your traffic sources for accurate channel classification
  </Card>
  <Card title="Dashboard" href="/docs/features/dashboard">
    See traffic source data alongside KPIs and campaign performance
  </Card>
</Cards>

---

Connecting your ad platforms lets Atribu pull in your campaigns, ad sets (or ad groups), individual ads, and daily spend data. This is what makes it possible to calculate **ROAS** (Return on Ad Spend) -- the ratio of revenue earned to money spent on advertising. Without this connection, Atribu can still track visitors and conversions, but it cannot tell you which specific campaigns are profitable.

## What gets synced

Once connected, Atribu automatically imports:

| Data | Description |
|------|-------------|
| **Campaigns** | All campaigns in your ad account, including name, status, and objective |
| **Ad sets / Ad groups** | The targeting groups within each campaign |
| **Ads** | Individual ad creatives, including thumbnails and video references |
| **Daily spend** | How much you spent each day, broken down by campaign, ad set, and ad |
| **Impressions** | How many times your ads were shown |
| **Clicks** | How many times people clicked your ads |

Performance metrics like CPM (cost per thousand impressions), CPC (cost per click), and CTR (click-through rate) are calculated automatically from this data.

## Connect your platforms

<Tabs items={["Meta (Facebook & Instagram)", "Google Ads"]}>
<Tab value="Meta (Facebook & Instagram)">

<Steps>
<Step>

### Open Integrations

In your Atribu dashboard, go to **Settings > Integrations**.

</Step>
<Step>

### Click Connect Meta

Find the Meta card and click **Connect**. You will be redirected to Facebook.

</Step>
<Step>

### Authorize in Facebook

Log in to Facebook (if not already) and review the permissions Atribu is requesting:

- **ads_read** -- Read your ad account data (campaigns, spend, performance)

Click **Continue** to authorize.

</Step>
<Step>

### Select ad accounts

After authorization, choose which ad accounts you want to sync. You can select multiple accounts if you manage ads for several businesses.

</Step>
<Step>

### Done

Atribu begins syncing your data immediately. You will see campaigns and spend data in your dashboard within a few minutes.

</Step>
</Steps>

<Callout type="info" title="Sync frequency">
Atribu syncs your Meta ad data automatically. After the initial import (which pulls historical data), updates arrive throughout the day. Spend data for the current day may take a few hours to reflect the latest numbers, as Meta's reporting API has its own delays.
</Callout>

<Callout type="warn" title="Re-authorization">
If Atribu adds new features that require additional permissions, you may need to reconnect your Meta account. When this happens, you will see a banner in Settings. Facebook only shows the permissions dialog when Atribu explicitly requests it, so the reconnection process is quick -- your existing data is preserved.
</Callout>

</Tab>
<Tab value="Google Ads">

<Steps>
<Step>

### Open Integrations

In your Atribu dashboard, go to **Settings > Integrations**.

</Step>
<Step>

### Click Connect Google Ads

Find the Google Ads card and click **Connect**. You will be redirected to Google.

</Step>
<Step>

### Sign in with Google

Sign in with the Google account that has access to your Google Ads account. Review the permissions and click **Allow**.

</Step>
<Step>

### Select your customer account

If your Google account has access to multiple Google Ads customer accounts (common for agencies using a Manager Account), select the one you want to sync.

</Step>
<Step>

### Done

Atribu begins syncing your Google Ads data. Campaigns, ad groups, ads, and spend data will appear in your dashboard within minutes.

</Step>
</Steps>

<Callout type="info" title="Manager accounts">
If you use a Google Ads Manager Account (MCC), you can connect individual client accounts underneath it. Each client account syncs independently.
</Callout>

</Tab>
</Tabs>

## Troubleshooting

### I do not see my ad accounts

- **Meta**: Make sure you are logging in with a Facebook account that has at least "Advertiser" access to the ad account. If you manage ads through a Business Manager, the ad account must be shared with your personal Facebook account.
- **Google Ads**: Make sure you are signing in with the Google account that has direct access (or Manager Account access) to the Google Ads customer account.

### Data is missing or incomplete

- **Recent changes**: After connecting, the initial sync pulls historical data. This can take a few minutes for accounts with many campaigns. Check back in 10-15 minutes.
- **Paused campaigns**: Atribu imports all campaigns regardless of status (active, paused, archived). If you do not see a specific campaign, check that you connected the correct ad account.
- **Spend shows zero**: Some campaigns (especially brand awareness campaigns) may have impressions but very low or zero recorded spend on certain days. This is normal.

### I need to reconnect

If your connection stops working (for example, if you changed your Facebook password or revoked access), go to **Settings > Integrations**, find the platform, and click **Reconnect**. Your historical data is preserved.

## Next steps

<Cards>
  <Card title="Connect Payments" href="/docs/getting-started/connect-payments">
    Link Stripe or MercadoPago to attribute real revenue to your campaigns
  </Card>
  <Card title="Connect GoHighLevel" href="/docs/getting-started/connect-crm">
    Sync contacts, deals, and appointments from your CRM
  </Card>
  <Card title="Meta Integration Details" href="/docs/integrations/meta">
    Video previews, CAPI conversion export, and Meta-specific configuration
  </Card>
  <Card title="Google Ads Integration Details" href="/docs/integrations/google-ads">
    Conversion export, Manager Account support, and Google Ads-specific setup
  </Card>
</Cards>

---

If you use GoHighLevel (GHL) as your CRM, connecting it to Atribu gives you visibility into the full customer journey -- from the first ad click to a booked appointment to a closed deal. Atribu syncs your contacts, opportunities (deals), and calendar events, and maps your pipeline stages to conversion types so you can track progress at every step.

## What gets synced

| Data | Description |
|------|-------------|
| **Contacts** | Name, email, phone number. Used to build the identity graph -- matching CRM contacts to website visitors. |
| **Opportunities** | Deals in your pipeline, including stage, value, and UTM attribution data. GHL stores which ad campaign originated each deal. |
| **Calendar events** | Booked appointments, including the calendar name, assigned team member, and linked contact. |
| **Pipeline stages** | Your pipeline configuration, mapped to Atribu conversion types (see below). |

## Connect GoHighLevel

<Steps>
<Step>

### Open Integrations

In your Atribu dashboard, go to **Settings > Integrations**.

</Step>
<Step>

### Click Connect GoHighLevel

Find the GoHighLevel card and click **Connect**. You will be redirected to the GHL Marketplace.

</Step>
<Step>

### Select your location

GHL uses "locations" (also called sub-accounts) to organize different businesses. Select the location you want to connect to this Atribu profile.

</Step>
<Step>

### Authorize permissions

Review and approve the permissions Atribu is requesting:

- **Contacts** (read-only) -- Access to your contact records
- **Opportunities** (read-only) -- Access to your deals and pipeline data
- **Locations** (read-only) -- Access to your location details
- **Calendars** (read-only) -- Access to your calendars and booked events

Click **Authorize** to complete the connection.

</Step>
<Step>

### Sync begins automatically

Atribu starts importing your GHL data right away. Contacts, opportunities, and calendar events will appear in your dashboard within a few minutes.

</Step>
</Steps>

## Pipeline stage mapping

When GHL opportunities move through your pipeline stages, Atribu maps each stage to a **conversion type**. This is how Atribu knows whether a deal represents a new lead, a booked appointment, or a closed deal.

<Callout type="info" title="Conversion types explained">
Atribu uses these standard conversion types:

- **lead_created** -- A new lead entered your pipeline (for example, someone filled out a form or was added as a contact)
- **appointment_booked** -- A meeting or consultation was scheduled
- **qualified** -- The lead has been vetted and meets your criteria
- **closed_won** -- The deal was won and the client committed
- **payment_received** -- Actual payment was collected (this comes from Stripe/MercadoPago, not GHL)

Your GHL pipeline stages are matched to these types automatically based on stage names. You can customize the mapping in **Settings > Conversions**.
</Callout>

## Understanding pipeline value vs. cash revenue

<Callout type="warn" title="Pipeline values are not real revenue">
GHL deal values represent the **projected worth** of a deal -- what you expect to earn if the deal closes. These are useful for forecasting, but they are not the same as money in your bank account.

Atribu separates these clearly:

- **Pipeline value** (from GHL) -- Shown in the "Pipeline" section of your dashboard. Useful for tracking deal flow and sales performance, but never included in ROAS.
- **Cash revenue** (from Stripe/MercadoPago) -- Shown in the "Revenue" section and used for all ROAS calculations. This is actual collected payment.

A $10,000 deal in your GHL pipeline only counts toward ROAS once the client actually pays $10,000 through Stripe or MercadoPago.
</Callout>

## Calendar integration

Once GHL is connected, the **Calendar** page in Atribu shows your booked appointments alongside attribution data. For each appointment, you can see:

- Which ad campaign brought the customer in
- The customer's full journey (pages viewed, forms submitted)
- Deal value and pipeline stage

Atribu merges live GHL calendar data with its own attribution records, so you get real-time appointment status combined with marketing attribution in one view.

## GHL and Meta native lead forms

Many leads that come through Meta (Facebook and Instagram) native lead forms go directly to GHL without the person ever visiting your website. These leads have campaign data from Meta's attribution (which ad they clicked, which ad set, etc.) but no website visit to track.

Atribu handles this automatically by creating **synthetic touchpoints** -- attribution records based on the campaign data that GHL passes along. This means even leads from Instagram lead forms that never visited your site are attributed to the correct ad campaign.

## Troubleshooting

### Missing contacts

GHL's contacts API may not return every contact in a single sync. Contacts that were created through opportunities (deals) rather than direct form submissions may take an additional sync cycle to appear. If specific contacts are missing after 24 hours, try triggering a manual re-sync from **Settings > Integrations**.

### Pipeline stages are not mapping correctly

Go to **Settings > Conversions** to review and adjust your stage-to-conversion mappings. Atribu matches stages by keyword (for example, a stage named "Appointment Set" maps to `appointment_booked`). If your stage names use non-standard labels, you may need to map them manually.

### Duplicate contacts

If the same person appears under multiple contact records in GHL (for example, with different email addresses), Atribu's identity resolution system will try to merge them based on shared identifiers (email, phone, contact ID). In some cases, manual deduplication in GHL may be needed.

## Next steps

<Cards>
  <Card title="Install the Tracker" href="/docs/getting-started/install-tracker">
    Make sure the tracking script is on your site for full attribution
  </Card>
  <Card title="Revenue Types" href="/docs/concepts/revenue-types">
    Understand the difference between cash, pipeline, and gross revenue
  </Card>
  <Card title="Attribution Models" href="/docs/concepts/attribution-models">
    Learn how Atribu assigns credit across touchpoints
  </Card>
  <Card title="GoHighLevel Integration Details" href="/docs/integrations/gohighlevel">
    Pipeline mapping, calendar merge, and GHL-specific configuration
  </Card>
  <Card title="Synthetic Touches" href="/docs/concepts/synthetic-touches">
    How off-site leads from Meta lead forms get attributed
  </Card>
</Cards>

---

Connecting your payment provider is what unlocks the most valuable metric in Atribu: **real revenue attribution**. Once connected, every payment is matched back to the ad campaign, ad set, and specific ad that originally brought the customer to your site. This lets you calculate **ROAS** (Return on Ad Spend) -- the amount of revenue you earn for every dollar spent on ads.

## What gets tracked

When a payment comes through Stripe or MercadoPago, Atribu creates an **outcome event** -- a record that something valuable happened. The attribution engine then works backward through the customer's journey to find which ad touchpoints led to that payment.

For each payment, Atribu captures:

| Data | Description |
|------|-------------|
| **Amount** | The payment amount in its original currency |
| **Currency** | The currency of the transaction (automatically converted for reporting) |
| **Customer email** | Used to match the payment to a known visitor via the identity graph |
| **Billing details** | Name, phone, and address when available |
| **Timestamp** | When the payment occurred |

## Connect your provider

<Tabs items={["Stripe", "MercadoPago"]}>
<Tab value="Stripe">

<Steps>
<Step>

### Open Integrations

In your Atribu dashboard, go to **Settings > Integrations**.

</Step>
<Step>

### Click Connect Stripe

Find the Stripe card and click **Connect**. You will be redirected to the Stripe authorization page.

</Step>
<Step>

### Authorize in Stripe

Review the permissions and click **Connect** or **Authorize**. If you manage multiple Stripe accounts, select the one you want to connect.

</Step>
<Step>

### Done

That is it. Stripe webhooks are configured automatically -- you do not need to set up any webhook endpoints manually. Payments will start appearing in Atribu as soon as they are processed.

</Step>
</Steps>

<Callout type="info" title="Automatic webhook setup">
When you connect Stripe, Atribu automatically registers webhook listeners for payment events. It listens for `charge.succeeded`, `payment_intent.succeeded`, and `checkout.session.completed` events. You do not need to configure anything in the Stripe dashboard.
</Callout>

### How Stripe attribution works

Stripe payments typically do not contain UTM parameters or ad campaign data. Instead, Atribu uses the **identity graph** to connect payments to ad clicks:

1. A visitor clicks your ad and lands on your site. The tracker records their anonymous ID and the campaign details from the URL.
2. The visitor fills out a form (or completes a booking). The tracker automatically calls `identify()` with their email, linking their anonymous browser ID to a known customer.
3. Later, when they pay via Stripe, the payment arrives with the same email. Atribu matches it to the same customer profile.
4. The attribution engine traces back through the customer's touchpoints to find which ad brought them in.

This is why installing the tracker and having forms on your site is so important -- it creates the bridge between anonymous ad clicks and real payments.

</Tab>
<Tab value="MercadoPago">

<Steps>
<Step>

### Open Integrations

In your Atribu dashboard, go to **Settings > Integrations**.

</Step>
<Step>

### Click Connect MercadoPago

Find the MercadoPago card and click **Connect**. You will be redirected to MercadoPago's authorization page.

</Step>
<Step>

### Authorize access

Log in to your MercadoPago account and approve the connection.

</Step>
<Step>

### Done

Payments will start flowing into Atribu automatically. Like Stripe, the webhook setup is handled for you.

</Step>
</Steps>

</Tab>
</Tabs>

## Revenue types

<Callout type="warn" title="Cash vs. pipeline revenue">
Atribu distinguishes between different types of revenue. Only **cash revenue** (actual payments from Stripe or MercadoPago) counts toward your ROAS calculation. Here is why this matters:

- **Cash revenue** -- Real money received through Stripe or MercadoPago. This is the only number used for ROAS.
- **Pipeline value** -- Deal values from your CRM (GoHighLevel). These are projections of how much a deal might be worth, not confirmed payments. A "$5,000 deal" in your pipeline does not mean $5,000 has been collected.

If pipeline values were mixed into ROAS, a single large deal estimate could make an underperforming campaign look profitable. Atribu keeps these separate so your ROAS always reflects real, collected cash.
</Callout>

## Payment Links

Atribu can generate Stripe Payment Links with built-in attribution tracking. These links carry metadata that connects the payment directly to the customer and campaign, providing the strongest possible attribution signal.

You can create Payment Links from the Atribu dashboard under **Payment Links**. Each link includes tracking metadata so that when a customer completes payment, Atribu can attribute it without relying solely on the identity graph.

## Troubleshooting

### Payments are not appearing

- **Check the connection status**: Go to **Settings > Integrations** and verify that your Stripe or MercadoPago connection shows as "Connected".
- **Wait a moment**: Webhook delivery is near-instant, but there can be a short delay (under a minute) before the payment appears in your dashboard.
- **Test mode vs live mode**: Make sure you connected your live Stripe account, not a test mode account. Test payments will not appear.

### Revenue shows zero but payments are coming in

- **Check conversion definitions**: Go to **Settings > Conversions** and make sure `payment_received` is configured. This is set up by default, but may have been modified.
- **Identity matching**: If the customer email on the payment does not match any known visitor, the payment will be recorded but may not be attributed to a campaign. Make sure the tracker is installed and forms are being captured.

## Next steps

<Cards>
  <Card title="Connect GoHighLevel" href="/docs/getting-started/connect-crm">
    Sync contacts, deals, and appointments from your CRM
  </Card>
  <Card title="Attribution Models" href="/docs/concepts/attribution-models">
    Learn how credit is assigned across touchpoints
  </Card>
  <Card title="Revenue Types" href="/docs/concepts/revenue-types">
    Understand why only cash payments count for ROAS
  </Card>
  <Card title="Stripe Integration Details" href="/docs/integrations/stripe">
    Payment Links, webhook setup, and Stripe-specific attribution details
  </Card>
</Cards>

---

The Atribu tracking script is a lightweight JavaScript snippet that runs on your website. It captures:

- **Page views** -- which pages visitors see and how long they stay
- **Visitor identity** -- assigns each device a unique anonymous ID so you can follow the journey across visits
- **Traffic sources** -- reads UTM parameters and click IDs (fbclid, gclid) from your ad URLs to know which campaign brought each visitor
- **Form submissions** -- automatically detects when someone fills out a form and extracts their email or phone number
- **Booking completions** -- detects GoHighLevel, Calendly, and Cal.com booking widgets

## Installation

Go to **Settings > Tracking** in your Atribu dashboard. You will see your personalized tracking snippet with your unique tracking key already filled in. Copy it and install it using one of the methods below.

<Tabs items={["HTML (copy-paste)", "Google Tag Manager", "Shopify"]}>
<Tab value="HTML (copy-paste)">

Paste the snippet into the `<head>` section of every page on your site. If you use a CMS like WordPress, Webflow, or Squarespace, look for the "Custom Code" or "Header Code Injection" setting.

```html title="Tracking snippet"
<script>
  window.ATRIBU_TRACKING_KEY = "your-tracking-key-here";
  window.ATRIBU_TRACKING_ENDPOINT = "https://www.atribu.app/api/tracking/collect";
  window.ATRIBU_INTERCEPT_FBQ = true;
  window.ATRIBU_META_BRIDGE_PAGEVIEW = false;
  window.atribuTracker = window.atribuTracker || function() {
    (window.atribuTracker.q = window.atribuTracker.q || []).push(arguments);
  };
</script>
<script src="https://www.atribu.app/atribu-tracker.js" defer></script>
```

<Callout type="info" title="Use your actual snippet">
The code above is a template. Copy your real snippet from **Settings > Tracking** -- it has your tracking key pre-filled.
</Callout>

The `defer` attribute ensures the script loads without slowing down your page. Events that happen before the script finishes loading are queued and sent once it is ready.

</Tab>
<Tab value="Google Tag Manager">

<Steps>
<Step>

### Create a new tag

In Google Tag Manager, go to **Tags > New** and select **Custom HTML** as the tag type.

</Step>
<Step>

### Paste the snippet

Copy your tracking snippet from **Settings > Tracking** in Atribu and paste it into the HTML field.

```html title="Custom HTML tag content"
<script>
  window.ATRIBU_TRACKING_KEY = "your-tracking-key-here";
  window.ATRIBU_TRACKING_ENDPOINT = "https://www.atribu.app/api/tracking/collect";
  window.ATRIBU_INTERCEPT_FBQ = true;
  window.ATRIBU_META_BRIDGE_PAGEVIEW = false;
  window.atribuTracker = window.atribuTracker || function() {
    (window.atribuTracker.q = window.atribuTracker.q || []).push(arguments);
  };
</script>
<script src="https://www.atribu.app/atribu-tracker.js" defer></script>
```

</Step>
<Step>

### Set the trigger

Set the trigger to **All Pages** so the tracker loads on every page of your site.

</Step>
<Step>

### Publish

Click **Submit** to publish the container. The tracker will start collecting data immediately.

</Step>
</Steps>

</Tab>
<Tab value="Shopify">

Shopify's Web Pixel system provides a sandboxed environment for third-party scripts.

<Steps>
<Step>

### Go to Customer Events

In your Shopify admin, navigate to **Settings > Customer Events**.

</Step>
<Step>

### Add a custom pixel

Click **Add custom pixel** and give it a name like "Atribu Tracker".

</Step>
<Step>

### Paste the snippet

Copy your tracking snippet from **Settings > Tracking** in Atribu and paste it into the code editor.

</Step>
<Step>

### Save and connect

Click **Save**, then toggle the pixel to **Connected**.

</Step>
</Steps>

<Callout type="warn" title="Shopify limitations">
Shopify's Web Pixel runs in a sandboxed iframe. Form auto-capture and booking widget detection may not work inside the sandbox. For full functionality, consider adding the tracker via the theme's `theme.liquid` file instead (paste it before the closing `</head>` tag).
</Callout>

</Tab>
</Tabs>

## How the tracker works

<Callout type="info" title="IDs and cookies">
The tracker creates two identifiers for each visitor:

- **Anonymous ID** (`atribu_visitor_id` cookie) -- A unique ID for each device/browser. Persists for 1 year. This is how Atribu counts unique visitors.
- **Session ID** (`atribu_session_id` cookie) -- A unique ID for each visit. Resets after 30 minutes of inactivity or when the visitor arrives from a new marketing source (different UTM parameters or click ID). This is how Atribu counts sessions.

Both IDs are stored as first-party cookies so server-side code (like checkout pages) can read them and pass them to payment providers for better attribution.
</Callout>

### Automatic features

The tracker automatically captures these events without any extra code:

| Feature | What it does |
|---------|-------------|
| Page views | Tracks every page the visitor sees, including single-page app navigation |
| Form submissions | Detects form submissions, extracts email/phone, and links the anonymous visitor to a known customer |
| Booking widgets | Detects GoHighLevel, Calendly, and Cal.com booking completions |
| Stripe checkout redirects | Detects `?session_id=cs_*` in the URL after a Stripe Payment Link redirect |
| Outbound link clicks | Tracks clicks to external domains |
| File downloads | Tracks downloads (PDF, ZIP, etc.) |
| Engagement | Measures scroll depth and time on page |

### The identify() call

The most important thing the tracker does is **link anonymous visitors to known customers**. When someone fills out a form on your site, the tracker automatically calls `identify()` with their email or phone number. This creates a connection between the anonymous browser visitor and the customer record in your CRM or payment provider.

Without this link, Atribu cannot attribute a Stripe payment back to the original ad click. Form auto-capture handles this automatically for standard HTML forms. If you use a custom form (React, Vue, etc.), you can call `identify()` manually:

```js title="Manual identify call"
window.atribuTracker.identify({
  email: "customer@example.com",
  phone: "+1234567890"
});
```

## Verify it is working

After installing the tracker:

1. Visit your website in a browser (not incognito -- some ad blockers are more aggressive there)
2. Open your Atribu dashboard
3. Check the **Online Now** counter in the top right -- you should see at least 1 visitor
4. After a few minutes, page views and sessions will appear in the dashboard

<Callout type="warn" title="Ad blockers">
Some browser ad blockers may block the tracking script. If you or your team use ad blockers, you may not see your own visits. To ensure the tracker works for all visitors -- including those with ad blockers -- set up a **custom tracking domain**. This routes tracking requests through your own domain instead of `atribu.app`, which ad blockers do not block.

Go to **Settings > Tracking > Custom Domain** to set this up.
</Callout>

## Next steps

<Cards>
  <Card title="Connect Ad Platforms" href="/docs/getting-started/connect-ads">
    Link Meta and Google Ads to see which campaigns drive traffic
  </Card>
  <Card title="Connect Payments" href="/docs/getting-started/connect-payments">
    Link Stripe or MercadoPago to track revenue
  </Card>
  <Card title="How Tracking Works" href="/docs/tracking/how-tracking-works">
    Deep dive into visitors, sessions, cookies, and how the tracker captures data
  </Card>
  <Card title="Identity Resolution" href="/docs/concepts/identity-resolution">
    How anonymous visitors become known customers across devices and channels
  </Card>
</Cards>

---

Atribu is a marketing attribution platform that connects your ad platforms (Meta, Google Ads), CRMs (GoHighLevel), and payment providers (Stripe, MercadoPago) in one place. Instead of guessing which campaigns are working, you see exactly which ads led to real payments -- so you can stop wasting budget and double down on what works.

<VideoEmbed src="/docs/videos/getting-started.mp4" title="Get started with Atribu in 5 steps" />

## Setup overview

Getting started takes about 15 minutes. Here is what you will do:

<Steps>
<Step>

### Create your account

Sign up at [atribu.app](https://www.atribu.app) and create your first workspace. A workspace is your top-level container -- most businesses need just one. Inside each workspace, you create a **profile** for each brand or client you manage.

</Step>
<Step>

### Install the tracking script

Add a small JavaScript snippet to your website. This is how Atribu sees who visits your site, which pages they view, and where they came from (Google, Instagram, a specific ad, etc.). It works like Google Analytics but is purpose-built for attribution.

[Install the tracker](/docs/getting-started/install-tracker)

</Step>
<Step>

### Connect your ad platforms

Link your Meta (Facebook and Instagram) or Google Ads accounts. Atribu pulls in your campaigns, ad sets, individual ads, and daily spend data automatically. This is what makes it possible to calculate your return on ad spend (ROAS) -- the ratio of revenue earned to money spent on ads.

[Connect ad platforms](/docs/getting-started/connect-ads)

</Step>
<Step>

### Connect your payment provider

Link Stripe or MercadoPago so Atribu knows when actual money comes in. Every payment is matched back to the ad that originally brought the customer to your site. This is the key difference between Atribu and basic analytics tools: you see real revenue, not just clicks.

[Connect payments](/docs/getting-started/connect-payments)

</Step>
<Step>

### Connect your CRM (optional)

If you use GoHighLevel, connect it to pull in contacts, deals, appointments, and pipeline stages. This lets you track the full journey from ad click to booked appointment to closed deal.

[Connect GoHighLevel](/docs/getting-started/connect-crm)

</Step>
</Steps>

## What happens next

Once your integrations are connected and the tracker is installed, data starts flowing into your dashboard within minutes. You will see:

- **Visitors and sessions** from your tracking script
- **Ad spend and performance** from your connected ad platforms
- **Revenue and ROAS** once payments start coming through
- **Full customer journeys** showing every touchpoint from first ad click to payment

The more data sources you connect, the more complete your attribution picture becomes.

To understand how Atribu assigns credit to different touchpoints, see [Attribution Models](/docs/concepts/attribution-models). For details on how anonymous visitors become known customers, see [Identity Resolution](/docs/concepts/identity-resolution).

## Next steps

<Cards>
  <Card title="Install the Tracker" href="/docs/getting-started/install-tracker">
    Add the tracking script to your website in under 5 minutes
  </Card>
  <Card title="Connect Ad Platforms" href="/docs/getting-started/connect-ads">
    Link Meta and Google Ads to import campaigns and spend
  </Card>
  <Card title="Connect Payments" href="/docs/getting-started/connect-payments">
    Link Stripe or MercadoPago to track real revenue
  </Card>
  <Card title="Connect GoHighLevel" href="/docs/getting-started/connect-crm">
    Sync contacts, deals, and appointments from your CRM
  </Card>
</Cards>

---

# Export Allowlist

In Healthcare Mode, Atribu uses an **allowlist** — not a blocklist — to control which conversion events are sent to ad platforms. If an event type is not on the allowlist, it is blocked from export. Period.

## Why allowlist, not blocklist

A blocklist says "send everything except these." An allowlist says "send nothing except these." For healthcare data, the allowlist model is the only safe approach because:

- New event types are blocked by default until explicitly reviewed
- You cannot accidentally expose PHI by forgetting to add something to a blocklist
- The mental model is simple: if you didn't check it, it doesn't go out

<Callout type="warn" title="Blocklist fields are hidden in HIPAA mode">
When Healthcare Mode is active, the blocklist fields (event path blocklist, parameter blocklist) are completely hidden from the UI. They are mutually exclusive with the allowlist — you cannot have both.
</Callout>

## Default allowlist

When you first enable HIPAA mode, two event types are pre-approved:

| Event Type | Why It's Safe |
|-----------|---------------|
| **Payment Received** | Revenue data with hashed identifiers. No health condition information in the payload. |
| **Lead Created** | Contact submission with hashed email/phone. PII is stripped; no diagnosis or treatment context. |

Everything else — including appointment_booked, closed_won, checkout_started, order_placed, and any custom events — is **blocked by default**.

## Configuring the allowlist

1. Go to **Conversion Sync > Privacy & Compliance**
2. In Healthcare Mode, the allowlist section shows checkboxes for each conversion definition
3. Check the event types you want to allow for export
4. Uncheck any you want to block
5. Click **Save**

Each checkbox corresponds to a `conversion_key` in your profile's conversion definitions. The allowlist is stored in `privacy_config.healthcare_export_allowlist`.

## What happens to blocked events

Blocked events are:

- **Still stored internally** — attribution, dashboards, and reports are unaffected
- **Recorded in the export ledger** — with status `skipped` and reason `healthcare_allowlist_blocked`
- **Visible in the Conversion Sync feed** — you can see exactly which events were blocked and why
- **Not sent to any ad platform** — Meta, Google Ads, or any future destination

## Pipeline stage transitions

Pipeline stage transitions (CRM pipeline changes like "Qualified" or "Booked") are **always blocked** in Healthcare Mode, regardless of the allowlist. They cannot be checked or unchecked.

**Why:** Stage transitions often contain pipeline names, deal descriptions, or stage labels that could reveal health conditions (e.g., "Dental Implant Consultation — Qualified").

## Per-event-type review guide

Before adding an event type to the allowlist, ask:

| Question | If Yes... |
|----------|-----------|
| Could the event name or payload reveal a health condition? | Do not add to allowlist |
| Does the event include free-text fields (notes, descriptions)? | Do not add to allowlist |
| Does the event include appointment type or service name? | Do not add — may reveal treatment |
| Is the event purely financial (amount + currency)? | Generally safe to add |
| Is the event a simple form submission (email/phone only)? | Generally safe — PII is hashed before export |

## Interplay with PII stripping

Even for events on the allowlist, Healthcare Mode still strips:

- Client IP address
- User agent string
- HTTP referrer
- Page title
- External IDs (fbclid, gclid, fbc, fbp)
- URL path (reduced to origin only)
- Meta LDU flags injected

The allowlist controls **which event types** go out. PII stripping controls **what fields** are in those events. Both layers work together.

---

# Incident Response

This document covers what to do if Protected Health Information (PHI) is sent to an ad platform or otherwise exposed outside the intended scope.

## Severity levels

| Level | Description | Response Time |
|-------|-------------|---------------|
| P1 — Confirmed breach | PHI confirmed sent to Meta/Google | Within 1 hour |
| P2 — Suspected breach | Possible PHI exposure, not confirmed | Within 4 hours |
| P3 — Policy violation | Healthcare account misconfigured, no confirmed PHI sent | Within 24 hours |

## Immediate containment (P1/P2)

<Steps>

<Step>
### Stop all exports (within 15 minutes)

Disable exports for the affected profile:
- Go to **Settings > Integrations** and disconnect Meta/Google destinations, OR
- Go to **Conversion Sync > Destinations** and disable all destination configs, OR
- Set `export_meta_enabled = false` and `export_google_enabled = false` in profile_attribution_settings

If not already active, switch privacy mode to HIPAA.
</Step>

<Step>
### Assess scope (within 1 hour)

Query the conversion_exports ledger to determine:
- How many events were sent during the exposure window
- What data was in the payloads (check `privacy_mode_snapshot.mode` — if `"standard"`, payloads may contain unfiltered PII)
- How many individuals were affected
- Which PHI categories were involved (names, emails, health conditions, appointment types)

Check `payload_redacted` — if it contains full data (not `{redacted: true, hipaa_mode: true}`), PHI may be in the audit log.
</Step>

<Step>
### Document findings

Record:
- Timeline: when misconfiguration started, when detected, when contained
- Affected profiles and workspaces
- Number of events sent with PHI
- Categories of PHI exposed
- Destinations that received the data
- Whether the data can be deleted from the destination
</Step>

</Steps>

## HIPAA breach notification rules

Under the HIPAA Breach Notification Rule (45 CFR 164.400-414):

### 500+ individuals affected

| Notify | Deadline |
|--------|----------|
| HHS (Department of Health and Human Services) | Within 60 days of discovery |
| State attorney general (affected states) | Within 60 days |
| Affected individuals | Within 60 days, by mail or email |

### Fewer than 500 individuals

| Notify | Deadline |
|--------|----------|
| HHS | Annual log submission by March 1 of the following year |
| Affected individuals | Within 60 days |

## Atribu's obligations as Business Associate

Under the BAA (Annex F, Section F-6), Atribu must:
1. Report the breach to the Customer (Covered Entity) without unreasonable delay
2. Provide: nature of incident, categories of affected data, likely consequences, mitigation steps
3. The Customer is responsible for downstream notifications to HHS and individuals

<Callout type="error" title="Atribu reports to the customer, not to HHS directly">
As a Business Associate, Atribu's notification obligation is to the Covered Entity (your customer). The customer then determines whether to notify HHS, state authorities, and affected individuals.
</Callout>

## Post-incident review

After containment and notification:

1. **Root cause analysis** — Why was the profile in standard mode? Was Healthcare Mode disabled accidentally? Did the onboarding flow miss the healthcare use case?
2. **Preventive measures** — Should the workspace be in Healthcare Agency Mode? Are there other profiles that need migration?
3. **System improvements** — Update detection logic, review allowlist defaults, consider additional safeguards
4. **Documentation** — Record the incident in audit_log_events, update legal compliance notes, file per company policy

---

# Migrating to Healthcare Mode

This guide covers switching an existing profile from standard mode to HIPAA mode, including what changes, what doesn't, and how to roll back if needed.

## Pre-migration checklist

Before switching:

- [ ] List all active signal rules and their event types
- [ ] Identify which rules send non-revenue events (appointment_booked, closed_won, etc.)
- [ ] Inform the customer that some exports will be blocked after migration
- [ ] Confirm the customer's legal team is aware of the DPA/BAA
- [ ] Record current export volume in Conversion Sync > Deliveries for comparison

## Migration steps

<Steps>

<Step>
### Audit current state

Note the current setup:
- Privacy mode (should be `standard`)
- Active signal rules and their source event types
- Recent export volume and patterns in the Conversion Sync feed
- Which destinations are active (Meta, Google)
</Step>

<Step>
### Accept the DPA/BAA

Navigate to **Conversion Sync > Privacy & Compliance** and switch privacy mode to HIPAA. The DPA/BAA acceptance modal appears. Scroll through and accept.

This sets: BAA Approved, HIPAA Eligible, Signed, all acknowledgements checked.
</Step>

<Step>
### Configure the allowlist

The privacy panel now shows allowlist checkboxes instead of blocklist fields.

Review each conversion definition carefully:

| Event Type | Recommendation |
|-----------|----------------|
| Payment Received | Safe — revenue data, no PHI |
| Lead Created | Generally safe — PII is hashed before export |
| Appointment Booked | Review carefully — may reveal health conditions |
| Closed Won | Review carefully — deal names may contain health context |
| Checkout Started | Depends on what is being purchased |
| Order Placed | Depends on product names |

Only check event types you are confident do not leak PHI.
</Step>

<Step>
### Verify exports

Wait for new events to flow through, then check Conversion Sync > Deliveries:
- Allowed event types: `status: sent`
- Blocked event types: `status: skipped`, reason: `healthcare_allowlist_blocked`
- All sent events should show privacy filters in `filters_applied`
</Step>

<Step>
### Communicate to the customer

Inform them:
- Which event types are now exported vs. blocked
- Internal attribution and reporting are unaffected
- The dashboard now shows a green "HIPAA Active" badge
- Pipeline stage transitions are no longer exported
</Step>

</Steps>

## Impact on existing data

| What | Impact |
|------|--------|
| Historical events in events_enriched | No change — already stored |
| Historical conversion_exports | No change — already sent/skipped |
| Active signal rules | Still active, but gated by the allowlist |
| Pipeline stage transition rules | Blocked from exporting (rules still saved) |
| Attribution data | No change — internal attribution is unaffected |
| Dashboard metrics | No change — reads from views/sessions, not exports |

## Rollback

If you need to switch back:

1. Go to **Conversion Sync > Privacy & Compliance**
2. Switch privacy mode from HIPAA to Standard
3. Blocklist fields reappear, allowlist checkboxes disappear
4. The legal gate is no longer enforced
5. All event types become exportable again

<Callout type="warn" title="Rolling back removes all HIPAA protections">
Only switch back if the profile no longer handles healthcare data. The DPA/BAA acceptance record is preserved in the legal compliance table for audit purposes.
</Callout>

## Bulk migration for agencies

1. Enable **Healthcare Agency Mode** in Workspace Settings > Compliance
2. This does not auto-switch existing profiles — each must be migrated individually
3. Use the compliance overview table to track progress
4. New profiles created after enabling agency mode default to HIPAA

---

# Healthcare Onboarding

This guide walks you through enabling Healthcare Mode, accepting the legal agreements, configuring the export allowlist, and verifying that your setup is correct.

## Prerequisites

Before you begin:

- You have an Atribu workspace with at least one profile
- You have connected at least one ad platform (Meta or Google Ads)
- You have owner or admin access to the workspace

## Activation

<Steps>

<Step>
### Enable Healthcare Mode

There are two ways to activate:

**Option A — Via Conversion Sync (recommended for first-time setup):**
Navigate to the profile's Conversion Sync page. If the profile is in standard mode, a Healthcare Intercept card appears asking "Do you handle healthcare data?" Click **Yes, enable Healthcare Mode**.

**Option B — Via Settings:**
Go to Settings > Compliance and toggle **Enable Healthcare Mode** on.

Both paths open the DPA/BAA acceptance modal.
</Step>

<Step>
### Accept the DPA and BAA

The modal shows the full text of:
1. **Part I — Data Processing Agreement** — how Atribu processes your data
2. **Part II — HIPAA Business Associate Agreement (Annex F)** — HIPAA-specific obligations

Scroll through the full text, then click **I Accept**. This records:
- BAA status: Approved
- HIPAA eligibility: Eligible
- Signature status: Signed (with timestamp and user ID)
- All three compliance acknowledgements checked automatically
</Step>

<Step>
### Configure the export allowlist

After acceptance, the Privacy & Compliance panel switches from blocklist fields to allowlist checkboxes. Each checkbox represents a conversion definition in your profile.

**Default allowlist:** Only **Payment Received** and **Lead Created** are pre-checked.

Review each event type and only check the ones you are confident do not contain PHI in their payloads. Save when done.
</Step>

<Step>
### Create signal rules

Create signal rules as normal in the rule wizard. Note these HIPAA-specific behaviors:

- Non-revenue event types show an amber warning
- Pipeline stage transitions show a red warning (blocked from export)
- Privacy overrides in the rule wizard are locked — all strip toggles are enforced by the HIPAA floor
</Step>

<Step>
### Verify with a test send

Use the test send feature in Conversion Sync > Deliveries to confirm:
- Events reach the ad platform successfully
- The delivery detail shows privacy filters applied (IP, UA, referrer, external IDs stripped)
- The dashboard shows a green **HIPAA Active** badge
</Step>

</Steps>

## Agency workflow

For agencies managing multiple healthcare clients:

1. Go to **Workspace Settings > Compliance**
2. Enable **Healthcare Agency Mode** — new profiles will default to HIPAA
3. The compliance overview table shows all profiles' HIPAA status at a glance
4. Each profile must still individually accept the DPA/BAA
5. Use the table links to jump directly to each profile's Conversion Sync settings

<Callout type="info" title="Existing profiles are not auto-switched">
Enabling Healthcare Agency Mode only affects newly created profiles. Existing profiles must be migrated individually. See the [migration guide](/docs/healthcare/migration).
</Callout>

---

# Healthcare Mode

Atribu's Healthcare Mode enables HIPAA-compliant attribution for dental, medical, mental health, and other healthcare businesses that advertise online. It protects Protected Health Information (PHI) while still allowing you to measure which ads drive real revenue.

## Why Healthcare Mode exists

Meta and Google ban healthcare ad accounts that send health-related data through their conversion APIs. Healthcare Mode ensures that only safe, scrubbed conversion signals reach ad platforms — while Atribu still tracks the full customer journey internally for your attribution reports.

## How it works

Healthcare Mode changes the export behavior from **blocklist** (send everything, block specific things) to **allowlist** (block everything, only send what you explicitly approve).

| Layer | Standard Mode | Healthcare Mode |
|-------|--------------|-----------------|
| Internal storage | All events stored | All events stored (unchanged) |
| Export policy | Blocklist — send all, block specific paths/params | Allowlist — block all, send only approved event types |
| PII stripping | Configurable per field | All fields stripped (IP, UA, referrer, page title, external IDs) |
| Meta LDU | Optional | Always enabled |
| URL handling | Full URL sent | Origin only (path stripped) |
| Stage transitions | Exportable | Blocked from export |
| Legal gate | Optional | Required — BAA must be signed before any export |

## Legal stack

Healthcare Mode includes a complete legal stack:

- **Data Processing Agreement (DPA)** — governs how Atribu processes personal data on your behalf. [Read the full DPA](/dpa).
- **HIPAA Business Associate Agreement (Annex F)** — the BAA annex that applies when you handle PHI. [Read Annex F](/dpa/baa).
- **Click-wrap acceptance** — accept both documents electronically when enabling Healthcare Mode.

## Who should use Healthcare Mode

Enable Healthcare Mode if your business (or your agency's client):

- Is a healthcare provider (dental, medical, chiropractic, mental health, etc.)
- Is a health plan or insurance entity
- Handles Protected Health Information in any form
- Runs ads where conversion data could reveal health conditions (e.g., booking a dental implant consultation)

<Callout type="warn" title="When in doubt, enable it">
The only downside of Healthcare Mode is reduced ad optimization signal density. The upside is avoiding a HIPAA violation or Meta account ban.
</Callout>

## What's next

- [Set up Healthcare Mode](/docs/healthcare/onboarding) — step-by-step activation guide
- [Configure the export allowlist](/docs/healthcare/export-allowlist) — choose which events reach ad platforms
- [Migrate existing accounts](/docs/healthcare/migration) — switch from standard to HIPAA mode

---

# Healthcare Support Runbook

## "Why is my export blocked?"

This is the most common question from healthcare accounts. Here's how to diagnose it.

<Steps>

<Step>
### Check the Conversion Sync feed

Go to **Conversion Sync > Deliveries** and find the blocked event. The status column shows `skipped` and the detail panel shows the skip reason.
</Step>

<Step>
### Read the skip reason

| Skip Reason | What It Means | Fix |
|-------------|---------------|-----|
| `healthcare_allowlist_blocked` | Event type not on HIPAA allowlist | Add it to the allowlist in Privacy & Compliance, or explain why it should stay blocked |
| `healthcare_stage_transition_blocked` | Pipeline stages can't be exported in HIPAA mode | By design — use conversion events instead |
| `healthcare_missing_conversion_key` | Signal rule missing a valid conversion definition | Re-create the rule with a proper conversion definition source |
| `legal_gate_blocked` | BAA/legal requirements not met | Complete legal setup: BAA Approved + HIPAA Eligible |
| `legal_compliance_missing` | No legal record exists | Customer needs to accept the DPA/BAA |
| `hipaa_baa_unsigned` | BAA approved but not signed | Complete the signature workflow |
</Step>

<Step>
### Verify the setup

Confirm these three things:
1. **Privacy mode** is `hipaa` (not `standard` with blocklists)
2. **Legal panel** shows BAA Approved, HIPAA Eligible, all acknowledgements checked
3. **Allowlist** has the expected event types checked
</Step>

</Steps>

## Common mistakes

### Customer switched back to standard mode

If a healthcare customer accidentally switches from HIPAA to standard mode:
- Blocklist fields reappear, allowlist disappears
- No event-type gating occurs
- PII may be sent unfiltered

**Fix:** Switch back to HIPAA in Privacy & Compliance. The allowlist configuration is preserved.

### Rules created before HIPAA mode was enabled

Signal rules created in standard mode are not retroactively blocked. After enabling HIPAA mode:
- Existing rules are subject to the allowlist gate on the next export cycle
- No action needed — the pipeline checks the allowlist at export time, not rule creation time

## Verifying privacy filters

1. Go to **Conversion Sync > Deliveries** and click on a sent event
2. In the detail panel, check:
   - `filters_applied` should include: `ip_removed`, `user_agent_removed`, `url_sanitized`, `external_id_removed:fbc`, etc.
   - `privacy_mode_snapshot.mode` should be `"hipaa"`
   - `legal_gate_snapshot` should show `allowed: true`
3. If `payload_redacted` shows `{ redacted: true, hipaa_mode: true }` — this confirms raw PHI was not stored in the audit log

## What gets stripped in HIPAA mode

| Field | Stripped? | Notes |
|-------|-----------|-------|
| Client IP | Yes | Removed from Meta user_data |
| User agent | Yes | Removed from Meta user_data |
| HTTP referrer | Yes | Removed from custom_data |
| Page title | Yes | Removed from custom_data |
| External IDs (fbclid, gclid, fbc, fbp) | Yes | Removed from Meta user_data |
| URL path | Yes | Reduced to origin only |
| Hashed email (Meta) | Sent | For ad matching — hashed, not plaintext |
| Hashed phone (Meta) | Sent | For ad matching — hashed, not plaintext |
| Hashed email (Google) | Stripped | When strip_external_ids is active |
| Hashed phone (Google) | Stripped | When strip_external_ids is active |
| Meta LDU flags | Injected | Always in HIPAA mode |

## Escalation matrix

| Severity | Trigger | Response |
|----------|---------|----------|
| P1 | PHI confirmed sent to Meta/Google | Disable exports immediately. See [incident response](/docs/healthcare/incident-response) |
| P2 | Healthcare account in standard mode with active exports | Switch to HIPAA. Audit recent exports |
| P3 | Legal setup incomplete, exports blocked | Guide through [onboarding](/docs/healthcare/onboarding) |
| P4 | Allowlist configuration question | Explain the [allowlist](/docs/healthcare/export-allowlist) |

---

Atribu connects your ad platforms, CRM, and payment providers to show you exactly which ads drive revenue. Stop guessing which campaigns work — see the full picture from ad click to payment.

<VideoEmbed src="/docs/videos/what-is-atribu.mp4" title="What is Atribu — 30 second overview" />

## Get started

<Cards>
  <Card title="Get started in 5 steps" href="/docs/getting-started/overview">
    Create your account, connect your data sources, install the tracker, and see your first attribution data
  </Card>
  <Card title="Install tracking" href="/docs/getting-started/install-tracker">
    Add the Atribu tracking script to your website in under 2 minutes
  </Card>
  <Card title="Connect ad platforms" href="/docs/getting-started/connect-ads">
    Connect Meta (Facebook/Instagram) and Google Ads to sync campaign data
  </Card>
  <Card title="Connect payments" href="/docs/getting-started/connect-payments">
    Connect Stripe or MercadoPago to track revenue attribution
  </Card>
</Cards>

## Features

<Cards>
  <Card title="Dashboard" href="/docs/features/dashboard">
    Your marketing command center — KPIs, charts, traffic sources, top campaigns
  </Card>
  <Card title="Ads Performance" href="/docs/features/ads-performance">
    Rank campaigns by ROAS, drill into ad-level metrics (CTR, CPC, CPM, CAC)
  </Card>
  <Card title="Customer Journeys" href="/docs/features/customer-journey">
    See the full path from ad click to payment for every customer
  </Card>
  <Card title="Ads Explorer" href="/docs/features/ads-explorer">
    Spy on competitor ads — see what they're running and for how long
  </Card>
  <Card title="Ads Lab" href="/docs/features/ads-lab">
    AI-powered ad creative generator — create copy and scripts from your winning ads
  </Card>
  <Card title="Calendar" href="/docs/features/calendar">
    See appointments and conversions on a unified timeline
  </Card>
</Cards>

## Concepts

<Cards>
  <Card title="Attribution Models" href="/docs/concepts/attribution-models">
    8 models for assigning credit — last touch, first touch, linear, and more
  </Card>
  <Card title="Revenue Types" href="/docs/concepts/revenue-types">
    Cash vs pipeline vs gross — why only confirmed payments count for ROAS
  </Card>
  <Card title="Identity Resolution" href="/docs/concepts/identity-resolution">
    How anonymous visitors become known customers across devices and channels
  </Card>
</Cards>

## API

<Cards>
  <Card title="API Quickstart" href="/docs/api/quickstart">
    Make your first API call in 2 minutes
  </Card>
  <Card title="API Reference" href="/docs/api/overview">
    Full endpoint documentation — analytics, campaigns, conversions, customers
  </Card>
</Cards>

## For AI agents

| Resource | Description |
|----------|-------------|
| [`/llms.txt`](/llms.txt) | Structured markdown index of all documentation |
| [`/llms-full.txt`](/llms-full.txt) | Complete documentation as a single markdown file |
| [`/api/v1/openapi.json`](/api/v1/openapi.json) | OpenAPI 3.1 specification |

---

# GoHighLevel (GHL)

The GoHighLevel integration brings your CRM data into Atribu. It syncs contacts, opportunities (deals), and calendar appointments so you can track every step of your sales funnel -- from ad click to closed deal.

---

## What you get

- Contacts synced with Atribu's identity graph (name, email, phone)
- Opportunities with deal values, pipeline stages, and UTM attribution data
- Calendar appointments merged into Atribu's Calendar view
- Full-funnel tracking from first ad click through to closed deal

---

## Connecting GoHighLevel

<Steps>
<Step>

## Open Integrations

Go to **Settings > Integrations** in your Atribu workspace.

</Step>
<Step>

## Start the connection

Click **Connect** next to the GoHighLevel card. You will be redirected to the GHL Marketplace.

</Step>
<Step>

## Select your location

Choose the GHL location (sub-account) you want to connect. Each location connects to one Atribu profile.

</Step>
<Step>

## Authorize permissions

Grant Atribu the requested permissions:

- **Contacts** -- read contact data (name, email, phone)
- **Opportunities** -- read deal data (stage, value, attribution)
- **Locations** -- identify your sub-account
- **Calendars & Events** -- read appointment data

</Step>
<Step>

## Verify the connection

Back in Atribu, the GHL card should show **Connected**. Atribu will begin importing your contacts and opportunities.

</Step>
</Steps>

---

## What syncs

### Contacts

Every GHL contact is linked to Atribu's identity graph using email and phone number. This creates a unified customer profile that connects the contact to their website visits, ad clicks, and payments.

| Data | Description |
|------|-------------|
| **Name** | First and last name |
| **Email** | Primary email address |
| **Phone** | Primary phone number |
| **Source** | Where the contact came from (form, ad, referral) |

### Opportunities (deals)

GHL opportunities carry rich attribution data that Atribu reads to connect deals back to ad campaigns.

| Data | Description |
|------|-------------|
| **Pipeline stage** | Current stage (e.g., New Lead, Qualified, Closed Won) |
| **Deal value** | Monetary value of the opportunity |
| **Contact** | Linked contact with identity data |
| **UTM data** | Campaign, source, medium, and click IDs from the lead's origin |
| **Stage history** | Timeline of stage changes |

### Calendar events

GHL appointments are synced and displayed in Atribu's Calendar view, merged with tracked events from your website.

| Data | Description |
|------|-------------|
| **Appointment time** | Start and end time |
| **Contact** | Linked contact information |
| **Assigned user** | Team member responsible |
| **Calendar name** | Which calendar the appointment belongs to |
| **Status** | Confirmed, cancelled, no-show, etc. |

---

## Pipeline stage mapping

GHL organizes deals into pipelines with stages. For example:

```txt title="Example sales pipeline"
New Lead  -->  Qualified  -->  Proposal Sent  -->  Closed Won
```

Atribu maps each stage to a conversion type so it can track your funnel:

| Stage keyword | Conversion type |
|---------------|----------------|
| Contains "lead" or is the first stage | `lead_created` |
| Contains "appointment" or "booked" | `appointment_booked` |
| Contains "won" or "closed" | `closed_won` |

You can customize this mapping in **Settings > Outcomes** to match your specific pipeline stages.

---

## Pipeline values vs real revenue

<Callout type="warn" title="Pipeline values are NOT used for ROAS">
GHL deal values represent what you **expect** to earn -- not what you have actually collected. A $5,000 deal in your pipeline might close for $3,000, get renegotiated, or fall through entirely.

Atribu tracks pipeline values separately as **pipeline revenue** and displays them in their own section of the dashboard. They are **never** included in ROAS calculations. Only confirmed payments from Stripe or MercadoPago count for ROAS.
</Callout>

---

## Calendar merge

GHL appointments appear in Atribu's Calendar view alongside events tracked from your website. Atribu merges data from both sources:

- **GHL data**: real-time appointment status, calendar name, assigned team member
- **Atribu data**: attribution information (which ad campaign brought this customer)

When both sources have data for the same appointment, they are merged into a single entry with the best of both worlds.

---

## UTM attribution from GHL

When a lead comes from a Meta or Google ad, GHL captures the campaign information from the click. Atribu reads this data to attribute the lead back to the correct campaign, ad set, and ad.

This means that even if a lead fills out a form directly on Facebook (using a Meta Lead Form) and goes straight into GHL without visiting your website, Atribu can still identify which ad campaign generated the lead.

<Callout type="info" title="Synthetic touches for off-site leads">
When a lead enters GHL from a Meta lead form (not from your website), there is no website visit to track. Atribu automatically creates **synthetic touches** -- virtual touchpoints that represent the ad interaction. This ensures these leads are included in your attribution reports, even without a website session.

Learn more in [Synthetic Touches](/docs/concepts/synthetic-touches).
</Callout>

---

## Troubleshooting

### Contacts are missing names

Some GHL contacts are created through opportunities rather than the contacts list. In these cases, the name may not be available during the initial sync. Atribu will backfill names as more data becomes available from subsequent syncs.

### Opportunities are not syncing

- Check that the connection status shows **Connected** in **Settings > Integrations**
- Make sure the GHL location has at least one pipeline with opportunities
- If you recently added a new pipeline, trigger a manual sync

### Appointments are not showing in Calendar

Calendar sync requires the **Calendars** and **Calendar Events** permissions. If these were not granted during setup:

1. Go to **Settings > Integrations**
2. Disconnect GoHighLevel
3. Reconnect and make sure all permissions are granted

---

## Related

<Cards>
  <Card title="Revenue Types" href="/docs/concepts/revenue-types">
    Why pipeline values are tracked separately from cash revenue
  </Card>
  <Card title="Calendar" href="/docs/features/calendar">
    See GHL appointments with attribution data on a visual timeline
  </Card>
  <Card title="Conversion Goals" href="/docs/settings/goals">
    Customize how pipeline stages map to conversion types
  </Card>
</Cards>

---

# Google Ads

The Google Ads integration connects your search, display, and video campaigns to Atribu. It syncs your full campaign hierarchy with daily performance data and lets you export conversion events back to Google for bid optimization.

---

## What you get

- Full campaign structure: campaigns, ad groups, and individual ads
- Daily spend, impressions, and clicks for every ad
- Automatic traffic classification -- visits from Google Ads are labeled **Paid Search**
- Conversion export so Google can optimize your bids toward real business outcomes

---

## Connecting Google Ads

<Steps>
<Step>

## Open Integrations

Go to **Settings > Integrations** in your Atribu workspace.

</Step>
<Step>

## Start the connection

Click **Connect** next to the Google Ads card. You will be redirected to Google.

</Step>
<Step>

## Sign in with Google

Sign in with the Google account that has access to your Google Ads accounts. Grant Atribu permission to read and manage your ad data.

<Callout type="info" title="Offline access">
Atribu requests offline access so it can sync data in the background without requiring you to be logged in.
</Callout>

</Step>
<Step>

## Select your customer account

Choose which Google Ads customer account (also called a CID) you want to sync. If you use a Manager Account (MCC), you will see all linked accounts.

</Step>
</Steps>

---

## What syncs

| Data | Description |
|------|-------------|
| **Campaigns** | Campaign name, status, type (Search, Display, Video, Performance Max) |
| **Ad Groups** | Ad group name, targeting, bid strategy |
| **Ads** | Ad copy, headlines, descriptions, final URLs |
| **Daily Spend** | Spend amount, impressions, clicks per day |

Performance metrics like CPC and CTR are calculated in real time from the raw data rather than stored as static numbers.

---

## Google Ads conversion export

Atribu can send conversion events back to Google Ads. This tells Google which ad clicks led to actual business outcomes (leads, appointments, purchases), so its Smart Bidding algorithms can optimize toward the results that matter to your business.

For example, if most of your paying customers come from a specific keyword group, Google will learn to bid more aggressively on those keywords.

---

## Click ID tracking

Google automatically adds a `gclid` parameter to the URL when someone clicks your ad:

```txt title="Example URL with gclid"
https://yoursite.com/landing-page?gclid=Cj0KCQj...
```

Atribu detects this click ID and uses it to:

1. Classify the visit as **Paid Search** in your traffic breakdown
2. Link the visitor to the specific Google Ads campaign and ad group
3. Track whether that visitor later converts

You do not need to set up UTM parameters manually -- `gclid` handling is automatic.

---

## Troubleshooting

### I do not see my ad accounts

If your Google Ads accounts do not appear during setup:

- Make sure the Google account you signed in with has **Standard** or **Admin** access to the Google Ads account
- If you use a Manager Account (MCC), check that the account is linked and active
- Try signing out of Google and signing back in with the correct account

### Data is not syncing

If your campaigns appear but spend data is missing or outdated:

1. Go to **Settings > Integrations** and check the connection status
2. If the status shows an error, try disconnecting and reconnecting
3. Google Ads data is typically available within a few hours of each day ending

---

## Related

<Cards>
  <Card title="Install the Tracker" href="/docs/getting-started/install-tracker">
    Required for connecting ad clicks to website visits
  </Card>
  <Card title="Ads Performance" href="/docs/features/ads-performance">
    See your Google Ads campaigns ranked by ROAS
  </Card>
  <Card title="Channel Classification" href="/docs/concepts/channels">
    How Google Ads traffic is classified as Paid Search
  </Card>
  <Card title="Conversion Sync" href="/docs/features/conversion-sync">
    Send conversions back to Google Ads for better optimization
  </Card>
</Cards>

---

# Google Search Console

The Google Search Console (GSC) integration shows you which search queries bring visitors to your site organically -- without paid ads. This helps you understand your SEO performance alongside your paid campaigns.

---

## What you get

- Search queries that drive organic traffic to your site
- Performance metrics: impressions, clicks, CTR, and average position per query
- Visibility into which keywords bring free traffic vs paid traffic

---

## Connecting Google Search Console

<Steps>
<Step>

## Open Integrations

Go to **Settings > Integrations** in your Atribu workspace.

</Step>
<Step>

## Start the connection

Click **Connect** next to the Google Search Console card. You will be redirected to Google.

</Step>
<Step>

## Sign in with Google

Sign in with the Google account that has access to your Search Console properties. Grant Atribu permission to read your search data.

</Step>
<Step>

## Select your property

Choose the Search Console property (website) you want to sync. This should match the domain you are tracking with Atribu.

<Callout type="info" title="Property types">
If you have both a **domain property** (e.g., `example.com`) and a **URL-prefix property** (e.g., `https://www.example.com/`), choose the domain property for the most complete data.
</Callout>

</Step>
</Steps>

---

## What syncs

| Data | Description |
|------|-------------|
| **Search queries** | The actual keywords people search for on Google |
| **Impressions** | How many times your site appeared in search results for each query |
| **Clicks** | How many times someone clicked through to your site |
| **CTR** | Click-through rate (clicks / impressions) |
| **Average position** | Where your site typically ranks for each query (1 = top result) |

---

## Where the data appears

Search Console data appears in the **keyword breakdown** section of your dashboard. This gives you a clear picture of which search terms bring organic visitors, separate from your paid traffic sources.

<Callout type="info" title="Organic vs Paid Search">
Google Search Console shows your **organic** (free) search traffic. This is separate from **Google Ads**, which shows your paid search campaigns. Together, they give you a complete picture of your Google search presence -- both the traffic you pay for and the traffic you earn through SEO.
</Callout>

---

## Troubleshooting

### No data is showing

Google Search Console data can take 2-3 days to become available -- this is a limitation of Google's reporting, not Atribu. If your property was recently verified, wait a few days for data to accumulate.

### I do not see my property

- Make sure you are signed in with the Google account that owns or has access to the Search Console property
- Verify that the property is set up and verified in [Google Search Console](https://search.google.com/search-console)
- If you manage multiple properties, check that you selected the correct one during setup

### Data seems incomplete

Google Search Console does not report queries with very low volume for privacy reasons. If a query generated only one or two clicks, it may not appear in the data. This is normal behavior from Google's side.

---

## Related

<Cards>
  <Card title="Traffic Sources" href="/docs/features/traffic-sources">
    See keyword data in your dashboard breakdown boards
  </Card>
  <Card title="Channel Classification" href="/docs/concepts/channels">
    How organic search traffic is classified and tracked
  </Card>
</Cards>

---

# MercadoPago

The MercadoPago integration tracks payments from Latin American customers. If your business accepts payments through MercadoPago, Atribu records every transaction and attributes revenue to the ad campaigns that brought those customers.

---

## What you get

- Every MercadoPago payment recorded as a `payment_received` conversion
- Revenue attributed to specific ad campaigns for accurate ROAS
- Multi-currency support -- payments in MXN, BRL, ARS, COP, and other currencies are normalized to your reporting currency
- Customer identity matching via email addresses

---

## Connecting MercadoPago

<Steps>
<Step>

## Open Integrations

Go to **Settings > Integrations** in your Atribu workspace.

</Step>
<Step>

## Start the connection

Click **Connect** next to the MercadoPago card. You will be redirected to MercadoPago.

</Step>
<Step>

## Authorize on MercadoPago

Sign in with your MercadoPago account and authorize Atribu to receive payment notifications.

</Step>
<Step>

## Verify the connection

Back in Atribu, the MercadoPago card should show **Connected**. Payments will start flowing in automatically via webhooks.

</Step>
</Steps>

---

## How it works

The MercadoPago integration works the same way as Stripe:

1. **MercadoPago sends a webhook** to Atribu when a payment is completed
2. **Atribu creates a conversion** -- a `payment_received` event is recorded with the payment amount and currency
3. **The identity graph links the payment** -- Atribu matches the payer's email to any previous visits on your site
4. **Attribution is computed** -- Atribu traces back to the original ad click and assigns revenue credit

---

## What data Atribu receives

| Data | Description |
|------|-------------|
| **Payment amount** | The amount charged, in the original currency |
| **Currency** | MXN, BRL, ARS, COP, CLP, PEN, UYU, USD, etc. |
| **Payer email** | Used for identity matching |

---

## Currency normalization

<Callout type="info" title="Automatic currency conversion">
MercadoPago payments often arrive in local currencies (Mexican Pesos, Brazilian Reais, Argentine Pesos, etc.). Atribu automatically normalizes these amounts to your profile's reporting currency so all your revenue numbers, ROAS, and dashboards display in a single consistent currency.
</Callout>

---

## Revenue and ROAS

Just like Stripe, MercadoPago payments are classified as **cash** revenue. This is the only type of revenue used for ROAS calculations. Your ROAS reflects actual money received from customers, not projections or estimates.

---

## Troubleshooting

### Payments are not appearing

- Check that the connection status shows **Connected** in **Settings > Integrations**
- MercadoPago webhooks may take a few seconds to a minute to arrive
- If the issue persists, disconnect and reconnect the integration

### Payments show as unattributed

Attribution requires the payer's email to match a known visitor. If the customer paid without ever visiting your tracked site or submitting their email through a form, the payment will be recorded but cannot be attributed to an ad campaign.

### Currency looks wrong on the dashboard

Make sure your profile's reporting currency is set correctly in **Settings > Profile**. Atribu converts all incoming payments to this currency using current exchange rates.

---

## Related

<Cards>
  <Card title="Revenue Types" href="/docs/concepts/revenue-types">
    Why only cash payments count for ROAS
  </Card>
  <Card title="Identity Resolution" href="/docs/concepts/identity-resolution">
    How payments are matched to ad clicks via the identity graph
  </Card>
  <Card title="Install the Tracker" href="/docs/getting-started/install-tracker">
    Required for connecting ad clicks to website visits
  </Card>
</Cards>

---

# Meta (Facebook & Instagram)

The Meta integration connects your Facebook and Instagram ad accounts to Atribu. It syncs your full campaign hierarchy with daily performance data and enables two-way data flow through the Meta Conversions API (CAPI).

---

## What you get

- Full campaign structure: campaigns, ad sets, and individual ads
- Daily spend, impressions, clicks, reach, and frequency for every ad
- Video creative source URLs for previewing ads directly in Atribu
- Automatic traffic classification -- visits from Meta ads are labeled **Paid Social**
- Conversion export via CAPI so Meta can optimize your ad delivery

---

## Connecting Meta

<Steps>
<Step>

## Open Integrations

Go to **Settings > Integrations** in your Atribu workspace.

</Step>
<Step>

## Start the connection

Click **Connect** next to the Meta (Facebook & Instagram) card. You will be redirected to Facebook.

</Step>
<Step>

## Authorize on Facebook

Sign in with the Facebook account that manages your ad accounts. Grant Atribu the requested permissions:

- **Ads Read** -- lets Atribu pull campaign data and performance metrics
- **Ads Management** -- lets Atribu send conversion data back to Meta (CAPI)
- **Pages Read Engagement** -- lets Atribu load video creatives from your Page

<Callout type="warn" title="Grant all permissions">
If you skip a permission, some features will not work. Video previews require Pages Read Engagement, and CAPI requires Ads Management.
</Callout>

</Step>
<Step>

## Select ad accounts

Choose which ad accounts you want to sync. Atribu will begin importing data immediately.

</Step>
</Steps>

---

## What syncs

| Data | Description |
|------|-------------|
| **Campaigns** | Campaign name, status, objective, budget |
| **Ad Sets** | Targeting, placement, schedule, bid strategy |
| **Ads** | Creative, copy, video IDs, thumbnail URLs |
| **Daily Spend** | Spend amount, impressions, clicks, reach, frequency per day |
| **Video Sources** | Playable video URLs for previewing creatives in Atribu |

Performance metrics like CPM, CPC, and CTR are calculated in real time from the raw data (spend, impressions, clicks) rather than stored as static values. This means they always reflect the most up-to-date numbers, even after currency normalization.

---

## Sync frequency

After connecting, Atribu runs an initial sync to import your historical data. After that, data syncs periodically throughout the day. You can also trigger a manual sync from **Settings > Integrations** at any time.

New campaigns and ads typically appear within a few hours of being created in Meta Ads Manager.

---

## Meta Conversions API (CAPI)

The Meta Conversions API sends your conversion data (leads, purchases, appointments) back to Meta. This helps Meta's algorithm understand which ad clicks lead to real business outcomes, so it can optimize your ad delivery and find more people like your best customers.

In simple terms: Atribu tells Meta "this person clicked your ad and then became a paying customer," so Meta can show your ads to similar people.

<Callout type="info" title="Why CAPI matters">
As browser privacy restrictions grow (cookie blocking, iOS tracking limits), server-side conversion data through CAPI becomes increasingly important for accurate ad optimization. Without it, Meta may not know that many of your conversions came from their ads.
</Callout>

---

## Click ID tracking

Meta automatically adds a `fbclid` parameter to the URL when someone clicks your ad. For example:

```txt title="Example URL with fbclid"
https://yoursite.com/landing-page?fbclid=AbC123xYz...
```

Atribu detects this click ID and uses it to:

1. Classify the visit as **Paid Social** in your traffic breakdown
2. Link the visitor to the specific Meta campaign, ad set, and ad they clicked
3. Track whether that visitor later converts (fills a form, makes a payment, etc.)

You do not need to set up UTM parameters manually -- `fbclid` handling is automatic.

---

## Re-authorization

<Callout type="warn" title="Re-authorization may be needed">
If Atribu adds new features that require additional Facebook permissions, you may need to re-authorize the connection. Go to **Settings > Integrations** and click **Re-authorize** next to Meta. This will show the Facebook permissions dialog again so you can grant the new permissions.
</Callout>

---

## Troubleshooting

### Video previews are not loading

Ad videos are owned by your Facebook Page, not your personal account. Atribu needs the **Pages Read Engagement** permission to access them. If you skipped this permission during setup:

1. Go to **Settings > Integrations**
2. Click **Re-authorize** next to Meta
3. Make sure all permissions are granted

### I do not see all my ad accounts

Your Facebook user account needs access to the ad accounts through Business Manager. Check that:

- You are an admin or advertiser on the ad account
- The ad account is part of a Business Manager you have access to
- You selected the correct ad accounts during the connection step

### Data seems outdated

Meta rate-limits API requests, so there may be a delay of a few hours for the latest data. You can trigger a manual sync from **Settings > Integrations** to refresh immediately.

---

## Related

<Cards>
  <Card title="Install the Tracker" href="/docs/getting-started/install-tracker">
    Required for connecting ad clicks to website visits
  </Card>
  <Card title="UTM Parameters" href="/docs/tracking/utm-parameters">
    Tag your Meta ad URLs for detailed attribution
  </Card>
  <Card title="Ads Performance" href="/docs/features/ads-performance">
    See your Meta campaigns ranked by ROAS and other metrics
  </Card>
  <Card title="Conversion Sync" href="/docs/features/conversion-sync">
    Send conversions back to Meta CAPI for better ad optimization
  </Card>
</Cards>

---

# Shopify

The Shopify integration connects your online store to Atribu. It tracks orders and customer data so you can see which ad campaigns drive the most sales.

---

## What you get

- Orders recorded as conversion events with revenue data
- Customer information (email, name) linked to Atribu's identity graph
- Attribution of e-commerce revenue to specific ad campaigns
- **Full storefront tracking** via the Atribu theme extension (engagement, scroll depth, video, forms, web vitals)
- **Checkout event tracking** via the Shopify Web Pixel (add to cart, checkout started, purchase)
- Webhooks for `orders/paid`, `orders/create`, and `checkouts/create`

---

## How the dual-track setup works

Atribu uses two complementary extensions on your Shopify store:

| Extension | Where it runs | What it captures |
|---|---|---|
| **Theme extension** (Atribu Tracker) | Storefront pages (product, collection, home, blog) | Engagement, scroll depth, video, forms, identity, web vitals, UTMs, click IDs |
| **Web Pixel** | Sandboxed checkout | `add_to_cart`, `checkout_started`, `purchase` with order value |

The theme extension gives you full behavioral analytics on your storefront. The web pixel handles the checkout, where Shopify does not allow external scripts to run directly.

Both share the same `anonymous_id` and `session_id` in localStorage, so a visitor's browsing behavior is linked to their eventual purchase.

<Callout type="info" title="Why two extensions?">
Shopify's checkout runs in a sandboxed environment that blocks external scripts. The Web Pixel API is the only way to track events there. For everything else (storefront pages), the full Atribu tracker provides much richer data than the pixel alone.
</Callout>

---

## Connecting Shopify

<Steps>
<Step>

## Open Integrations

Go to **Settings > Integrations** in your Atribu workspace.

</Step>
<Step>

## Enter your store domain

Type your Shopify store domain (e.g., `my-store` or `my-store.myshopify.com`) into the Shopify card.

</Step>
<Step>

## Authorize the app

Click **Connect**. You will be redirected to Shopify to install and authorize the Atribu app. Atribu requests:

- **Read orders** and **Read all orders** -- to track purchases and revenue
- **Read customers** -- to match buyers to ad clicks via the identity graph
- **Write pixels** -- to automatically activate the checkout pixel
- **Read customer events** -- to read pixel event data

</Step>
<Step>

## Enable storefront tracking

After connecting, click the **Enable storefront tracking** button. This opens your Shopify theme editor directly to the Atribu Tracker toggle.

1. Toggle **Atribu Tracker** ON
2. Your tracking key is pre-filled
3. Click **Save**

<Callout type="warn" title="Don't skip this step">
Without the theme extension, you only get checkout events from the web pixel. You miss all storefront engagement data (scroll depth, video views, form interactions, web vitals) that powers engagement-weighted attribution.
</Callout>

</Step>
<Step>

## Verify

Back in Atribu, the Shopify card should show **Connected**. The web pixel is activated automatically during the connection step.

Check **Settings > Customer events** in your Shopify admin to confirm the Atribu pixel shows **Connected**.

</Step>
</Steps>

---

## What syncs

| Data | Source | Description |
|---|---|---|
| **Page views** | Theme extension | Every storefront page visit with UTMs and click IDs |
| **Engagement** | Theme extension | Scroll depth, time on page, video views, click quality, web vitals |
| **Add to cart** | Web Pixel | Product added to cart with value |
| **Checkout started** | Web Pixel | Checkout initiated with subtotal |
| **Purchase** | Web Pixel + Webhook | Completed order with total value, order ID, checkout token |
| **Order details** | Webhook | Order amount, currency, customer email/name, product data |
| **Customer identity** | Theme extension + Webhook | Email from forms (via tracker `identify()`) and from orders |

---

## How attribution works

1. A customer clicks your ad and arrives on your Shopify store (with UTM params or click IDs)
2. The **Atribu theme tracker** records the visit, engagement, and captures the ad attribution data
3. If the customer fills out a form (email signup, contact form), `identify()` links their anonymous visit to their email
4. The customer makes a purchase -- the **web pixel** fires `purchase`, and the **webhook** delivers the order with customer email
5. Atribu matches the customer's email to the earlier storefront visit
6. Revenue is attributed to the ad campaign that drove the original click

### Engagement-weighted attribution

When the theme extension is enabled, Atribu computes an `engagement_quality_score` (0-100) for each storefront session. This score factors into the **engagement-weighted** attribution model, giving more credit to ad clicks that drove high-quality visits (deep scrolling, long time on page, video watched, no errors).

---

## Theme extension settings

The Atribu Tracker app embed supports these settings in the Shopify theme editor:

| Setting | Default | Description |
|---|---|---|
| Tracking Key | *(required)* | Your `trk_` key from Atribu Settings > Tracking |
| Tracking Endpoint | `https://www.atribu.app/api/tracking/collect` | Change only if using a custom domain |
| Tracker Script URL | `https://www.atribu.app/atribu-tracker.js` | Change only if self-hosting |
| Intercept Meta Pixel | ON | Mirror `fbq()` events server-side for better Meta CAPI match quality |
| Session Timeout | 30 minutes | Minutes of inactivity before a new session starts |
| Session Mode | Inactivity or source change | Also starts a new session when the visitor arrives from a different campaign |
| Click Quality | ON | Track rage clicks and dead clicks |
| Web Vitals | ON | Capture LCP, CLS, INP performance metrics |
| CTA Visibility | ON | Track when call-to-action buttons enter the viewport |
| Video Tracking | ON | Track native video, Wistia, and Video.js playback |

---

## Troubleshooting

### Orders are not syncing

- Check that the connection shows **Connected** in **Settings > Integrations**
- Make sure the Atribu app is still installed in your Shopify admin under **Settings > Apps and sales channels**
- Verify webhooks are registered: **Settings > Notifications > Webhooks** in Shopify admin
- If you recently changed your store domain, reconnect from Atribu

### Web pixel shows "Disconnected"

The web pixel is activated automatically when you connect Shopify from Atribu. If it shows disconnected:

- Reconnect from Atribu (Settings > Integrations > Shopify > Reconnect)
- Check **Settings > Customer events** in Shopify admin

### No engagement data on storefront

- Make sure the **Atribu Tracker** app embed is toggled ON in the theme editor
- Verify the tracking key is correct
- Check the browser console for errors on a storefront page

### Revenue is not attributed to campaigns

For attribution to work, the customer must be identifiable. This requires either:

- The Atribu theme tracker capturing the ad click (UTMs/click IDs) on the storefront visit
- The customer's email matching a previously identified visitor in Atribu

If neither condition is met, the order will be recorded but shown as unattributed.

### Deduplication

The web pixel and webhooks may both capture the same purchase. Atribu deduplicates by `order_id` and `checkout_token` -- you will never see double-counted revenue.

---

## Related

<Cards>
  <Card title="Revenue Types" href="/docs/concepts/revenue-types">
    How order values (gross) differ from cash payments
  </Card>
  <Card title="Identity Resolution" href="/docs/concepts/identity-resolution">
    How Shopify customer emails are matched to ad clicks
  </Card>
  <Card title="Engagement-Weighted Attribution" href="/docs/concepts/attribution-models">
    How storefront engagement quality affects credit distribution
  </Card>
</Cards>

---

# Stripe

The Stripe integration tracks every payment your business receives as a conversion event. Atribu attributes that revenue back to the ad campaign, ad set, and specific ad that originally brought the customer to your site.

---

## What you get

- Every Stripe payment recorded as a `payment_received` conversion
- Revenue attributed to specific ad campaigns for accurate ROAS
- Customer identity matching -- Atribu links payments to earlier ad clicks
- Payment Links with built-in attribution tracking

---

## Connecting Stripe

<Steps>
<Step>

## Open Integrations

Go to **Settings > Integrations** in your Atribu workspace.

</Step>
<Step>

## Start the connection

Click **Connect** next to the Stripe card. You will be redirected to Stripe.

</Step>
<Step>

## Authorize on Stripe

Sign in with your Stripe account and authorize Atribu. This sets up a secure webhook so Stripe notifies Atribu every time a payment occurs.

</Step>
<Step>

## Verify the connection

Back in Atribu, the Stripe card should show **Connected**. Payments will start flowing in automatically.

</Step>
</Steps>

---

## How it works

When a customer makes a payment through Stripe, the following happens:

1. **Stripe sends a webhook** to Atribu with the payment details (amount, currency, customer email, billing info)
2. **Atribu creates a conversion** -- a `payment_received` event is recorded with the exact amount
3. **The identity graph links the payment** -- Atribu matches the customer's email from the payment to any previous visits on your site
4. **Attribution is computed** -- Atribu traces back to the original ad click and assigns revenue credit to the campaign

Atribu processes three types of Stripe events to ensure no payment is missed:

| Event | Purpose |
|-------|---------|
| `charge.succeeded` | Primary payment event -- most charges arrive this way |
| `checkout.session.completed` | Richer data from Stripe Checkout and Payment Links |
| `payment_intent.succeeded` | Fallback for cases where the charge is not in the payload |

<Callout type="info" title="How payment attribution works">
Here is a typical flow: a customer clicks your Meta ad, visits your landing page, and fills out a contact form. Two weeks later, they pay via Stripe. Atribu matches the email from the Stripe payment to the email from the form submission, then traces that visitor back to the original Meta ad click. The revenue is attributed to that specific ad.
</Callout>

---

## What data Atribu receives

| Data | Description |
|------|-------------|
| **Payment amount** | The amount charged, in the original currency |
| **Currency** | USD, EUR, MXN, etc. -- Atribu normalizes to your reporting currency |
| **Customer email** | From receipt email or billing details |
| **Customer name** | From billing details, if provided |
| **Customer phone** | From billing details, if provided |
| **Stripe Customer ID** | Used for identity matching across multiple payments |

---

## Revenue and ROAS

<Callout type="warn" title="Stripe payments are the gold standard for ROAS">
Stripe payments are classified as **cash** revenue. This is the **only** type of revenue used for ROAS (Return on Ad Spend) calculations. Pipeline values from CRMs like GoHighLevel are tracked separately and never mixed into ROAS. This ensures your ROAS reflects actual money received, not projections.
</Callout>

---

## Payment Links

Atribu can generate Stripe Payment Links with built-in attribution tracking. These links include metadata that ties the payment directly to a specific customer and campaign, even before the customer completes checkout.

This is useful for:

- Sending personalized payment links to leads
- Tracking which campaigns generate the most direct payments
- Attributing revenue when the customer pays without visiting your website first

---

## Stripe dashboard extension

Atribu has a Stripe App that runs directly inside your Stripe dashboard. It shows you which ad campaigns drove each customer and their attributed revenue -- without leaving Stripe. Ask your account manager about enabling it.

---

## Troubleshooting

### Payments are not appearing

- Check that the connection status shows **Connected** in **Settings > Integrations**
- Stripe webhooks may take a few seconds to arrive. Refresh the page after a minute.
- If the issue persists, disconnect and reconnect the Stripe integration

### A payment shows as unattributed

Attribution requires the customer to be identified before paying. This usually happens when:

- The customer filled out a form on your site (email or phone captured)
- The customer's email on Stripe matches an email already in Atribu

If a customer pays without ever visiting your tracked site or submitting their email beforehand, the payment will be recorded but cannot be attributed to an ad campaign.

---

## Related

<Cards>
  <Card title="Revenue Types" href="/docs/concepts/revenue-types">
    Why only cash payments count for ROAS
  </Card>
  <Card title="Identity Resolution" href="/docs/concepts/identity-resolution">
    How payments are matched to ad clicks via the identity graph
  </Card>
  <Card title="Payment Links" href="/docs/features/payment-links">
    Create Stripe Payment Links with built-in attribution tracking
  </Card>
  <Card title="Conversion Sync" href="/docs/features/conversion-sync">
    Send payment events back to ad platforms for revenue optimization
  </Card>
</Cards>

---

All MCP requests require a Bearer token in the `Authorization` header.

```bash title="Authorization header"
Authorization: Bearer atb_user_your_token_here
```

MCP tokens are **user-scoped** -- a single token grants access to every workspace you belong to. The workspace and profile are specified per tool call, not stored on the token.

## Creating a token

Create tokens from **Developer > MCP Tokens** in the Atribu dashboard.

<Callout type="warn" title="Show once">
Tokens are shown exactly once at creation. Store them securely -- you cannot retrieve them later.
</Callout>

## Scopes

Each token has granular scopes that control what data and actions are available.

| Scope | Access |
|-------|--------|
| `mcp:read` | Attribution data, campaigns, creatives, journeys (PII masked) |
| `mcp:read_pii` | Unmask email, phone, and full name when `include_sensitive=true` |
| `mcp:write` | Write-back operations (Meta CAPI conversion export) |

**Default scope** when creating a token: `mcp:read`.

<Callout type="error" title="Write scope">
`mcp:write` allows sending conversion data to external platforms. Only grant this when you specifically need Meta CAPI write-back. The workspace admin must also enable write-back in workspace settings.
</Callout>

## Rate limits

MCP uses **weighted rate limiting**. Each tool has a cost in units, and your token has a per-minute burst cap.

**Per-minute cap:** 120 units (default).

| Tool | Cost |
|------|------|
| `list_workspaces`, `list_profiles` | 0 units (free) |
| `get_performance_summary`, `compare_periods`, `top_campaigns`, `top_creatives` | 1 unit |
| `explain_campaign`, `explain_customer_journey`, `creative_fatigue_check`, `find_anomalies`, `whatsapp_attribution_summary` | 2 units |
| `compare_attribution_models` | 5 units |
| `send_meta_conversions` | 10 units |

When rate limited, the tool returns a `rate_limited` error with `retry_after` (seconds) and `retryable: true`.

## Usage metering

Your subscription includes a monthly unit allowance. Units are debited on each successful tool call.

| Plan | Units per period |
|------|-----------------|
| Trial | 2,000 / 14 days |
| Starter | 20,000 / month |
| Growth | 200,000 / month |
| Agency | 2,000,000 / month |
| Enterprise | Unlimited |

Check your current usage from the **Developer > MCP Tokens** tab in the dashboard.

## Token management

### Rotation

Rotate tokens with zero downtime:

<Steps>
<Step>
Click **Rotate** next to the token in the dashboard
</Step>
<Step>
A new token is generated and revealed once
</Step>
<Step>
Update your AI tool configuration with the new token
</Step>
<Step>
The old token is automatically revoked
</Step>
</Steps>

### Revocation

Click **Revoke** to immediately invalidate a token. Any AI tool using that token will lose access instantly.

## Concurrent sessions

Tokens have no server-side state. Multiple AI tools or IDE sessions can use the same token simultaneously without interfering with each other. Each tool call specifies its own workspace and profile scope.

## Related

<Cards>
  <Card title="Quickstart" href="/docs/mcp/quickstart">
    Connect in under 2 minutes
  </Card>
  <Card title="Error Reference" href="/docs/mcp/errors">
    Error codes and troubleshooting
  </Card>
  <Card title="Privacy & PII" href="/docs/mcp/privacy">
    How PII masking and workspace settings work
  </Card>
</Cards>

---

When an MCP tool call fails, the response includes `isError: true` and a structured error object. Your AI tool will typically explain the error and suggest a fix.

## Error response format

```json title="Error response"
{
  "error": {
    "code": "rate_limited",
    "message": "Per-minute rate limit exceeded. Wait 12 seconds.",
    "retryable": true,
    "retry_after": 12,
    "request_id": "01968a3b-..."
  }
}
```

Some errors include additional fields:

- `action_url` -- a link to the Atribu dashboard page where you can fix the issue
- `workspaces` -- a list of available workspaces (for `workspace_required`)
- `profiles` -- a list of available profiles (for `profile_required`)
- `support_hint` -- a suggestion to contact support with the `request_id`

---

## Error codes

### Authentication errors

| Code | Retryable | Cause | Fix |
|------|-----------|-------|-----|
| `auth_required` | No | Missing or malformed Authorization header | Add `Authorization: Bearer atb_user_...` header |
| `token_expired` | No | Token has been revoked or expired | Create a new token from **Developer > MCP Tokens** |

---

### Scope errors

| Code | Retryable | Cause | Fix |
|------|-----------|-------|-----|
| `workspace_required` | No | User has multiple workspaces and none was specified | Pass `workspace_id` -- the error includes a list of your workspaces |
| `profile_required` | No | Workspace has multiple profiles and none was specified | Pass `profile_id` -- the error includes a list of profiles |
| `insufficient_scope` | No | Token lacks the required scope for this tool | Create a new token with the needed scope |

<Callout type="info" title="Single-resource inference">
If you have exactly one workspace or one profile, it's selected automatically. These errors only occur when there are multiple options.
</Callout>

---

### Rate limit errors

| Code | Retryable | Cause | Fix |
|------|-----------|-------|-----|
| `rate_limited` | Yes | Per-minute or per-period unit cap exceeded | Wait `retry_after` seconds, then retry |

The `retry_after` field tells you how many seconds to wait. AI tools that support retry will handle this automatically.

---

### Connector errors

| Code | Retryable | Cause | Fix |
|------|-----------|-------|-----|
| `connector_expired` | No | A required integration's OAuth token has expired | Re-authorize at the `action_url` in the error response |
| `connector_required` | No | A required integration is not connected | Connect it at the `action_url` in the error response |

These errors include an `action_url` that links directly to the integrations page in your Atribu dashboard.

---

### Write-back errors

| Code | Retryable | Cause | Fix |
|------|-----------|-------|-----|
| `writeback_disabled` | No | Workspace admin has not enabled MCP write-back | Ask a workspace admin to enable it in **Settings > Privacy & MCP** |
| `idempotency_conflict` | No | A confirm with this idempotency key was already processed | The prior result is returned -- no action needed |
| `circuit_open` | Yes | 3+ consecutive failures in the last 30 minutes | Wait for the cooldown, then investigate the underlying failures |

---

### Input errors

| Code | Retryable | Cause | Fix |
|------|-----------|-------|-----|
| `invalid_input` | No | Invalid or missing parameters | Check the parameter types and required fields |
| `data_unavailable` | No | No data exists for the specified window or entity | Widen the date range or verify the entity exists |

---

### Server errors

| Code | Retryable | Cause | Fix |
|------|-----------|-------|-----|
| `internal_error` | Yes | Unexpected server error | Retry once. If persistent, contact support with the `request_id` |

---

## Troubleshooting

### "I get workspace_required but I only have one workspace"

This can happen if your token was created before a workspace was deleted. Try calling `list_workspaces` to see which workspaces your token can access, and pass the `workspace_id` explicitly.

### "Data seems stale or empty"

Check the `meta.data_as_of` field in successful responses. If it's more than a few hours old, your integration may need re-syncing. Go to **Settings > Integrations** and check the sync status.

### "I can't see customer emails"

PII is masked by default. You need all three: `mcp:read_pii` token scope, `include_sensitive: true` in the tool call, and workspace PII mode set to `full_default`. See [Privacy & PII](/docs/mcp/privacy).

### "send_meta_conversions returns insufficient_scope"

This tool requires the `mcp:write` token scope, workspace write-back enabled, and workspace admin role. Check all three conditions.

## Related

<Cards>
  <Card title="Authentication" href="/docs/mcp/authentication">
    Token scopes and rate limits
  </Card>
  <Card title="Available Tools" href="/docs/mcp/tools">
    All 13 tools with parameters
  </Card>
  <Card title="Quickstart" href="/docs/mcp/quickstart">
    Get connected in under 2 minutes
  </Card>
</Cards>

---

# MCP Server

Atribu provides a hosted [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that gives AI tools direct access to your attribution data. Ask questions in natural language and get answers grounded in real campaign performance, customer journeys, and revenue data.

```mermaid
flowchart LR
    A["Claude Code / Cursor"] -->|MCP protocol| B["mcp.atribu.app"]
    B -->|queries| C["Atribu Data"]
    C --> D["Campaigns"]
    C --> E["Journeys"]
    C --> F["Revenue"]
    B -->|structured response| A
```

---

## What you can do

- **"What's my ROAS this month?"** -- get headline KPIs with spend, revenue, and attribution coverage
- **"Which campaigns should I kill?"** -- detect creative fatigue and identify underperformers
- **"Show me the journey for this customer"** -- trace every touchpoint from first click to payment
- **"Compare last week to the week before"** -- period-over-period analysis with delta percentages
- **"Are there any anomalies?"** -- automatic z-score anomaly detection across spend, revenue, and traffic
- **"Send these conversions to Meta"** -- push attributed conversions to Meta CAPI for optimization

---

## How it works

Atribu's MCP server is **centrally hosted** at `https://mcp.atribu.app/mcp`. Your AI tool connects using a personal access token -- no local installation or server setup required.

The server exposes **13 tools** that return structured, semantically-labeled data. All reasoning and narration happens on the AI tool's side -- Atribu provides the data, your AI provides the analysis.

<Cards>
<Card title="Quickstart" href="/docs/mcp/quickstart">
  Connect in under 2 minutes
</Card>
<Card title="Authentication" href="/docs/mcp/authentication">
  Token scopes, rate limits, and metering
</Card>
<Card title="Available Tools" href="/docs/mcp/tools">
  All 13 tools with parameters and examples
</Card>
<Card title="Write-Back (Meta CAPI)" href="/docs/mcp/write-back">
  Push conversions to Meta with safety rails
</Card>
</Cards>

---

## Supported AI tools

The MCP server works with any tool that supports the Streamable HTTP transport:

| Tool | Transport | Status |
|------|-----------|--------|
| Claude Code (CLI) | Streamable HTTP | Supported |
| Claude Desktop | Streamable HTTP | Supported |
| Cursor | Streamable HTTP | Supported |
| Windsurf | Streamable HTTP | Supported |
| VS Code (Copilot) | Streamable HTTP | Supported |
| Any MCP-compatible client | Streamable HTTP | Supported |

---

## Key design decisions

**No AI on the server.** The MCP server returns structured data with semantic labels, units, and metadata. Your AI tool interprets the data and generates the narrative. This means zero LLM inference cost on Atribu's side and you always get the latest model capabilities from your AI tool.

**Explicit scope per call.** Every tool call specifies which workspace and profile to query. There's no "active workspace" state on the server, so concurrent AI sessions using the same token are safe.

**PII masked by default.** Customer emails, phone numbers, and names are masked unless you explicitly request unmasking with the right token scope and workspace setting.

**Weighted metering.** Tools cost different amounts of units depending on complexity. Listing workspaces is free; sending conversions to Meta costs 10 units. Your plan includes a monthly unit allowance.

---

## Related

<Cards>
  <Card title="REST API" href="/docs/api/quickstart">
    Programmatic access via traditional REST endpoints
  </Card>
  <Card title="Attribution Models" href="/docs/concepts/attribution-models">
    How credit is assigned across touchpoints
  </Card>
  <Card title="Revenue Types" href="/docs/concepts/revenue-types">
    Cash vs pipeline vs gross revenue
  </Card>
</Cards>

---

The MCP server masks personally identifiable information (PII) by default. This page explains how masking works and how to configure access to unmasked data.

## Default behavior

When a tool returns customer data (e.g. `explain_customer_journey`), PII fields are masked:

| Field | Masked value | Unmasked value |
|-------|-------------|----------------|
| Email | `j***@e****.com` | `jane@example.com` |
| Phone | `+1 2** *** 5678` | `+1 234 567 5678` |
| Name | First name only (`Jane`) | Full name (`Jane Smith`) |

This applies automatically -- no configuration needed.

---

## Requesting unmasked data

To see full PII, three conditions must be met simultaneously:

<Steps>
<Step>

### Token scope

Your MCP token must include the `mcp:read_pii` scope. Create or rotate a token with this scope from **Developer > MCP Tokens**.

</Step>
<Step>

### Explicit request

Pass `include_sensitive: true` in the tool call. The AI tool will do this when you explicitly ask for unmasked data (e.g. "show me the actual email addresses").

</Step>
<Step>

### Workspace setting

A workspace admin must set the workspace PII mode to `full_default` in **Settings > Privacy & MCP**. The default is `masked_default`.

</Step>
</Steps>

If any condition is not met, the response is masked and `meta.pii_level_applied` is set to `"masked"`. No error is returned -- the tool gracefully falls back to masking.

---

## Workspace admin controls

Workspace administrators can configure two MCP-related settings from **Settings > Privacy & MCP**:

| Setting | Default | Description |
|---------|---------|-------------|
| PII mode | `masked_default` | Controls whether tokens with `mcp:read_pii` can see full PII |
| Write-back | Disabled | Controls whether tokens with `mcp:write` can send data to Meta CAPI |

These settings apply to all MCP tokens used to access the workspace, regardless of which user created the token.

---

## What is logged

MCP tool invocations log **metadata only**:

- Tool name, duration, status code, units debited
- Workspace ID, profile ID, request ID
- Record count, PII level applied

Raw tool arguments, response data, and PII are **never logged**. The `request_id` in every response can be used for support inquiries without exposing sensitive data.

---

## Data flow

```mermaid
flowchart TD
    A["AI Tool calls explain_customer_journey"] --> B{"include_sensitive?"}
    B -->|No| C["Return masked PII"]
    B -->|Yes| D{"Token has mcp:read_pii?"}
    D -->|No| C
    D -->|Yes| E{"Workspace pii_mode = full_default?"}
    E -->|No| C
    E -->|Yes| F["Return full PII"]
```

## Related

<Cards>
  <Card title="Authentication" href="/docs/mcp/authentication">
    Token scopes and rate limits
  </Card>
  <Card title="Write-Back" href="/docs/mcp/write-back">
    Meta CAPI write-back safety flow
  </Card>
  <Card title="Team & Workspace" href="/docs/settings/team">
    Manage workspace roles and permissions
  </Card>
</Cards>

---

<Steps>
<Step>

## Create an MCP token

1. Log in to [Atribu](https://www.atribu.app)
2. Go to **Developer** in the sidebar
3. Switch to the **MCP Tokens** tab
4. Click **New token**
5. Select at least the **Read analytics** scope
6. Copy the token

<Callout type="warn" title="Save your token">
The token starts with `atb_user_` and is shown only once. Store it somewhere secure.
</Callout>

</Step>
<Step>

## Connect your AI tool

<Tabs items={["Claude Code", "Cursor", "Claude Desktop", "Other"]}>
<Tab value="Claude Code">
```bash title="Terminal"
claude mcp add atribu --transport http \
  https://mcp.atribu.app/mcp \
  --header "Authorization: Bearer atb_user_YOUR_TOKEN"
```
</Tab>
<Tab value="Cursor">

Add to your Cursor MCP settings (`.cursor/mcp.json`):

```json title=".cursor/mcp.json"
{
  "mcpServers": {
    "atribu": {
      "url": "https://mcp.atribu.app/mcp",
      "headers": {
        "Authorization": "Bearer atb_user_YOUR_TOKEN"
      }
    }
  }
}
```
</Tab>
<Tab value="Claude Desktop">

Add to your Claude Desktop config (`claude_desktop_config.json`):

```json title="claude_desktop_config.json"
{
  "mcpServers": {
    "atribu": {
      "url": "https://mcp.atribu.app/mcp",
      "headers": {
        "Authorization": "Bearer atb_user_YOUR_TOKEN"
      }
    }
  }
}
```
</Tab>
<Tab value="Other">

Any MCP client that supports Streamable HTTP transport works. Configure it with:

- **URL:** `https://mcp.atribu.app/mcp`
- **Transport:** Streamable HTTP
- **Auth header:** `Authorization: Bearer atb_user_YOUR_TOKEN`

</Tab>
</Tabs>

</Step>
<Step>

## Try it out

Open your AI tool and ask a question about your ad performance:

```
What's my ROAS this month? Which campaigns are performing best?
```

The AI tool will automatically call the right Atribu tools and return a narrative answer grounded in your real data.

<Callout type="info" title="Workspace resolution">
If you belong to multiple workspaces, the AI tool will ask you to specify which one. If you have only one workspace with one profile, it's selected automatically.
</Callout>

</Step>
</Steps>

## Verify the connection

You can verify the MCP server is reachable:

```bash title="Health check"
curl https://mcp.atribu.app/health/live
```

```json title="Response"
{ "status": "ok" }
```

## Example conversation

Here's what a typical interaction looks like in Claude Code:

```
You: What happened to our ad performance last week vs the week before?

Claude: I'll compare the two periods for you.

[calls compare_periods with last week vs prior week]

Your ad performance improved last week:
- Spend: $2,450 → $2,680 (+9.4%)
- Revenue: $8,200 → $11,400 (+39.0%)
- ROAS: 3.35 → 4.25 (+26.9%)
- Conversions: 42 → 58 (+38.1%)

The jump in revenue is driven primarily by "Summer Sale - Retargeting"
which generated $4,200 in attributed revenue at a 6.8x ROAS.

Data as of: 2026-04-14T18:30:00Z | Model: last_touch | Currency: USD
```

## Next steps

<Cards>
  <Card title="Authentication" href="/docs/mcp/authentication">
    Token scopes, rate limits, and usage metering
  </Card>
  <Card title="Available Tools" href="/docs/mcp/tools">
    All 13 tools with parameters and examples
  </Card>
  <Card title="Privacy & PII" href="/docs/mcp/privacy">
    How PII masking works and how to configure it
  </Card>
</Cards>

---

Atribu ships an opinionated [Claude Code skill](https://code.claude.com/docs/en/skills) that teaches Claude how to reason about your attribution data. Once installed, the skill auto-loads whenever you ask about ROAS, campaigns, creatives, or customer journeys — Claude follows the skill's rules and calls the right MCP tools in the right order.

## What the skill does

The MCP server returns raw data; the skill supplies the judgment:

- **Cash-ROAS by default** — the skill teaches Claude that `revenue_type='cash'` is the only thing that counts as ROAS. GHL pipeline deals are "deal value," not revenue.
- **Tool ordering heuristics** — for each common question ("which ads should I kill?", "why is ROAS dropping?", "who's converting?"), the skill specifies which MCP tools to call and in what order.
- **Model guidance** — when to use `last_touch` vs `engagement_weighted` vs `compare_attribution_models` based on conversion volume and sales cycle.
- **Write-back safety** — mandatory preview → user-confirm → confirm-with-idempotency-key flow for `send_meta_conversions`.
- **PII explanations** — when you ask for unmasked data and your token can't provide it, Claude explains exactly what to change instead of silently returning masked values.

---

## Install

The skill is distributed as a Claude Code plugin from the public repo [AtribuCore/atribu-attribution-skill](https://github.com/AtribuCore/atribu-attribution-skill).

<Steps>
<Step>

### Add the marketplace and install the plugin

In any Claude Code session, run:

```
/plugin marketplace add AtribuCore/atribu-attribution-skill
/plugin install atribu-attribution@atribu-attribution
```

The first command registers the marketplace; the second installs the `atribu-attribution` plugin (which bundles the skill).

</Step>
<Step>

### Restart Claude Code

A fresh session guarantees the skill is loaded. You can verify with:

```
What skills are available?
```

You should see `atribu-attribution` in the list.

</Step>
<Step>

### Configure the MCP server

If you haven't already, add the Atribu MCP server ([quickstart](/docs/mcp/quickstart)):

```bash title="Terminal"
claude mcp add atribu --transport http \
  https://mcp.atribu.app/mcp \
  --header "Authorization: Bearer atb_user_YOUR_TOKEN"
```

</Step>
</Steps>

### Updating

When the skill is updated upstream, refresh with:

```
/plugin update atribu-attribution
```

### Other MCP clients

Cursor, Claude Desktop, Windsurf, and other MCP-compatible tools use the same MCP server but don't have Claude Code's plugin system. Copy the contents of [`SKILL.md`](https://github.com/AtribuCore/atribu-attribution-skill/blob/main/plugins/atribu-attribution/skills/atribu-attribution/SKILL.md) into your system prompt or project rules for equivalent behavior.

---

## Skill structure

Inside the public repo, the plugin is laid out as:

```
atribu-attribution-skill/
├── .claude-plugin/marketplace.json     # Marketplace manifest
├── plugins/
│   └── atribu-attribution/
│       ├── .claude-plugin/plugin.json  # Plugin manifest
│       └── skills/atribu-attribution/
│           ├── SKILL.md                # Main instructions
│           ├── references/
│           │   ├── tool-ordering.md    # Detailed heuristics
│           │   └── error-playbook.md   # Error code handling
│           └── evals/evals.json        # Test cases
├── README.md
└── LICENSE                             # MIT
```

The **main `SKILL.md`** stays focused — the quick reference. When Claude needs deeper detail (error handling or tool selection), it reads files in `references/` on demand. This follows Anthropic's recommendation to keep `SKILL.md` lean and lazy-load verbose material.

---

## Evals

The skill ships with `evals/evals.json` — test cases with expected behavior. Each eval is a `{ prompt, expected_output, assertions }` object that documents what Claude should do when the skill is loaded.

Example assertions:

- `"Which ads should I kill?"` → must call `creative_fatigue_check` first, then `top_creatives`
- `"Show me actual email addresses"` → must explain the three-condition PII requirement, not silently return masked data
- `"Send conversions to Meta"` → must call `preview` first, ask for user confirmation, generate an idempotency key, report the `audit_id`

Evals aren't automatically run by Claude Code — they're reference test cases for anyone modifying the skill. Use them as a regression checklist before changing SKILL.md.

---

## Contributing

The skill is open source under MIT. If you find it giving bad guidance, [open an issue](https://github.com/AtribuCore/atribu-attribution-skill/issues) or send a PR at [AtribuCore/atribu-attribution-skill](https://github.com/AtribuCore/atribu-attribution-skill). Released versions are tagged so end users can pin if they want.

## Related

<Cards>
  <Card title="MCP Quickstart" href="/docs/mcp/quickstart">
    Set up the MCP server connection
  </Card>
  <Card title="Available Tools" href="/docs/mcp/tools">
    All 21 MCP tools the skill orchestrates
  </Card>
  <Card title="Write-Back" href="/docs/mcp/write-back">
    The preview/dry-run/confirm flow for send_meta_conversions
  </Card>
</Cards>

---

The Atribu MCP server exposes 17 tools. Every tool returns a structured response with a `data` object and a `meta` object containing request metadata, data freshness, and attribution context.

## Shared parameters

Most tools accept these common parameters:

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `workspace_id` | UUID | No | Which workspace to query. Inferred if you have exactly one. |
| `profile_id` | UUID | No | Which profile to query. Inferred if the workspace has exactly one. |
| `window_start` | `YYYY-MM-DD` | Yes | Start of the date range |
| `window_end` | `YYYY-MM-DD` | Yes | End of the date range |
| `model` | string | No | Attribution model. Default: `last_touch` |

**Available attribution models:** `last_touch`, `first_touch`, `split_50_50`, `linear`, `position_based`, `time_decay`, `last_non_direct`, `custom_weighted`, `engagement_weighted`

---

## Discovery tools

### whoami

Identity, usage, and effective settings for the current MCP token. Returns scopes, units used vs cap, your workspaces, and (when scope is unambiguous) active workspace settings (`pii_mode`, `mcp_writeback_enabled`, your role) and the active profile's currency. Always call once per session — it tells you upfront whether PII unmask or write-back will work, so the AI tool can guide the user correctly without trial-and-error calls.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `workspace_id` | UUID | No | Optional. If passed (or you have one workspace), `active_workspace` is populated. |
| `profile_id` | UUID | No | Optional. If passed (or workspace has one profile), `active_profile` is populated. |

**Cost:** 0 units

**Returns:** `{ token, usage, user, workspaces[], active_workspace, active_profile, server_version }`. `active_workspace` and `active_profile` are `null` when scope is ambiguous.

---

### list_workspaces

List all workspaces you have access to. Use the returned `id` values as `workspace_id` in subsequent calls.

| Parameter | Type | Required |
|-----------|------|----------|
| — | — | — |

**Cost:** 0 units

**Returns:** Array of `{ id, name }` objects.

---

### list_profiles

List all profiles within a workspace. Use the returned `id` values as `profile_id` in subsequent calls.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `workspace_id` | UUID | No | Inferred if you have one workspace |

**Cost:** 0 units

**Returns:** Array of `{ id, name, domain }` objects.

---

## Performance tools

### get_performance_summary

Headline KPIs for a date window plus the equivalent prior period. Returns three buckets: **cash** (real revenue, drives ROAS), **pipeline** (CRM deal counts + raw `value_amount` sum — NOT revenue), and **leads** (top of funnel).

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `model` | string | No | `last_touch` |

**Cost:** 1 unit

**Returns:**

```json title="Example response (data, current_period)"
{
  "spend": { "amount": 1032.42 },
  "cash": {
    "attributed_revenue": { "amount": 4000 },
    "attributed_conversions": 6,
    "total_conversions": 17,
    "collected": { "amount": 9749 },
    "roas": { "value": 3.87, "formatted": "3.87x" }
  },
  "pipeline": {
    "appointments_booked": 38,
    "closed_won": 0,
    "crm_value_amount_sum": {
      "attributed": { "amount": 76600 },
      "total": { "amount": 79600 }
    },
    "note": "Counts are concrete events. crm_value_amount_sum is the raw sum of whatever the agency entered in their CRM's deal value field — NOT revenue. Never present as a revenue claim without confirming the CRM convention."
  },
  "leads": { "count": 409, "note": "Top of funnel events with no revenue." },
  "traffic": { "visitors": 1550, "pageviews": 2386, "bounce_rate_percent": 87.3 }
}
```

<Callout type="warn" title="Pipeline crm_value_amount_sum is NOT revenue">
The CRM `value_amount` field means whatever the agency configured it to mean — deal target, customer lifetime value estimate, retainer size, or arbitrary. Use **counts** (`appointments_booked`, `closed_won`) as the primary signal. Only mention the sum with explicit context. **Never include `crm_value_amount_sum` in ROAS calculations or call it "revenue" or "projected revenue".**
</Callout>

---

### compare_periods

Compare two arbitrary date windows side by side. Useful for year-over-year, pre/post campaign launch, or seasonal comparisons.

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `prev_window_start` | date | Yes | — |
| `prev_window_end` | date | Yes | — |
| `model` | string | No | `last_touch` |

**Cost:** 1 unit

**Returns:** Same structure as `get_performance_summary` with `current` and `previous` objects representing the two windows.

---

## Campaign tools

### top_campaigns

Top campaigns ranked by attributed revenue within a date window.

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `model` | string | No | `last_touch` |
| `limit` | number | No | `10` (max 50) |

**Cost:** 1 unit

**Returns:** Array of campaigns with `name`, `platform_id`, `spend`, `revenue`, `roas`, `conversions`, `clicks`, `impressions`.

<Callout type="info" title="platform_id">
Each campaign includes a `platform_id` which you can pass to `explain_campaign` for a deep dive.
</Callout>

---

### top_ad_sets

Top ad sets ranked by attributed cash revenue. Sits between `top_campaigns` (broader) and `top_creatives` (narrower) — answers "which audience/placement is delivering?" before drilling into individual creatives.

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `model` | string | No | `last_touch` |
| `limit` | number | No | `10` (max 50) |

**Cost:** 1 unit

**Requires:** Meta Ads connection

**Returns:** Array of ad sets with `ad_set_name`, `campaign_name` (parent), `spend`, `attributed_revenue`, `roas`, `attributed_conversions`, `cac`, `ctr_percent`, `impressions`, `clicks`, `avg_engagement_score`.

---

### top_creatives

Top individual ads ranked by ROAS, with creative details (thumbnail URLs, headlines, body text).

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `model` | string | No | `last_touch` |
| `limit` | number | No | `10` (max 50) |

**Cost:** 1 unit

**Requires:** Meta Ads connection

**Returns:** Array of ads with `name`, `spend`, `revenue`, `roas`, `ctr`, `thumbnail_url`, `headline`, `body`.

---

### explain_campaign

Deep dive into a single campaign: headline metrics, daily trend, and constituent ads.

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `campaign_id` | string | Yes | — |
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `model` | string | No | `last_touch` |

**Cost:** 2 units

The `campaign_id` can be a UUID (internal ID), a Meta `platform_id`, or a campaign name. The tool resolves it automatically.

**Returns:**

```json title="Example response (data)"
{
  "summary": {
    "name": "Summer Sale - Retargeting",
    "platform_id": "23851234567890",
    "spend": 1200.00,
    "revenue": 5400.00,
    "roas": 4.50,
    "conversions": 28,
    "clicks": 1840,
    "impressions": 42000
  },
  "daily_trend": [
    { "date": "2026-04-01", "spend": 180, "revenue": 720, "clicks": 260 }
  ],
  "ads": [
    { "name": "Video - Testimonial", "spend": 600, "revenue": 3200, "roas": 5.33 }
  ]
}
```

---

## Customer tools

### explain_customer_journey

Full event timeline for a single customer: ad clicks, page views, form fills, conversions, and payments.

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `customer_profile_id` | UUID | Yes | — |
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `include_sensitive` | boolean | No | `false` |
| `offset` | number | No | `0` |
| `limit` | number | No | `50` (max 100) |

**Cost:** 2 units

**Returns:** Customer info (name, masked email/phone) plus an array of timeline events ordered chronologically.

<Callout type="info" title="PII masking">
By default, email and phone are masked (e.g. `j***@e****.com`). To see unmasked values, pass `include_sensitive: true` -- requires `mcp:read_pii` scope and workspace PII mode set to `full_default`. See [Privacy & PII](/docs/mcp/privacy).
</Callout>

---

## Analysis tools

### compare_attribution_models

Run the same date window through multiple attribution models to see how credit distribution shifts.

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `models` | string[] | No | `["last_touch", "first_touch", "linear", "position_based", "time_decay"]` |

**Cost:** 5 units

**Returns:** One result set per model, each with the same campaign-level metrics. Compare to understand which channels are undervalued by single-touch models.

---

### creative_fatigue_check

Detect ads showing fatigue signals: declining CTR, rising CPM, or audience saturation compared to the prior equal-length window.

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `model` | string | No | `last_touch` |

**Cost:** 2 units

**Requires:** Meta Ads connection

**Returns:** Array of ads with current vs prior period metrics and fatigue indicators.

---

### find_anomalies

Flag unusual daily spikes or drops in spend, revenue, or traffic using z-score analysis (median + MAD).

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `metrics` | string[] | No | `["spend", "cash_revenue", "visitors"]` |
| `threshold_sigma` | number | No | `2.0` |
| `model` | string | No | `last_touch` |

**Cost:** 2 units

**Returns:** Array of anomalous days with the metric name, observed value, expected range, and z-score magnitude.

---

### get_funnel

Goal-driven conversion funnel for the date window. Walks the canonical lead-gen progression `lead_created → appointment_booked → showed → qualified → closed_won` with per-stage counts, stage-to-stage conversion rates, and drop-off counts. Also returns negative-outcome buckets (`disqualified`, `no_show`, `closed_lost`).

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `scope` | `all` \| `pipeline_only` \| `contact_only` | No | `all` |

**Cost:** 1 unit

`scope=pipeline_only` restricts to events linked to a CRM pipeline (deals in a stage). `scope=contact_only` restricts to events without one (lead-only / form fills).

**Returns:**

```json title="Example response (data)"
{
  "scope": "all",
  "window": { "start": "2026-04-01", "end": "2026-04-30" },
  "main_chain": [
    { "key": "lead_created", "label": "Leads", "count": 409, "conversion_rate_from_previous_pct": null, "drop_off_from_previous_count": 0 },
    { "key": "appointment_booked", "label": "Booked", "count": 38, "conversion_rate_from_previous_pct": 9.29, "drop_off_from_previous_count": 371 },
    { "key": "showed", "label": "Showed", "count": 24, "conversion_rate_from_previous_pct": 63.16, "drop_off_from_previous_count": 14 },
    { "key": "qualified", "label": "Qualified", "count": 12, "conversion_rate_from_previous_pct": 50.00, "drop_off_from_previous_count": 12 },
    { "key": "closed_won", "label": "Closed Won", "count": 0, "conversion_rate_from_previous_pct": 0, "drop_off_from_previous_count": 12 }
  ],
  "drop_off_outcomes": [
    { "key": "disqualified", "label": "Disqualified", "count": 7 },
    { "key": "no_show", "label": "No Show", "count": 5 },
    { "key": "closed_lost", "label": "Closed Lost", "count": 3 }
  ],
  "totals": {
    "all_events": 498,
    "leads": 409,
    "closed_won": 0,
    "lead_to_closed_won_rate_pct": 0
  }
}
```

<Callout type="info" title="Counts are events, not unique customers">
One customer can appear at multiple stages. Don't compute customer-level rates from these event counts. Stage-to-stage conversion rates assume customers progress linearly — in reality customers can skip stages or be re-classified.
</Callout>

<Callout type="warn" title="Stages only show events that are also conversions">
Counts include only events whose `event_key` is mapped under **Settings → Outcomes → Conversion Definitions**. If a CRM stage like `disqualified` shows 0 but your team uses it (e.g. labels "Descualificado", "Lead Abandonado"), the conversion definition is missing. Map the key in Conversion Definitions to make those events appear in the funnel.
</Callout>

---

### get_tracking_breakdown

Top values per traffic-dimension (channel, country, page, browser, device, OS, referrer, campaign, etc.) ranked by visitors. Returns up to 6 dimensions in one call.

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `dimensions` | string[] | No | `["channel", "country", "page", "browser"]` |
| `limit` | number | No | `10` (max 50) |

**Available dimensions:** `channel`, `referrer`, `campaign`, `page`, `entry_page`, `exit_page`, `country`, `region`, `city`, `device`, `browser`, `os`

**Cost:** 1 unit

**Returns:**

```json title="Example response (data)"
{
  "breakdowns": {
    "channel": [
      { "dimension_value_key": "paid_social", "label": "Paid Social", "visitors": 820, "visits": 1120, "pageviews": 2400, "bounce_rate_pct": 62.4, "conversions": 14, "conversion_rate_pct": 1.71, "cash_revenue": { "amount": 3200 } }
    ],
    "country": [
      { "dimension_value_key": "US", "label": "United States", "visitors": 540, "visits": 720, "pageviews": 1820, "bounce_rate_pct": 58.1, "conversions": 12, "conversion_rate_pct": 2.22, "cash_revenue": { "amount": 2800 } }
    ]
  },
  "window": { "start": "2026-04-01", "end": "2026-04-30" },
  "dimensions_requested": ["channel", "country"],
  "limit_per_dimension": 10
}
```

<Callout type="info" title="Revenue is cash only">
The `cash_revenue` field uses `revenue_type='cash'` only — same definition the dashboard uses for ROAS. Pipeline / CRM deal values are NOT included here.
</Callout>

---

### list_conversions

Paginated list of conversions for a date window, ordered by `conversion_time DESC`. Each row carries the customer (masked PII by default), first-touch channel, time-to-complete, revenue, plus the customer's lifetime totals.

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `goal` | string | No | `payment_received` |
| `search` | string | No | — |
| `cursor_time` | string | No | — |
| `cursor_id` | string | No | — |
| `limit` | number | No | `25` (max 100) |
| `include_sensitive` | boolean | No | `false` |

**Cost:** 1 unit

`goal` filters by `event_type` (e.g. `payment_received`, `lead_created`, `appointment_booked`, `closed_won`). Pass `all` to include every conversion type. Use `next_cursor` from the response for the next page.

For a SINGLE customer's full event timeline, use [`explain_customer_journey`](#explain_customer_journey) instead.

---

### list_visitors

Paginated visitor roster (identified + anonymous) for a date window, ordered by `last_seen_at DESC`. Each row carries last-seen timestamp, session count, total cash revenue, channel/source from the latest session, and device/geo.

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `search` | string | No | — |
| `cursor_time` | string | No | — |
| `cursor_id` | string | No | — |
| `limit` | number | No | `25` (max 100) |
| `include_sensitive` | boolean | No | `false` |

**Cost:** 1 unit

`is_identified=true` means the visitor has a `customer_profile_id` (matched via `identify()` or a server-side event). False means anonymous-only. `total_revenue` is cash only (`revenue_type='cash'`) and reflects the customer's lifetime sum.

---

### get_attribution_quality

Diagnostic — how trustworthy is attribution data for this date window? Returns the share of conversion-related events with full UTMs (best), with `fbclid` only (Meta click but no UTMs), and with no tracking at all (worst).

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |

**Cost:** 1 unit

**Returns:**

```json title="Example response (data)"
{
  "window": { "start": "2026-04-01", "end": "2026-04-30" },
  "totals": {
    "total_events": 409,
    "with_full_utms": 248,
    "with_fbclid_only": 102,
    "with_no_tracking": 59
  },
  "percentages": {
    "full_utms_pct": 60.6,
    "fbclid_only_pct": 24.9,
    "no_tracking_pct": 14.4,
    "attributable_coverage_pct": 85.5
  }
}
```

<Callout type="info" title="Heuristic">
`attributable_coverage_pct` above 80% is healthy. Between 50–80% means attribution numbers should be treated cautiously. Below 50% indicates UTM tagging gaps — recommend an audit before trusting campaign-level reports.
</Callout>

---

### get_engagement_summary

Site-wide engagement quality for the date window: scroll depth, time on page, rage clicks, dead clicks, form completion, video completion, JS errors, and Core Web Vitals (LCP/FCP/CLS/INP/TTFB). Optional breakdown by page (top N most-engaged) or day (timeline).

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `breakdown_by` | `none` \| `page` \| `day` | No | `none` |
| `page_limit` | number | No | `10` (max 50) |

**Cost:** 1 unit

**Returns:** `summary.attention` (scroll depth, time on page), `summary.frustration` (rage/dead clicks, JS errors), `summary.forms` (submitted/abandoned + completion rate), `summary.video` (plays/completions + completion rate), `summary.web_vitals` (avg LCP/FCP/CLS/INP/TTFB + LCP good/poor pct). With `breakdown_by='page'` adds `pages[]`. With `breakdown_by='day'` adds `daily[]`.

<Callout type="info" title="Different from get_tracking_breakdown">
`get_tracking_breakdown` is about **traffic mix** (which channels, countries, pages bring users). `get_engagement_summary` is about **UX quality** (are users engaging? Are they frustrated? Is the site fast?). Engagement events are display-only — they NEVER become conversions and are never included in attribution credit.
</Callout>

---

### whatsapp_attribution_summary

Revenue and conversions attributed to WhatsApp touchpoints (Click-to-WhatsApp ads or WhatsApp-sourced traffic).

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |

**Cost:** 2 units

**Requires:** WhatsApp integration

**Returns:** Conversation counts, conversion counts, revenue attributed to WhatsApp touchpoints.

---

## Write-back tool

### send_meta_conversions

Push attributed conversions to Meta Conversions API (CAPI) for ad delivery optimization. This tool has a three-step safety flow: preview, dry-run, confirm.

See the dedicated [Write-Back guide](/docs/mcp/write-back) for the full flow, safety rails, and examples.

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `mode` | `preview` \| `dry_run` \| `confirm` | Yes | — |
| `window_start` | date | Yes | — |
| `window_end` | date | Yes | — |
| `event_types` | string[] | Yes | — |
| `pixel_id` | string | For dry_run/confirm | — |
| `idempotency_key` | string | For confirm | — |
| `test_event_code` | string | No | — |
| `max_events` | number | No | `500` (max 500) |

**Cost:** 10 units

**Requires:** `mcp:write` scope, Meta Ads connection, workspace write-back enabled, workspace admin role.

---

## Response envelope

Every tool returns a consistent envelope:

```json title="Response structure"
{
  "data": { },
  "meta": {
    "request_id": "01968a3b-...",
    "workspace_id": "...",
    "profile_id": "...",
    "window_start": "2026-04-01",
    "window_end": "2026-04-14",
    "attribution_model": "last_touch",
    "currency": "USD",
    "data_as_of": "2026-04-14T18:30:00Z",
    "freshness_by_provider": {
      "meta": "2026-04-14T18:30:00Z",
      "ghl": "2026-04-14T17:00:00Z"
    },
    "data_freshness_warning": null,
    "record_count": 12,
    "pii_level_applied": "masked"
  }
}
```

| Meta field | Description |
|------------|-------------|
| `request_id` | Unique ID for debugging and support |
| `data_as_of` | Oldest sync timestamp across required providers |
| `freshness_by_provider` | Per-provider last sync time |
| `data_freshness_warning` | Human-readable warning if data is stale (>6 hours) |
| `pii_level_applied` | `masked` or `full` -- what PII level was actually used |

## Related

<Cards>
  <Card title="Write-Back (Meta CAPI)" href="/docs/mcp/write-back">
    The full preview/dry-run/confirm flow for send_meta_conversions
  </Card>
  <Card title="Error Reference" href="/docs/mcp/errors">
    Error codes and troubleshooting
  </Card>
  <Card title="Attribution Models" href="/docs/concepts/attribution-models">
    How each model distributes credit
  </Card>
</Cards>

---

The `send_meta_conversions` tool lets you push attributed conversions from Atribu to Meta's Conversions API (CAPI). This improves Meta's ad delivery optimization by feeding real conversion data back to the algorithm.

<Callout type="warn" title="Irreversible action">
Confirmed conversions are sent to Meta and cannot be recalled. The tool enforces a three-step safety flow to prevent accidental submissions.
</Callout>

## Prerequisites

Before using write-back, all four conditions must be met:

1. **Token scope** -- your MCP token must include `mcp:write`
2. **Workspace setting** -- a workspace admin must enable **MCP write-back** in workspace settings
3. **User role** -- you must be a workspace **owner** or **admin**
4. **Meta connection** -- a Meta Ads connection must be active

If any condition is unmet, the tool returns a typed error explaining what's missing and how to fix it.

---

## The three-step flow

### Step 1: Preview

Build conversion events locally and inspect them without calling Meta.

```
Send my last 7 days of payment_received conversions to Meta in preview mode
```

The tool returns:
- Total event count
- Sample events (first 3) with mapped fields
- Match-quality breakdown (great / good / weak) based on available user data

No data leaves Atribu during preview.

---

### Step 2: Dry-run

Submit events to Meta with a test event code. Meta validates the payload format and match quality without recording real conversions.

```
Do a dry-run of those conversions with pixel ID 1234567890
```

The tool returns:
- `events_received` count from Meta
- `fbtrace_id` for debugging in Meta Events Manager
- Any validation warnings from Meta

<Callout type="info" title="Test events">
Dry-run events appear in Meta Events Manager under the **Test Events** tab. They do not affect ad delivery or reporting.
</Callout>

---

### Step 3: Confirm

Submit events to Meta for real. Requires an idempotency key to prevent duplicate submissions.

```
Confirm sending those conversions. Use idempotency key "april-week2-payments"
```

The AI tool should generate a unique idempotency key (typically a UUID or descriptive string) and include it in the confirm call. If the same idempotency key is used twice, the tool returns the prior result instead of submitting again.

---

## Event type mapping

Atribu outcome types map to Meta standard events:

| Atribu event | Meta event |
|-------------|------------|
| `payment_received` | `Purchase` |
| `order_placed` | `Purchase` |
| `closed_won` | `Purchase` |
| `appointment_booked` | `Schedule` |
| `lead_created` | `Lead` |
| `checkout_started` | `InitiateCheckout` |
| `add_to_cart` | `AddToCart` |
| `add_payment_info` | `AddPaymentInfo` |
| `view_content` | `ViewContent` |
| `search` | `Search` |

---

## Safety rails

### Idempotency

Every `confirm` call requires an `idempotency_key`. If a confirm with the same key was already processed for this profile, the tool returns the prior result. This prevents accidental double-sends even if the AI tool retries.

### Circuit breaker

If 3 or more `confirm` operations fail for the same profile within 30 minutes, the tool enters circuit-open state and rejects new confirm calls. Wait for the cooldown or investigate the failures.

### Audit trail

Every operation (preview, dry-run, confirm) creates an immutable audit record with:
- Payload hash, event count, window dates
- External submission IDs (on success)
- Result status and error details
- Request ID for traceability

Audit records are visible to workspace admins in the dashboard.

---

## Match quality

The preview step estimates how well Meta can match your events to ad clicks:

| Quality | Criteria |
|---------|----------|
| **Great** | Has FBP cookie + email + phone + IP + user agent |
| **Good** | Has email or phone + IP or user agent |
| **Weak** | Missing most identifiers -- Meta may not match this event |

Higher match quality means Meta can better attribute the conversion to the right ad click, improving future ad delivery.

## Related

<Cards>
  <Card title="Meta Integration" href="/docs/integrations/meta">
    Connect your Meta ad accounts
  </Card>
  <Card title="Available Tools" href="/docs/mcp/tools">
    All 13 MCP tools
  </Card>
  <Card title="Error Reference" href="/docs/mcp/errors">
    Error codes including write-back specific errors
  </Card>
</Cards>

---

Attribution windows define the maximum time between a marketing touchpoint (like an ad click) and a conversion (like a payment) for the touchpoint to receive credit.

## Default windows

| Window | Default | Range | What it controls |
|--------|---------|-------|-----------------|
| **Click window** | 30 days | 1-365 days | How long after an ad click can a conversion be attributed |
| **View-through window** | 24 hours | 1-168 hours | How long after seeing an ad (without clicking) can a conversion be attributed |
| **First-touch window** | 90 days | 1-365 days | Maximum lookback period for the first-touch attribution model |

## How windows work

When a customer converts (e.g., makes a payment), Atribu looks back in time for touchpoints:

- If the customer clicked an ad **within the click window** (default 30 days), the touchpoint gets credit
- If the customer saw an ad **within the view-through window** (default 24 hours) but didn't click, the impression may get credit
- For first-touch attribution, Atribu looks as far back as the **first-touch window** (default 90 days) to find the original discovery touchpoint

<Callout type="info" title="Example">
Your click window is 30 days. A customer clicks your Facebook ad on March 1, browses your site, leaves, and comes back on March 25 to buy. The purchase IS attributed to the Facebook ad (25 days < 30-day window). If they buy on April 5, it's NOT attributed (35 days > 30-day window).
</Callout>

## Configuring windows

<Steps>
<Step>
Go to **Settings > General** (or Settings > Tracking)
</Step>
<Step>
Find the **Attribution Windows** section
</Step>
<Step>
Adjust the values for your business:
- **Short sales cycle** (e-commerce): 7-14 day click window
- **Medium sales cycle** (SaaS, services): 30 day click window (default)
- **Long sales cycle** (B2B, high-ticket): 60-90 day click window
</Step>
<Step>
Click Save. Changes apply to future attribution calculations.
</Step>
</Steps>

<Callout type="warn" title="Changing windows affects ROAS">
Wider windows = more conversions attributed = higher apparent ROAS. Narrower windows = fewer attributions = more conservative ROAS. Choose based on your actual sales cycle, not the number you want to see.
</Callout>

## Best practices

- **Match your sales cycle**: If most customers buy within 2 weeks, a 14-day window is more accurate than 90 days
- **Be consistent**: Changing windows frequently makes period-over-period comparisons unreliable
- **Use view-through conservatively**: A 24-hour view-through window means someone who saw (but didn't click) your ad yesterday gets credit if they buy today. This can inflate attribution.

## Related

<Cards>
  <Card title="Attribution Models" href="/docs/concepts/attribution-models">
    How credit is distributed across touchpoints within the window
  </Card>
  <Card title="Ads Performance" href="/docs/features/ads-performance">
    See how window changes affect campaign ROAS
  </Card>
</Cards>

---

Conversion goals tell Atribu which events matter for your business. When a tracked event matches a goal, it becomes a **conversion** that gets attributed to your ad campaigns.

## Default goals

When you create a new profile, Atribu automatically sets up these goals:

| Goal | Event Type | Revenue Type | Description |
|------|-----------|-------------|-------------|
| Payment Received | `payment_received` | Cash | Stripe or MercadoPago payment completed |
| Lead Created | `lead_created` | Pipeline | New lead in GHL |
| Appointment Booked | `appointment_booked` | Pipeline | Calendar booking completed |
| Closed Won | `closed_won` | Pipeline | Deal marked as won in GHL |
| Order Placed | `order_placed` | Gross | Shopify or e-commerce order |
| Contact Reply | `contact_reply` | Pipeline | Customer responded |

## Revenue types

Each goal has a **revenue type** that determines how its value is counted:

<Callout type="warn" title="Revenue types matter for ROAS">
Only **Cash** revenue is used for ROAS calculations. Pipeline and Gross values are tracked separately.
</Callout>

| Revenue Type | What it means | Used for ROAS? |
|-------------|--------------|----------------|
| **Cash** | Real money received (Stripe, MercadoPago) | Yes |
| **Pipeline** | Projected deal value from CRM (GHL) | No |
| **Gross** | Order value before refunds/cancellations | No |

## Creating custom goals

<Steps>
<Step>

Go to **Settings > Outcomes** tab

</Step>
<Step>

Click **Add Goal**

</Step>
<Step>

Configure the goal:
- **Name**: Display name (e.g., "Free Trial Started")
- **Source events**: Which event types trigger this goal (e.g., `trial_started`)
- **Revenue type**: Cash, Pipeline, or Gross
- **Attribution eligible**: Whether this goal should be included in attribution models

</Step>
<Step>

Click Save. New conversions matching this goal will be created automatically.

</Step>
</Steps>

## GHL pipeline stage mapping

If you use GoHighLevel, Atribu maps your pipeline stages to conversion types:

- "New Lead" stages → `lead_created`
- "Appointment" stages → `appointment_booked`
- "Won" / "Closed" stages → `closed_won`

You can customize this mapping in **Settings > Outcomes**. Atribu uses keyword matching to suggest initial mappings, but you can override them.

<Callout type="info" title="Automatic trigger">
When a new event arrives (from Stripe, GHL, or the tracker) that matches a goal's source event types, Atribu automatically creates a conversion and runs attribution. No manual action needed.
</Callout>

## Related

<Cards>
  <Card title="Revenue Types" href="/docs/concepts/revenue-types">
    How cash, pipeline, and gross revenue types differ
  </Card>
  <Card title="Custom Events" href="/docs/tracking/custom-events">
    Track custom actions and use them as conversion goals
  </Card>
  <Card title="Attribution Models" href="/docs/concepts/attribution-models">
    How credit is distributed across touchpoints for each conversion
  </Card>
</Cards>

---

Atribu uses a two-level organization: **Workspaces** contain **Profiles**. Each workspace can have multiple team members with different roles.

## Workspaces vs Profiles

| Level | What it is | Example |
|-------|-----------|---------|
| **Workspace** | Your organization or agency | "Acme Marketing Agency" |
| **Profile** | A specific business/client being tracked | "Client A - E-commerce Store" |

One workspace can have multiple profiles — perfect for agencies managing multiple clients.

## Team roles

| Role | Dashboard | Settings | Integrations | API Keys | Members |
|------|-----------|----------|-------------|----------|---------|
| **Admin** | View | Edit | Manage | Create/Revoke | Invite/Remove |
| **Editor** | View | Edit | View | Create | View |
| **Viewer** | View | View | View | No | View |

## Inviting team members

<Steps>
<Step>
Go to **Workspace Settings** (click workspace name in sidebar)
</Step>
<Step>
Click the **Members** tab
</Step>
<Step>
Enter the email address and select a role
</Step>
<Step>
Click **Invite**. They'll receive an email to join.
</Step>
</Steps>

<Callout type="info" title="Profile-level access">
You can also manage access per profile. Go to the profile's **Settings > Members** to control who can see which client's data.
</Callout>

## Audit logs

Workspace admins can view an activity log of all actions taken by team members:

- Go to **Workspace Settings > Audit Logs**
- See who did what and when (connection changes, settings updates, key creations)
- Last 50 events are retained

## Workspace settings

In **Workspace Settings > General**:

- **Workspace name**: Your organization name
- **Timezone**: Default timezone for new profiles

## Related

<Cards>
  <Card title="API Authentication" href="/docs/api/authentication">
    Create and manage API keys for programmatic access
  </Card>
  <Card title="Reports" href="/docs/features/reports">
    Generate shareable reports for team members and clients
  </Card>
</Cards>

---

# Auto-Capture

Atribu automatically captures form submissions, booking completions, and payment redirects -- no extra code needed. These auto-captured events are the foundation of attribution, because they connect anonymous visitors to real people.

---

## Form auto-capture

When a visitor submits a form on your website, Atribu detects it and does three things:

<Steps>
<Step>

### Extracts contact information

The tracker scans the form fields and looks for:

- **Email** -- fields with `type="email"`, or a `name`/`autocomplete` attribute containing `email`
- **Phone** -- fields with `type="tel"`, or a `name` attribute containing `phone` or `telephone`
- **First name** -- fields named `first_name`, `firstName`, `given_name`, `fname`, or with `autocomplete="given-name"`
- **Last name** -- fields named `last_name`, `lastName`, `family_name`, `lname`, `surname`, or with `autocomplete="family-name"`
- **Full name** -- fields named `name` or with `autocomplete="name"` (automatically split into first and last)

Hidden fields and invisible fields are ignored.

</Step>
<Step>

### Identifies the visitor

If an email or phone number is found, the tracker automatically calls `identify()`. This is the critical step that links the anonymous visitor (who clicked an ad) to their real identity (the person who filled out the form).

</Step>
<Step>

### Fires a `lead_submitted` event

A tracking event is recorded with the form ID and the page path, so you can see form submissions in your dashboard and use them as conversion goals.

</Step>
</Steps>

<Callout type="info" title="This is why attribution works">
The form auto-capture is where the magic happens. Before the form fill, Atribu only knows "an anonymous visitor clicked a Facebook ad and viewed 3 pages." After the form fill, Atribu knows "Jane at jane@example.com clicked a Facebook ad." When Jane later makes a payment through Stripe, Atribu connects it back to that original ad click.
</Callout>

<Callout type="warn" title="Custom or unusual form fields">
Atribu detects email and phone fields by checking the `type`, `name`, and `autocomplete` attributes on `<input>`, `<select>`, and `<textarea>` elements. If your form uses non-standard field names (like `data-email` or a custom web component), the tracker may not detect them. In that case, call `identify()` manually. See [Identify Users](/docs/tracking/identify-users) for details.
</Callout>

---

## Booking widget capture

Atribu detects when a visitor completes a booking through embedded scheduling widgets. The following platforms are supported:

| Platform | Detection method |
|----------|-----------------|
| **GoHighLevel** | Listens for the `msgsndr-booking-complete` postMessage from the GHL widget |
| **Calendly** | Listens for the `calendly.event_scheduled` postMessage |
| **Cal.com** | Listens for the `bookingSuccessful` postMessage |
| **Hash-based** | Detects URL hash changes like `#booking`, `#booked`, `#scheduled`, `#confirmed`, or `#thank-you` |

When a booking is detected:

1. Contact information is extracted when available (email, name, phone from the widget's message data)
2. `identify()` is called automatically if contact info is present
3. An `appointment_booked` event is fired

<Callout type="info" title="One booking per session">
To avoid duplicates, the booking event fires only once per session. If a visitor somehow triggers the booking completion message multiple times, only the first one is recorded.
</Callout>

### Custom booking patterns

If you use a booking widget that is not natively supported, you can register custom detection patterns:

```js title="Custom booking pattern"
window.ATRIBU_BOOKING_PATTERNS = [
  { match: "booking-confirmed", widget: "my_scheduler" }
];
```

The tracker will look for the `match` string in any `postMessage` data and fire the `appointment_booked` event when found.

---

## Stripe checkout capture

When you use Stripe Payment Links, customers are redirected back to your website after completing payment. The redirect URL contains a `session_id` parameter like:

```text title="Stripe redirect URL"
https://yoursite.com/thank-you?session_id=cs_live_a1b2c3...
```

Atribu detects this parameter and:

1. Fires a `stripe_checkout_completed` event with the Stripe checkout session ID
2. Cleans the `session_id` parameter from the URL (using `history.replaceState`) so the event does not fire again if the visitor refreshes the page

This allows Atribu to connect the payment to the browser session, which links it back to the original ad click.

<Callout type="info" title="Supported URL parameters">
The tracker checks for `session_id`, `checkout_session`, and `checkout_session_id` in the URL. Only values starting with `cs_` (Stripe's checkout session prefix) are processed.
</Callout>

---

## Next steps

<Cards>
  <Card title="Identify Users" href="/docs/tracking/identify-users">
    Manual identification for custom forms and login flows
  </Card>
  <Card title="Custom Events" href="/docs/tracking/custom-events">
    Track any action as a custom event
  </Card>
  <Card title="Identity Resolution" href="/docs/concepts/identity-resolution">
    How anonymous visitors become known customers across channels
  </Card>
  <Card title="Payment Links" href="/docs/features/payment-links">
    Create Stripe Payment Links with built-in attribution tracking
  </Card>
</Cards>

---

# Custom Domain

Use your own domain for tracking to improve data accuracy and avoid ad blockers.

---

## Why use a custom domain

Ad blockers and browser privacy features sometimes block third-party tracking scripts. When your tracking runs on a third-party domain, some visitors' data may be lost.

By setting up a custom subdomain (e.g., `t.yourdomain.com`), the tracker becomes a **first-party script** -- it runs on your own domain, just like the rest of your website. Most ad blockers do not block first-party requests, so you capture more accurate data.

---

## Setup

<Steps>
<Step>

### Open tracking settings

Go to **Settings > Tracking** in your Atribu dashboard.

</Step>
<Step>

### Enter your custom subdomain

Choose a subdomain on your domain, for example:

```text title="Example subdomains"
t.yourdomain.com
track.yourdomain.com
data.yourdomain.com
```

Any subdomain works. Keep it short -- `t` is a popular choice.

</Step>
<Step>

### Add the DNS record

Add a **CNAME** record at your DNS provider pointing your subdomain to Atribu's tracking server:

| Type | Name | Target |
|------|------|--------|
| CNAME | `t` | `track.atribu.app` |

The exact steps depend on your DNS provider (Cloudflare, GoDaddy, Namecheap, etc.). Look for "Add DNS record" or "Manage DNS" in your provider's dashboard.

</Step>
<Step>

### Verify the connection

Click **Verify** in Atribu. The system checks that your DNS record is properly configured and points to the correct target.

Once verified, your tracking snippet automatically uses the custom domain for all data collection.

</Step>
</Steps>

---

## Domain Connect (automatic setup)

If your DNS provider supports the Domain Connect protocol, Atribu can set up the DNS record for you automatically.

<Steps>
<Step>

### Enter your subdomain

Type your desired subdomain in the tracking settings.

</Step>
<Step>

### Click "Auto Setup"

Atribu checks if your DNS provider supports Domain Connect. If it does, you will see an "Auto Setup" option.

</Step>
<Step>

### Authorize the DNS change

You will be redirected to your DNS provider to approve the CNAME record. Once approved, the record is created automatically and verification happens right away.

</Step>
</Steps>

<Callout type="info" title="Domain Connect support">
Domain Connect is supported by many major DNS providers including GoDaddy, 1&1 IONOS, and others. If your provider does not support it, use the manual CNAME setup described above.
</Callout>

---

## Troubleshooting

### "DNS not verified" after adding the record

DNS changes can take time to propagate. In most cases it takes 1-5 minutes, but it can take up to **48 hours** depending on your DNS provider and TTL settings. Wait a few minutes and click **Verify** again.

### Using Cloudflare

If you use Cloudflare, you have two options:

- **DNS Only (gray cloud)** -- The CNAME resolves normally. Verification works as expected.
- **Proxied (orange cloud)** -- Cloudflare flattens the CNAME to an A/AAAA record. Atribu supports this and verifies by checking that the resolved IP addresses match.

Both options work. No special configuration is needed.

### Removing a custom domain

Go to **Settings > Tracking**, find your custom domain, and click **Remove**. Your tracking snippet will revert to using the default Atribu domain.

---

## Next steps

<Cards>
  <Card title="How Tracking Works" href="/docs/tracking/how-tracking-works">
    Understand visitors, sessions, and cookies
  </Card>
  <Card title="UTM Parameters" href="/docs/tracking/utm-parameters">
    Tag your traffic sources for accurate attribution
  </Card>
  <Card title="Data Quality" href="/docs/features/data-quality">
    Monitor tracking coverage and fix attribution gaps
  </Card>
</Cards>

---

# Custom Events

Beyond automatic page views and form captures, you can track any action on your website as a custom event. Use custom events to measure button clicks, video plays, pricing page views, or anything else that matters to your business.

---

## Basic event tracking

Call `window.atribuTracker.track()` with an event name and optional properties:

```js title="Track a custom event"
window.atribuTracker.track("button_clicked", {
  payload: {
    button_name: "pricing_cta",
    page: "/pricing",
  },
});
```

The event name is the first argument. The second argument is an object with a `payload` key containing any properties you want to attach to the event.

---

## Revenue tracking

For events that involve money (purchases, upgrades, upsells), use `trackRevenue()` to include the amount and currency:

```js title="Track a purchase with revenue"
window.atribuTracker.trackRevenue("purchase", 99.99, "USD", {
  product: "Pro Plan",
  billing_cycle: "monthly",
});
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `eventName` | string | Name of the event |
| `amount` | number | Revenue amount |
| `currency` | string | ISO currency code (USD, EUR, MXN, etc.) |
| `data` | object | Optional additional properties |

---

## Page view tracking

Page views are tracked automatically on every page load. For single-page applications (SPAs) where the page changes without a full reload, you can fire page views manually:

```js title="Manual page view for SPA navigation"
window.atribuTracker.page();
```

<Callout type="info" title="SPA auto-detection">
Atribu automatically detects `pushState` and `replaceState` navigation in most SPAs (React, Next.js, Vue, etc.) and fires page views without manual calls. Only use manual page views if auto-detection is not working for your setup.
</Callout>

---

## Event naming best practices

Good event names make your reports easier to read and your conversion goals easier to set up.

**Use `snake_case`**

```js title="Good event names"
window.atribuTracker.track("video_played", { payload: { video_id: "abc" } });
window.atribuTracker.track("pricing_viewed", { payload: { plan: "pro" } });
window.atribuTracker.track("checkout_started", { payload: { cart_value: 149 } });
```

**Be specific**

| Instead of | Use |
|-----------|-----|
| `click` | `pricing_cta_clicked` |
| `submit` | `contact_form_submitted` |
| `view` | `case_study_viewed` |
| `purchase` | `subscription_purchased` |

**Stay consistent**

Pick a naming convention and use it everywhere. If your first event is `form_submitted`, do not switch to `formSubmit` or `FormSubmitted` later.

---

## Using custom events as conversion goals

Any custom event can be turned into a conversion goal in Atribu. This means the attribution engine will track which ads and campaigns drive that specific action.

<Steps>
<Step>

### Track the event on your site

```js title="Track a custom conversion event"
window.atribuTracker.track("demo_requested", {
  payload: {
    plan_interest: "enterprise",
  },
});
```

</Step>
<Step>

### Create a conversion definition in Atribu

Go to **Settings > Conversions** and create a new conversion definition. Set the **Source event name** to `demo_requested`. Atribu will start attributing this event to your campaigns.

</Step>
</Steps>

<Callout type="info" title="Automatic events work too">
You can also create conversion definitions for auto-captured events like `lead_submitted` and `appointment_booked`. Any event that flows through the tracker can become an attribution goal.
</Callout>

---

## Additional tracking methods

### Set a user ID

If your app has its own user ID system, you can link it to the Atribu visitor:

```js title="Set a custom user ID"
window.atribuTracker.setUserId("user_12345");
```

### Observe element impressions

Track when a specific element becomes visible on screen (useful for measuring ad impressions or content visibility):

```js title="Track element visibility"
var cleanup = window.atribuTracker.observeImpression("#hero-banner", {
  payload: { banner_variant: "spring_sale" },
});

// Call cleanup() to stop observing
```

The event fires once when the element is at least 50% visible. You can customize the threshold:

```js title="Custom visibility threshold"
window.atribuTracker.observeImpression("#cta-section", {}, {
  threshold: 0.75, // Fire when 75% visible
});
```

---

## Next steps

<Cards>
  <Card title="Identify Users" href="/docs/tracking/identify-users">
    Connect anonymous visitors to real people
  </Card>
  <Card title="UTM Parameters" href="/docs/tracking/utm-parameters">
    Tag your traffic sources for accurate attribution
  </Card>
  <Card title="Conversion Goals" href="/docs/settings/goals">
    Turn custom events into conversion goals for attribution
  </Card>
</Cards>

---

Detailed setup instructions for each framework. The `@atribu/tracker` package works with any JavaScript framework thanks to its SSR-safe design.

## React

Create a provider component that initializes the tracker once:

```tsx title="src/components/AtribuProvider.tsx"
import { useEffect } from "react";
import { init } from "@atribu/tracker";

export function AtribuProvider({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    init({
      trackingKey: "trk_live_your_key",
      // Optional: customize behavior
      sessionTimeoutMinutes: 30,
      enableWebVitals: true,
    });
  }, []);

  return <>{children}</>;
}
```

```tsx title="src/main.tsx"
import { AtribuProvider } from "./components/AtribuProvider";

function App() {
  return (
    <AtribuProvider>
      <YourApp />
    </AtribuProvider>
  );
}
```

### Tracking events in components

```tsx title="src/components/PricingButton.tsx"
import { track } from "@atribu/tracker";

export function PricingButton() {
  return (
    <button onClick={() => track("pricing_clicked", { plan: "pro" })}>
      View Pricing
    </button>
  );
}
```

### Identifying users after form submission

```tsx title="src/components/ContactForm.tsx"
import { identify } from "@atribu/tracker";

export function ContactForm() {
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const data = new FormData(e.currentTarget);

    identify({
      email: data.get("email") as string,
      firstName: data.get("name") as string,
    });

    // Submit form...
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="Name" />
      <input name="email" type="email" placeholder="Email" />
      <button type="submit">Submit</button>
    </form>
  );
}
```

---

## Next.js (App Router)

<Callout type="warn" title="Client component required">
The tracker uses browser APIs (`window`, `localStorage`). It must be initialized in a Client Component with the `"use client"` directive.
</Callout>

```tsx title="src/components/AtribuProvider.tsx"
"use client";

import { useEffect } from "react";
import { init } from "@atribu/tracker";

export function AtribuProvider({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    init({ trackingKey: "trk_live_your_key" });
  }, []);

  return <>{children}</>;
}
```

```tsx title="src/app/layout.tsx"
import { AtribuProvider } from "@/components/AtribuProvider";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <AtribuProvider>{children}</AtribuProvider>
      </body>
    </html>
  );
}
```

<Callout type="info" title="SSR safe">
If you accidentally import `@atribu/tracker` in a Server Component, `init()` returns a silent no-op client. No errors, no crashes — it simply does nothing until running in the browser.
</Callout>

---

## Vue 3

```vue title="src/App.vue"
<script setup lang="ts">
import { onMounted } from "vue";
import { init } from "@atribu/tracker";

onMounted(() => {
  init({ trackingKey: "trk_live_your_key" });
});
</script>

<template>
  <RouterView />
</template>
```

### Tracking events

```vue title="src/components/PricingCard.vue"
<script setup lang="ts">
import { track } from "@atribu/tracker";

function handleClick() {
  track("pricing_clicked", { plan: "pro" });
}
</script>

<template>
  <button @click="handleClick">View Pricing</button>
</template>
```

---

## Svelte

```svelte title="src/App.svelte"
<script>
  import { onMount } from "svelte";
  import { init } from "@atribu/tracker";

  onMount(() => {
    init({ trackingKey: "trk_live_your_key" });
  });
</script>

<slot />
```

---

## Vanilla JavaScript

```html title="index.html"
<script type="module">
  import { init, track, identify } from "@atribu/tracker";

  init({ trackingKey: "trk_live_your_key" });

  // Track a custom event
  document.querySelector("#cta").addEventListener("click", () => {
    track("cta_clicked");
  });

  // Identify after form submit
  document.querySelector("form").addEventListener("submit", (e) => {
    const email = e.target.querySelector('[name="email"]').value;
    identify({ email });
  });
</script>
```

---

## SPA navigation

The tracker automatically detects Single Page App navigation via:
- `history.pushState` and `history.replaceState` interception
- `popstate` event listener
- `hashchange` event listener

Each navigation fires a new `page_view` event and resets engagement tracking (scroll depth, time on page). No additional configuration needed.

---

## SSR environments

In server-side rendering (Next.js, Nuxt, SvelteKit SSR), the tracker handles the missing `window` gracefully:

1. `init()` detects `typeof window === "undefined"`
2. Returns a **no-op client** where all methods (`track`, `identify`, etc.) are empty functions
3. No errors thrown, no `window is not defined` crashes
4. When the code later runs in the browser, `init()` activates normally

This means you can safely import and call tracker functions anywhere — server or client.

## Related

<Cards>
  <Card title="npm Package" href="/docs/tracking/npm-package">
    Full API reference and configuration options
  </Card>
  <Card title="Install Tracking" href="/docs/getting-started/install-tracker">
    Script tag installation (alternative to npm)
  </Card>
</Cards>

---

# How Tracking Works

When someone visits your website, Atribu's tracker records what they do — which pages they view, where they came from, and how they interact with your site. This data powers your dashboard, attribution reports, and revenue insights.

```mermaid
flowchart LR
    A["Browser"] -->|page view| B["Atribu Tracker\n(JavaScript)"]
    B -->|events| C["/api/tracking/collect"]
    C --> D["Enrichment Pipeline"]
    D --> E["events_enriched"]
    E --> F["Sessions & Views"]
    F --> G["Dashboard"]
```

---

## Two IDs, two concepts

Atribu uses two identifiers to understand your traffic. Both are generated automatically -- no setup required.

### Visitor ID (anonymous_id)

Think of this as a fingerprint for each device. The same person visiting your site from the same browser and device will always be recognized as the same visitor. This ID is stored in the browser for **1 year**, so returning visitors are counted accurately.

- Stored in `localStorage` as `atribu_anon_id`
- Also written as a first-party cookie (`atribu_visitor_id`) so server-side code can read it
- One per device/browser combination -- if someone visits from their phone and their laptop, that counts as two visitors

### Session ID

A session represents a single **visit**. It starts when someone arrives on your site and ends after **30 minutes of inactivity**. If they come back an hour later, that is a new session (a new visit), but the same visitor.

- Stored in `localStorage` as `atribu_session_id`
- Also written as a cookie (`atribu_session_id`) with a 30-minute expiry
- A new session also starts when the marketing source changes (e.g., a visitor clicks a different ad)

<Callout type="info" title="Visitors vs. sessions">
Your dashboard shows both metrics. **Visitors** tells you how many unique people came to your site. **Sessions** tells you how many total visits happened. One visitor can have many sessions.
</Callout>

---

## What gets captured on every page view

Each time a visitor loads a page, Atribu records:

| Data point | Example |
|-----------|---------|
| **Page URL** | `/pricing`, `/blog/how-to-run-ads` |
| **Referrer** | `google.com`, `facebook.com`, direct |
| **UTM parameters** | `utm_source=facebook`, `utm_medium=paid` |
| **Click IDs** | `fbclid`, `gclid`, `msclkid`, `ttclid` |
| **Device type** | Desktop, mobile, tablet |
| **Browser** | Chrome, Safari, Firefox |
| **Country** | Based on IP address |

---

## How sessions work

<Steps>
<Step>

### Visitor arrives

When someone lands on your site, the tracker checks if an active session exists. If the last activity was more than 30 minutes ago (or no session exists), a new session starts.

</Step>
<Step>

### Activity is tracked

Every page view, form submission, and custom event updates the session's "last seen" timestamp. As long as the visitor keeps interacting within 30-minute windows, it all counts as one session.

</Step>
<Step>

### Session ends

After 30 minutes of inactivity (no page views, no clicks, no events), the session expires. If the visitor returns after that, a new session begins.

</Step>
</Steps>

### Configuring the session timeout

The default timeout is 30 minutes, which works well for most websites. If you need a different value, you can configure it in your tracking snippet:

```js title="Custom session timeout (15 minutes)"
window.ATRIBU_SESSION_TIMEOUT_MINUTES = 15;
```

Valid range: 1 to 120 minutes.

### Source-change detection

By default, a new session starts only after inactivity. You can also start a new session when the marketing source changes -- for example, if a visitor clicks a Google ad, then clicks a Facebook ad 10 minutes later:

```js title="Enable source-change session splitting"
window.ATRIBU_SESSION_MODE = "inactivity_or_source_change";
```

---

## Cookies explained

Atribu writes two first-party cookies on your domain:

| Cookie | Lifetime | Purpose |
|--------|----------|---------|
| `atribu_visitor_id` | 1 year | Stores the visitor's anonymous ID so server-side code (like checkout flows) can read it and pass it to payment providers |
| `atribu_session_id` | 30 minutes | Stores the current session ID for the same reason |

These cookies are **first-party** (set on your own domain), **SameSite=Lax**, and contain only random identifiers -- no personal data.

<Callout type="info" title="Privacy by default">
Atribu does not collect any personal information until a visitor voluntarily identifies themselves (by filling out a form, booking an appointment, etc.). Until that point, visitors are completely anonymous -- Atribu only knows that "someone on this device" viewed certain pages. Learn how this anonymous-to-known transition works in [Identity Resolution](/docs/concepts/identity-resolution).
</Callout>

---

## Next steps

<Cards>
  <Card title="Auto-Capture" href="/docs/tracking/auto-capture">
    How Atribu automatically detects form submissions, bookings, and payments
  </Card>
  <Card title="Identify Users" href="/docs/tracking/identify-users">
    Connect anonymous visitors to real people for full attribution
  </Card>
  <Card title="UTM Parameters" href="/docs/tracking/utm-parameters">
    Tag your traffic sources for accurate channel classification
  </Card>
  <Card title="Custom Domain" href="/docs/tracking/custom-domain">
    Bypass ad blockers by routing tracking through your own domain
  </Card>
</Cards>

---

# Identify Users

The `identify()` function is the most important part of Atribu's tracking. It connects an anonymous website visitor to a real person -- and that connection is what makes attribution possible.

---

## Why identification matters

Without identification, Atribu sees two disconnected worlds:

- **Website side**: An anonymous visitor clicked a Facebook ad, viewed 3 pages, and left
- **Business side**: Jane Smith paid $500 through Stripe

The `identify()` call bridges this gap. Once a visitor is identified (usually through a form fill), Atribu can trace the entire journey from ad click to payment.

---

## How it works

<Steps>
<Step>

### Visitor clicks an ad

A potential customer clicks your Facebook ad and lands on your website. Atribu assigns them an `anonymous_id` -- a random identifier tied to their device.

</Step>
<Step>

### Visitor fills out a form

The visitor fills out a contact form with their email. The tracker automatically calls `identify({ email: "jane@example.com" })`.

</Step>
<Step>

### Atribu creates a customer profile

Behind the scenes, Atribu creates a customer profile that links the `anonymous_id` to the email address. The visitor is no longer anonymous -- they are Jane Smith.

</Step>
<Step>

### A payment arrives later

When Stripe sends a payment event for `jane@example.com`, Atribu looks up the customer profile, finds the linked `anonymous_id`, and traces it back to the original ad click. Full attribution is achieved.

</Step>
</Steps>

---

## Automatic identification

In most cases, you do not need to call `identify()` yourself. Atribu's [auto-capture](/docs/tracking/auto-capture) handles it for you:

- **Form submissions** -- email and phone are extracted from form fields and `identify()` is called before the `lead_submitted` event fires
- **Booking widgets** -- GoHighLevel, Calendly, and Cal.com widget completions extract contact info and call `identify()` automatically

If your site uses standard HTML forms or supported booking widgets, identification happens without any extra code.

---

## Manual identification

For custom forms, login flows, or other scenarios where auto-capture does not apply, call `identify()` directly.

<Tabs items={["After a custom form", "After login/signup", "With phone number"]}>
<Tab value="After a custom form">
```js title="Identify after a custom form submit"
document.querySelector("#my-form").addEventListener("submit", function () {
  var email = document.querySelector("#email-field").value;
  var name = document.querySelector("#name-field").value;

  window.atribuTracker.identify({
    email: email,
    firstName: name.split(" ")[0],
    lastName: name.split(" ").slice(1).join(" "),
  });
});
```
</Tab>
<Tab value="After login/signup">
```js title="Identify after user logs in"
async function onLoginSuccess(user) {
  window.atribuTracker.identify({
    email: user.email,
    firstName: user.firstName,
    lastName: user.lastName,
    userId: user.id,
  });
}
```
</Tab>
<Tab value="With phone number">
```js title="Identify with phone number"
window.atribuTracker.identify({
  phone: "+1-555-123-4567",
  firstName: "Jane",
});
```
</Tab>
</Tabs>

### Accepted fields

| Field | Type | Description |
|-------|------|-------------|
| `email` | string | Email address (highest priority for matching) |
| `phone` | string | Phone number (second priority) |
| `firstName` | string | First name |
| `lastName` | string | Last name |
| `userId` | string | Your internal user ID |

---

## Identity priority

When matching visitors to customer profiles, Atribu uses this priority order:

1. **Email** -- the strongest identifier. If two events share the same email, they are the same person.
2. **Phone number** -- used when email is not available.
3. **External IDs** -- provider-specific IDs like Stripe customer IDs or GHL contact IDs.

Atribu normalizes all identifiers before matching (lowercasing emails, stripping phone formatting), so `Jane@Example.com` and `jane@example.com` are treated as the same person.

---

## Safe to call multiple times

Calling `identify()` more than once is safe and encouraged. If a visitor fills out a form with their email and later provides their phone number, calling `identify()` again adds the phone number to the existing profile without overwriting the email.

```js title="Progressive enrichment"
// First form: email only
window.atribuTracker.identify({ email: "jane@example.com" });

// Later: phone number captured from a different form
window.atribuTracker.identify({ phone: "+1-555-123-4567" });

// Both identifiers are now linked to the same customer profile
```

<Callout type="error" title="Critical: every conversion point needs identification">
Without `identify()`, server-side events like payments (Stripe, MercadoPago) and CRM events (GoHighLevel) cannot be attributed to ad clicks. Make sure every form, checkout flow, or conversion point on your site calls `identify()` -- either through auto-capture or manually.
</Callout>

---

## Next steps

<Cards>
  <Card title="Auto-Capture" href="/docs/tracking/auto-capture">
    See which forms and widgets are automatically captured
  </Card>
  <Card title="Custom Events" href="/docs/tracking/custom-events">
    Track custom actions beyond form submissions
  </Card>
  <Card title="Identity Resolution" href="/docs/concepts/identity-resolution">
    How the identity graph connects visitors, emails, and payments
  </Card>
  <Card title="Data Quality" href="/docs/features/data-quality">
    Monitor identification coverage and fix missing data
  </Card>
</Cards>

---

The `@atribu/tracker` npm package is the official TypeScript SDK for Atribu. It bundles the full tracker runtime with type-safe configuration, framework-agnostic design, and automatic SSR safety.

## Installation

```bash title="Install"
npm install @atribu/tracker
```

## Quick start

<Tabs items={["React", "Next.js", "Vue 3", "Vanilla JS"]}>
<Tab value="React">
```tsx title="src/App.tsx"
import { useEffect } from "react";
import { init } from "@atribu/tracker";

function App() {
  useEffect(() => {
    init({ trackingKey: "trk_live_your_key" });
  }, []);

  return <div>Your app</div>;
}
```
</Tab>
<Tab value="Next.js">
```tsx title="src/components/AtribuProvider.tsx"
"use client";

import { useEffect } from "react";
import { init } from "@atribu/tracker";

export function AtribuProvider({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    init({ trackingKey: "trk_live_your_key" });
  }, []);

  return <>{children}</>;
}
```

Then wrap your root layout:
```tsx title="src/app/layout.tsx"
import { AtribuProvider } from "@/components/AtribuProvider";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <AtribuProvider>{children}</AtribuProvider>
      </body>
    </html>
  );
}
```
</Tab>
<Tab value="Vue 3">
```vue title="src/App.vue"
<script setup>
import { onMounted } from "vue";
import { init } from "@atribu/tracker";

onMounted(() => {
  init({ trackingKey: "trk_live_your_key" });
});
</script>
```
</Tab>
<Tab value="Vanilla JS">
```html title="index.html"
<script type="module">
  import { init } from "@atribu/tracker";
  init({ trackingKey: "trk_live_your_key" });
</script>
```
</Tab>
</Tabs>

## Configuration

All options passed to `init()`:

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `trackingKey` | `string` | **required** | Your ingest key from Settings > Tracking |
| `apiHost` | `string` | `window.location.origin` | Origin for the collect endpoint |
| `trackingEndpoint` | `string` | `{apiHost}/api/tracking/collect` | Full URL override for tracking |
| `interceptMetaFbq` | `boolean` | `true` | Intercept Meta Pixel `fbq()` calls for server-side deduplication |
| `metaBridgePageview` | `boolean` | `false` | Also mirror Meta PageView events |
| `sessionTimeoutMinutes` | `number` | `30` | Session inactivity timeout (1-120 min) |
| `sessionMode` | `string` | `"inactivity_only"` | `"inactivity_only"` or `"inactivity_or_source_change"` |
| `heartbeatIntervalSeconds` | `number \| false` | `60` | Engagement heartbeat interval (crash-recovery checkpoint). `0` or `false` to disable |
| `enableClickQuality` | `boolean` | `true` | Track rage clicks and dead clicks |
| `enableWebVitals` | `boolean` | `true` | Capture Core Web Vitals (LCP, FCP, CLS, INP, TTFB) |
| `enableCtaTracking` | `boolean` | `true` | Auto-detect and track CTA button visibility |
| `enableVideoTracking` | `boolean` | `true` | Auto-detect `<video>` elements and track milestones |
| `ignoredPages` | `string[]` | — | URL patterns to skip tracking (e.g., `["/admin/*"]`) |
| `customProperties` | `object \| function` | — | Static properties or function merged into every event |
| `transformRequest` | `function` | — | Middleware to modify or suppress events before sending |

### Custom properties

```ts title="Static properties"
init({
  trackingKey: "trk_live_...",
  customProperties: {
    environment: "production",
    appVersion: "2.1.0",
  },
});
```

```ts title="Dynamic properties (function)"
init({
  trackingKey: "trk_live_...",
  customProperties: (ctx) => ({
    currentPath: ctx.path,
    isLoggedIn: !!localStorage.getItem("token"),
  }),
});
```

### Transform request (middleware)

```ts title="Filter out events"
init({
  trackingKey: "trk_live_...",
  transformRequest: (event) => {
    // Suppress events from admin pages
    if (event.path?.startsWith("/admin")) return null;
    // Add custom header
    event.payload.buildId = "abc123";
    return event;
  },
});
```

## Tracking events

```ts title="Basic event"
import { track } from "@atribu/tracker";

track("button_clicked", { buttonName: "pricing_cta" });
```

```ts title="Revenue event"
import { trackRevenue } from "@atribu/tracker";

trackRevenue("purchase", 99.99, "USD", { plan: "Pro" });
```

```ts title="Self-describing event (with schema)"
import { trackSelfDescribing } from "@atribu/tracker";

trackSelfDescribing({
  eventSchema: "com.atribu.checkout",
  schemaVersion: 1,
  payload: { step: "payment", method: "credit_card" },
});
```

## Identifying users

```ts title="Identify after form submission"
import { identify } from "@atribu/tracker";

identify({
  email: "jane@example.com",
  firstName: "Jane",
  lastName: "Smith",
  phone: "+1-555-0123",
});
```

<Callout type="error" title="Critical for attribution">
Without `identify()`, anonymous visitors cannot be linked to payments or CRM events. Call it on every form submission, login, or signup. See [User Identification](/docs/tracking/identify-users) for details.
</Callout>

## Consent management

```ts title="Set consent after cookie banner"
import { setConsent } from "@atribu/tracker";

// After user accepts analytics cookies
setConsent({ analytics: true, marketing: false });
```

Consent state is persisted in `localStorage` and attached to every subsequent event.

## Impressions

```ts title="Track when an element becomes visible"
import { observeImpression } from "@atribu/tracker";

// CSS selector
const cleanup = observeImpression("#hero-banner", { section: "hero" });

// DOM element
const el = document.querySelector(".pricing-card");
const cleanup2 = observeImpression(el, { plan: "pro" }, { threshold: 0.8 });

// Cleanup on unmount
cleanup();
cleanup2();
```

## Lifecycle

```ts title="Flush and reset"
import { flush, reset } from "@atribu/tracker";

// Force-send all queued events (before navigation)
flush();

// Clear all stored state (on logout)
reset();
```

## Auto-captured events

The package automatically captures these events with zero configuration:

| Category | Events |
|----------|--------|
| **Navigation** | Page views, SPA navigation (pushState, popstate, hashchange) |
| **Sessions** | Session start/end, configurable timeout |
| **Engagement** | Scroll depth, time on page, heartbeat |
| **Links** | Outbound link clicks, file downloads (PDF, ZIP, etc.) |
| **Forms** | Form submissions with email/phone extraction |
| **Bookings** | GHL, Calendly, Cal.com widget completions |
| **Meta Pixel** | `fbq()` interception for server-side dedup |
| **Stripe** | Checkout completion detection (`?session_id=cs_*`) |
| **GHL Forms** | `fetch()` interception for div-based forms |
| **Click Quality** | Rage clicks (3+ rapid), dead clicks (optional) |
| **Web Vitals** | LCP, FCP, CLS, INP, TTFB (optional) |
| **CTA Visibility** | Action button/link visibility tracking (optional) |
| **Video** | `<video>` play/pause/milestones (optional) |
| **Errors** | Uncaught JavaScript errors |
| **Bot Detection** | Filters bots, tags AI agents (ChatGPT, Claude, etc.) |

## Script tag vs npm

| | npm Package | Script Tag |
|---|---|---|
| **Install** | `npm install @atribu/tracker` | `<script src="..."></script>` |
| **TypeScript** | Full type safety | No types |
| **Frameworks** | React, Next.js, Vue, Svelte | Vanilla HTML only |
| **SSR** | Built-in no-op client | Client-side only |
| **Config** | Typed `init()` object | Window variables |
| **Module** | ESM + CommonJS | IIFE global |
| **Functionality** | Identical | Identical |

## TypeScript support

All types are exported:

```ts title="Import types"
import type {
  AtribuConfig,
  AtribuClient,
  TrackOptions,
  IdentifyInput,
  ConsentPayload,
  TrackingEvent,
} from "@atribu/tracker";
```

## SSR safety

When `window` is undefined (Node.js, Next.js server components, SSR), `init()` returns a **silent no-op client**. All methods (`track`, `identify`, etc.) are safe to call — they simply do nothing server-side and activate once the code runs in the browser.

```ts title="Safe in any environment"
import { init, track } from "@atribu/tracker";

// This works in SSR — returns no-op client, no errors
const client = init({ trackingKey: "trk_live_..." });

// This is safe server-side — silently ignored
track("page_loaded");
```

## Related

<Cards>
  <Card title="Install Tracking" href="/docs/getting-started/install-tracker">
    Script tag installation for non-npm setups
  </Card>
  <Card title="Framework Guides" href="/docs/tracking/framework-guides">
    Detailed setup for React, Next.js, Vue, and Svelte
  </Card>
  <Card title="Custom Events" href="/docs/tracking/custom-events">
    Track custom actions and revenue events
  </Card>
</Cards>

---

# UTM Parameters

UTM parameters tell Atribu where your traffic comes from. They are small tags added to the end of your URLs that identify the traffic source, medium, and campaign.

---

## What are UTM parameters?

When you share a link in an ad, email, or social post, you can add UTM parameters to the URL so Atribu knows exactly where the visitor came from. Here is an example:

```text title="URL with UTM parameters"
https://yoursite.com/landing-page?utm_source=facebook&utm_medium=paid&utm_campaign=spring_sale&utm_content=video_ad_1
```

Everything after the `?` is a set of key-value pairs that Atribu reads and stores with the visitor's session. This data powers your Traffic Sources breakdown and campaign-level attribution.

---

## UTM parameters Atribu reads

| Parameter | What it tells Atribu | Example values |
|-----------|---------------------|----------------|
| `utm_source` | Where the traffic comes from | `facebook`, `google`, `newsletter`, `linkedin` |
| `utm_medium` | How they got there | `paid`, `cpc`, `email`, `organic`, `social` |
| `utm_campaign` | Which campaign sent them | `spring_sale`, `black_friday`, `webinar_march` |
| `utm_content` | Which ad or content variation | `video_ad_1`, `banner_blue`, `cta_bottom` |
| `utm_term` | Which keyword (for search ads) | `marketing_tools`, `crm_software` |

<Callout type="info" title="Only utm_source and utm_medium are required">
At minimum, always include `utm_source` and `utm_medium`. The other parameters are optional but highly recommended for detailed reporting.
</Callout>

---

## Click IDs (automatic, no setup needed)

Ad platforms automatically append a click ID to your URLs when someone clicks an ad. These are separate from UTM parameters -- you do not need to set them up.

| Click ID | Platform | What it does |
|----------|----------|-------------|
| `fbclid` | Meta (Facebook / Instagram) | Automatically added by Meta to every ad click |
| `gclid` | Google Ads | Automatically added by Google to every ad click |
| `msclkid` | Microsoft / Bing Ads | Automatically added by Microsoft to every ad click |
| `ttclid` | TikTok Ads | Automatically added by TikTok to every ad click |

Atribu detects these click IDs and uses them for two things:

1. **Channel classification** -- A visit with `fbclid` is classified as Paid Social, even if UTM parameters are missing
2. **Ad-level attribution** -- Click IDs help trace a specific click back to the exact ad that generated it

<Callout type="info" title="Click IDs take priority">
If a visit has both a click ID and UTM parameters, the click ID takes priority for channel classification. This prevents misclassification when UTM tags are missing or incorrect. For example, a visit with `gclid` is always classified as **Paid Search**, regardless of what `utm_medium` says.
</Callout>

---

## How UTM parameters feed into channels

Atribu automatically classifies each visit into a marketing channel based on UTMs, referrer, and click IDs:

| What Atribu sees | Classified as |
|-----------------|---------------|
| `utm_medium=paid` or `utm_medium=cpc` with `utm_source=facebook` | **Paid Social** |
| `utm_medium=paid` or `utm_medium=cpc` with `utm_source=google` | **Paid Search** |
| `fbclid` or `ttclid` in URL (no social referrer) | **Paid Social** |
| `gclid` or `msclkid` in URL | **Paid Search** |
| `utm_medium=email` | **Email** |
| `utm_medium=affiliate` | **Affiliates** |
| `utm_medium=display` | **Display** |
| Referrer from google.com, bing.com (no paid indicators) | **Organic Search** |
| Referrer from facebook.com, instagram.com (no paid indicators) | **Organic Social** |
| Other referrer | **Referral** |
| No referrer, no UTMs, no click IDs | **Direct** |

---

## Best practices

### Always pair utm_source and utm_medium

These two parameters work together. `utm_source` identifies the platform, `utm_medium` identifies the type of traffic:

```text title="Good: source + medium together"
?utm_source=facebook&utm_medium=paid
?utm_source=google&utm_medium=cpc
?utm_source=mailchimp&utm_medium=email
```

### Use consistent, lowercase naming

Pick a convention and stick with it across all your campaigns:

```text title="Consistent naming"
utm_source=facebook    (not Facebook, fb, or FB)
utm_medium=paid        (not Paid, cpc, or PPC)
utm_campaign=spring_sale  (not Spring Sale or SpringSale)
```

Atribu treats `Facebook` and `facebook` as different sources, so inconsistent casing leads to fragmented reports.

### Do not use UTMs on internal links

UTM parameters should only be used on links from **external** sources pointing to your site. Never add UTMs to links within your own website (like navigation menus or internal banners) -- this overwrites the original traffic source and breaks attribution.

```text title="External link (correct)"
Email newsletter link: https://yoursite.com/sale?utm_source=newsletter&utm_medium=email

Internal link (never do this):
https://yoursite.com/pricing?utm_source=homepage&utm_medium=banner
```

### Include utm_content for ad variations

When running multiple ad variations in the same campaign, use `utm_content` to distinguish them:

```text title="Distinguishing ad variations"
?utm_source=facebook&utm_medium=paid&utm_campaign=spring_sale&utm_content=video_ad
?utm_source=facebook&utm_medium=paid&utm_campaign=spring_sale&utm_content=carousel_ad
?utm_source=facebook&utm_medium=paid&utm_campaign=spring_sale&utm_content=static_image
```

This lets you see which creative variation drives the most conversions.

---

## Meta Ads and UTM parameters

When you connect your Meta Ads account to Atribu, campaign and ad data is synced automatically. Meta also appends `fbclid` to every ad click URL. For the best results, add UTM parameters to your Meta ad URLs:

```text title="Recommended Meta ad URL parameters"
?utm_source=facebook&utm_medium=paid&utm_campaign={{campaign.name}}&utm_content={{ad.name}}
```

Meta's dynamic URL parameters (`{{campaign.name}}`, `{{ad.name}}`) are automatically replaced with the actual campaign and ad names when someone clicks.

---

## Next steps

<Cards>
  <Card title="Channel Classification" href="/docs/concepts/channels">
    Full reference of how channels are classified
  </Card>
  <Card title="How Tracking Works" href="/docs/tracking/how-tracking-works">
    Understand visitors, sessions, and cookies
  </Card>
  <Card title="Traffic Sources" href="/docs/features/traffic-sources">
    See how UTM data appears in your dashboard breakdown boards
  </Card>
</Cards>

---

Todas las solicitudes a la API requieren un token Bearer en el encabezado `Authorization`.

```bash title="Encabezado de autorizacion"
Authorization: Bearer atb_live_your_key_here
```

## Obtener una clave

Crea claves de API desde **Configuracion > Desarrollador** en el dashboard de Atribu. Cada clave esta asociada a un solo perfil.

<Callout type="warn" title="Se muestra una sola vez">
Las claves se muestran exactamente una vez al crearlas. Guardalas de forma segura -- no podras recuperarlas despues.
</Callout>

## Permisos

Cada clave de API tiene permisos granulares que controlan a que datos puede acceder.

| Permiso | Acceso |
|---------|--------|
| `analytics:read` | Resumen, series de tiempo, desgloses (canales, paginas, paises, dispositivos, navegadores, SO, referidores), calidad, palabras clave |
| `campaigns:read` | Rendimiento de campanas, ROAS, desgloses por conjunto de anuncios/anuncio, tendencias diarias |
| `conversions:read` | Conteos de conversiones, series de tiempo de conversiones diarias, ingresos, efectivo cobrado |
| `customers:read` | Lista de clientes con PII (nombre, email), lineas de tiempo de recorrido individual |
| `visitors:read` | Lista de visitantes con PII (nombre, email), historial de sesiones |
| `realtime:read` | Conteo de visitantes en vivo |

**Permisos predeterminados** al crear una clave: `analytics:read`, `campaigns:read`, `conversions:read`, `realtime:read`.

<Callout type="error" title="Permisos de PII">
`customers:read` y `visitors:read` exponen datos personales (nombres, emails, detalles de recorrido). Solo otorga estos cuando tu integracion necesite especificamente datos a nivel de cliente.
</Callout>

## Limites de tasa

Predeterminado: **60 solicitudes por minuto** por clave.

Los endpoints pesados (clientes, visitantes, detalle de recorrido) cuestan 3x -- lo que significa que consumen 3 unidades de tu limite de tasa por llamada.

| Encabezado | Descripcion |
|------------|-------------|
| `X-RateLimit-Limit` | Tu limite total por minuto |
| `X-RateLimit-Remaining` | Solicitudes restantes en la ventana actual |
| `X-Request-Id` | ID unico de solicitud para depuracion |

Cuando se excede el limite de tasa, recibiras una respuesta `429` con un encabezado `Retry-After` (segundos).

## Rotacion de claves

Rota claves sin tiempo de inactividad:

<Steps>
<Step>
Llama al endpoint de **Rotacion** -- crea una nueva clave
</Step>
<Step>
Tanto la clave antigua como la nueva funcionan durante **48 horas**
</Step>
<Step>
Actualiza tu integracion con la nueva clave
</Step>
<Step>
La clave antigua expira automaticamente despues del periodo de gracia
</Step>
</Steps>

## Mejores practicas de seguridad

<Callout type="info" title="Solo servidor a servidor">
Esta es una API de servidor a servidor. Nunca expongas las claves en JavaScript del lado del cliente, aplicaciones moviles o codigo del navegador.
</Callout>

- **Usa los permisos minimos necesarios** -- no otorgues `customers:read` si solo necesitas analiticas
- **Rota las claves regularmente** -- el endpoint de rotacion lo hace sencillo
- **Revoca las claves comprometidas inmediatamente** -- la revocacion es instantanea

## Respuestas de error

```json title="Respuesta de error"
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or expired API key",
    "status": 401,
    "request_id": "req_a1b2c3d4"
  }
}
```

| Codigo | Estado | Significado |
|--------|--------|-------------|
| `unauthorized` | 401 | Clave de API faltante o invalida |
| `insufficient_scope` | 403 | La clave no tiene el permiso requerido |
| `rate_limit_exceeded` | 429 | Demasiadas solicitudes |
| `invalid_parameter` | 400 | Parametro de consulta invalido |
| `invalid_date_range` | 400 | El rango de fechas excede 366 dias o las fechas son invalidas |
| `internal_error` | 500 | Error del servidor -- incluye el `request_id` al reportar |

## Relacionado

<Cards>
  <Card title="Inicio Rapido de API" href="/docs/es/api/quickstart">
    Haz tu primera llamada a la API en 2 minutos
  </Card>
  <Card title="Referencia de API" href="/docs/es/api/overview">
    Documentacion completa de endpoints
  </Card>
  <Card title="Equipo y Workspace" href="/docs/es/settings/team">
    Administra el acceso y roles del equipo
  </Card>
</Cards>

---

# Desgloses

Siete endpoints de desglose comparten la misma estructura de respuesta. Cada uno devuelve valores de dimension clasificados por visitantes, junto con metricas de trafico y conversion.

**Permiso:** `analytics:read`

## Endpoints

<Cards>
  <Card title="GET /api/v1/channels" href="#solicitud">
    Fuente de trafico — Paid Social, Organic Search, Direct, etc.
  </Card>
  <Card title="GET /api/v1/pages" href="#solicitud">
    Ruta de pagina — principales paginas de destino y contenido.
  </Card>
  <Card title="GET /api/v1/countries" href="#solicitud">
    Codigo de pais — ISO 3166-1 alpha-2.
  </Card>
  <Card title="GET /api/v1/devices" href="#solicitud">
    Tipo de dispositivo — desktop, mobile, tablet.
  </Card>
  <Card title="GET /api/v1/browsers" href="#solicitud">
    Nombre del navegador — Chrome, Safari, Firefox, etc.
  </Card>
  <Card title="GET /api/v1/os" href="#solicitud">
    Sistema operativo — Windows, macOS, iOS, Android, etc.
  </Card>
  <Card title="GET /api/v1/referrers" href="#solicitud">
    Dominio de referencia — el sitio que envio al visitante.
  </Card>
</Cards>

## Parametros

| Parametro           | Tipo   | Requerido | Descripcion                                                              |
| ------------------- | ------ | --------- | ------------------------------------------------------------------------ |
| `date_from`         | string | Si        | Fecha de inicio en formato `YYYY-MM-DD`.                                 |
| `date_to`           | string | Si        | Fecha de fin en formato `YYYY-MM-DD`.                                    |
| `limit`             | number | No        | Maximo de resultados a devolver. Predeterminado `10`, maximo `100`.      |
| `filter[dimension]` | string | No        | Filtrar por cualquier dimension soportada. Ver [Filtros](#filtros) abajo.|

## Solicitud

Los siete endpoints aceptan los mismos parametros y devuelven la misma estructura. Los ejemplos a continuacion usan `/api/v1/channels`.

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/channels?date_from=2026-03-01&date_to=2026-03-25&limit=5"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/channels?date_from=2026-03-01&date_to=2026-03-25&limit=5",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data, meta } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/channels",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={"date_from": "2026-03-01", "date_to": "2026-03-25", "limit": 5},
)
data = res.json()["data"]
```
</Tab>
</Tabs>

## Respuesta

```json title="Respuesta exitosa (200 OK)"
{
  "data": [
    {
      "value": "Paid Social",
      "visitors": 3200,
      "visits": 4100,
      "pageviews": 12500,
      "bounce_rate": 38.5,
      "conversions": 85,
      "conversion_rate": 2.07,
      "revenue": 8500.00
    },
    {
      "value": "Organic Search",
      "visitors": 2100,
      "visits": 2800,
      "pageviews": 7200,
      "bounce_rate": 48.2,
      "conversions": 32,
      "conversion_rate": 1.14,
      "revenue": 3200.00
    }
  ],
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Campos de respuesta

| Campo             | Tipo   | Descripcion                                                              |
| ----------------- | ------ | ------------------------------------------------------------------------ |
| `value`           | string | El valor de la dimension (nombre del canal, ruta de pagina, codigo de pais, etc.). |
| `visitors`        | number | Visitantes unicos.                                                       |
| `visits`          | number | Sesiones totales (visitas).                                              |
| `pageviews`       | number | Vistas de pagina totales.                                                |
| `bounce_rate`     | number | Porcentaje de sesiones de una sola pagina.                               |
| `conversions`     | number | Conversiones totales atribuidas a este valor de dimension.               |
| `conversion_rate` | number | Conversiones divididas por visitantes, como porcentaje.                  |
| `revenue`         | number | Ingresos en efectivo atribuidos en tu moneda de reporte.                 |

## Filtros

Aplica filtros a cualquier endpoint de desglose usando la sintaxis de parametro de consulta `filter[dimension]=operator:value`. Se pueden combinar multiples filtros.

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL — filtrar paises por trafico de Paid Social"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/countries?date_from=2026-03-01&date_to=2026-03-25&filter[channel]=is:Paid+Social"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript — filtrar paises por trafico de Paid Social"
const url = new URL("https://www.atribu.app/api/v1/countries");
url.searchParams.set("date_from", "2026-03-01");
url.searchParams.set("date_to", "2026-03-25");
url.searchParams.set("filter[channel]", "is:Paid Social");

const res = await fetch(url, {
  headers: { Authorization: "Bearer atb_live_YOUR_KEY" },
});
```
</Tab>
<Tab value="Python">
```python title="Python — filtrar paises por trafico de Paid Social"
res = requests.get(
    "https://www.atribu.app/api/v1/countries",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "filter[channel]": "is:Paid Social",
    },
)
```
</Tab>
</Tabs>

### Operadores soportados

| Operador       | Descripcion                                  | Ejemplo                              |
| -------------- | -------------------------------------------- | ------------------------------------ |
| `is`           | Coincidencia exacta.                         | `filter[country]=is:US`              |
| `is_not`       | Excluir coincidencia exacta.                 | `filter[device]=is_not:desktop`      |
| `contains`     | Coincidencia de subcadena (sin distincion de mayusculas). | `filter[page]=contains:/pricing`     |
| `not_contains` | Excluir coincidencia de subcadena (sin distincion de mayusculas). | `filter[referrer]=not_contains:spam` |

### Dimensiones de filtro disponibles

`channel`, `referrer`, `campaign`, `page`, `entry_page`, `country`, `region`,
`city`, `browser`, `os`, `device`, `goal`.

<Callout type="info" title="Los filtros se aplican en todos los endpoints">
  Los filtros funcionan de manera identica en los siete endpoints de desglose asi como en
  el endpoint de [Series de Tiempo](/docs/es/api/timeseries). Por ejemplo,
  `filter[channel]=is:Direct` en `/api/v1/pages` muestra solo las paginas visitadas
  por trafico directo.
</Callout>

## Relacionado

<Cards>
  <Card title="Autenticacion" href="/docs/es/api/authentication">
    Configuracion de claves de API y permisos
  </Card>
  <Card title="Clasificacion de Canales" href="/docs/es/concepts/channels">
    Como se detectan los canales desde UTMs, referidores y click IDs
  </Card>
</Cards>

---

# Campanas

## Campanas Principales

```http title="Endpoint"
GET /api/v1/campaigns
```

Devuelve las campanas con mejor rendimiento clasificadas por valor de resultado atribuido, con datos de ROAS y gasto. Pasa `level` para profundizar hasta conjuntos de anuncios o anuncios individuales.

**Permiso:** `campaigns:read`

### Parametros

| Parametro   | Tipo   | Requerido | Descripcion                                                                 |
| ----------- | ------ | --------- | --------------------------------------------------------------------------- |
| `date_from` | string | Si        | Fecha de inicio en formato `YYYY-MM-DD`.                                    |
| `date_to`   | string | Si        | Fecha de fin en formato `YYYY-MM-DD`.                                       |
| `model`     | string | No        | Modelo de atribucion. Predeterminado `last_touch`. Opciones: `first_touch`, `linear`, `position_based`, `time_decay`, `last_non_direct`. |
| `limit`     | number | No        | Maximo de resultados. Predeterminado `10`, maximo `100`.                    |
| `level`     | string | No        | Nivel de jerarquia: `campaign`, `ad_set` o `ad`. Cuando se establece, devuelve campos de rendimiento detallados. |

### Solicitud

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/campaigns?date_from=2026-03-01&date_to=2026-03-25&model=last_touch&limit=5"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/campaigns?date_from=2026-03-01&date_to=2026-03-25&model=last_touch&limit=5",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data, meta } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/campaigns",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "model": "last_touch",
        "limit": 5,
    },
)
data = res.json()["data"]
```
</Tab>
</Tabs>

### Respuesta

```json title="Respuesta exitosa (200 OK)"
{
  "data": [
    {
      "campaign_id": "uuid",
      "campaign_name": "Spring Sale - Conversions",
      "spend": 1800.00,
      "outcome_count": 45,
      "outcome_value": 6200.00,
      "roas": 3.44
    }
  ],
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Campos de respuesta

| Campo           | Tipo   | Descripcion                                                          |
| --------------- | ------ | -------------------------------------------------------------------- |
| `campaign_id`   | string | UUID interno de la campana.                                          |
| `campaign_name` | string | Nombre legible de la campana desde la plataforma de anuncios.        |
| `spend`         | number | Gasto total en anuncios en tu moneda de reporte.                     |
| `outcome_count` | number | Numero de conversiones atribuidas.                                   |
| `outcome_value` | number | Ingresos totales en efectivo atribuidos.                             |
| `roas`          | number | Retorno sobre el gasto en anuncios (`outcome_value / spend`).        |

### Detalle a nivel de anuncio

Pasa `level=ad` para obtener rendimiento granular por creatividad de anuncio. Esto agrega campos adicionales a cada fila.

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL — desglose a nivel de anuncio"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/campaigns?date_from=2026-03-01&date_to=2026-03-25&level=ad"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript — desglose a nivel de anuncio"
const res = await fetch(
  "https://www.atribu.app/api/v1/campaigns?date_from=2026-03-01&date_to=2026-03-25&level=ad",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python — desglose a nivel de anuncio"
res = requests.get(
    "https://www.atribu.app/api/v1/campaigns",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={"date_from": "2026-03-01", "date_to": "2026-03-25", "level": "ad"},
)
data = res.json()["data"]
```
</Tab>
</Tabs>

#### Campos adicionales cuando se establece `level`

| Campo          | Tipo   | Descripcion                                          |
| -------------- | ------ | ---------------------------------------------------- |
| `entity_level` | string | El nivel de esta fila (`campaign`, `ad_set`, `ad`).  |
| `parent_name`  | string | Nombre de la entidad padre (campana o conjunto de anuncios). |
| `impressions`  | number | Impresiones totales servidas.                        |
| `clicks`       | number | Clics totales.                                       |
| `reach`        | number | Cuentas unicas alcanzadas.                           |
| `ctr`          | number | Tasa de clics (%).                                   |
| `avg_cpm`      | number | Costo promedio por 1,000 impresiones.                |
| `avg_cpc`      | number | Costo promedio por clic.                             |
| `cac`          | number | Costo de adquisicion de cliente (`spend / outcome_count`). |
| `status`       | string | Estado de la entidad (`ACTIVE`, `PAUSED`, etc.).     |
| `objective`    | string | Objetivo de la campana.                              |
| `daily_budget` | number | Presupuesto diario si esta configurado en la plataforma. |

---

## Tendencia de Campanas

```http title="Endpoint"
GET /api/v1/campaigns/trend
```

Devuelve gasto diario, impresiones y clics para hasta 10 campanas, conjuntos de anuncios o anuncios especificos. Usa esto para renderizar sparklines o comparar el rendimiento de entidades a lo largo del tiempo.

**Permiso:** `campaigns:read`

### Parametros

| Parametro    | Tipo   | Requerido | Descripcion                                                              |
| ------------ | ------ | --------- | ------------------------------------------------------------------------ |
| `date_from`  | string | Si        | Fecha de inicio en formato `YYYY-MM-DD`.                                 |
| `date_to`    | string | Si        | Fecha de fin en formato `YYYY-MM-DD`.                                    |
| `entity_ids` | string | Si        | Lista de UUIDs de entidades separados por coma (maximo 10).              |
| `level`      | string | No        | `campaign`, `ad_set` o `ad`. Predeterminado `campaign`.                  |

### Solicitud

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/campaigns/trend?date_from=2026-03-20&date_to=2026-03-25&entity_ids=uuid1,uuid2&level=campaign"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const ids = ["uuid1", "uuid2"].join(",");
const res = await fetch(
  `https://www.atribu.app/api/v1/campaigns/trend?date_from=2026-03-20&date_to=2026-03-25&entity_ids=${ids}&level=campaign`,
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/campaigns/trend",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-20",
        "date_to": "2026-03-25",
        "entity_ids": "uuid1,uuid2",
        "level": "campaign",
    },
)
data = res.json()["data"]
```
</Tab>
</Tabs>

### Respuesta

```json title="Respuesta exitosa (200 OK)"
{
  "data": [
    { "entity_id": "uuid1", "date": "2026-03-20", "spend": 120.00, "impressions": 15000, "clicks": 340 },
    { "entity_id": "uuid1", "date": "2026-03-21", "spend": 135.00, "impressions": 16200, "clicks": 380 },
    { "entity_id": "uuid2", "date": "2026-03-20", "spend": 95.00, "impressions": 11800, "clicks": 275 }
  ],
  "meta": {
    "date_from": "2026-03-20",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Campos de respuesta

| Campo         | Tipo   | Descripcion                                  |
| ------------- | ------ | -------------------------------------------- |
| `entity_id`   | string | El UUID de la campana, conjunto de anuncios o anuncio. |
| `date`        | string | Fecha del calendario (`YYYY-MM-DD`).         |
| `spend`       | number | Gasto en anuncios para esa entidad ese dia.  |
| `impressions` | number | Impresiones servidas.                        |
| `clicks`      | number | Clics registrados.                           |

<Callout type="info" title="Formato de entity ID">
  `entity_ids` espera UUIDs internos de Atribu (del campo `campaign_id` en
  la respuesta de Campanas Principales), no IDs especificos de la plataforma. Puedes pasar hasta
  10 IDs en una sola solicitud.
</Callout>

## Relacionado

<Cards>
  <Card title="Autenticacion" href="/docs/es/api/authentication">
    Configuracion de claves de API y permisos
  </Card>
  <Card title="Rendimiento de Anuncios" href="/docs/es/features/ads-performance">
    La vista del dashboard que usa los mismos datos
  </Card>
  <Card title="Modelos de Atribucion" href="/docs/es/concepts/attribution-models">
    Como el parametro model afecta los rankings de ROAS
  </Card>
</Cards>

---

# Conversiones e Ingresos

Cuatro endpoints para consultar la actividad de conversiones y atribucion de ingresos. Todos requieren el permiso `conversions:read`.

<Callout type="warn" title="Ingresos significa solo efectivo">
  Los ingresos en todos los endpoints a continuacion se refieren exclusivamente a pagos confirmados de
  Stripe y MercadoPago. Los valores de pipeline de GHL (etapas de negocio, proyecciones de
  closed-won) **nunca** se incluyen en los calculos de ingresos o ROAS -- son
  proyecciones, no efectivo confirmado.
</Callout>

---

## Conteo de Conversiones

```http title="Endpoint"
GET /api/v1/conversions
```

Devuelve las conversiones totales agrupadas por tipo de evento para el periodo solicitado.

**Permiso:** `conversions:read`

### Parametros

| Parametro   | Tipo   | Requerido | Descripcion                        |
| ----------- | ------ | --------- | ---------------------------------- |
| `date_from` | string | Si        | Fecha de inicio en formato `YYYY-MM-DD`. |
| `date_to`   | string | Si        | Fecha de fin en formato `YYYY-MM-DD`.   |

### Solicitud

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/conversions?date_from=2026-03-01&date_to=2026-03-25"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/conversions?date_from=2026-03-01&date_to=2026-03-25",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/conversions",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={"date_from": "2026-03-01", "date_to": "2026-03-25"},
)
data = res.json()["data"]
```
</Tab>
</Tabs>

### Respuesta

```json title="Respuesta exitosa (200 OK)"
{
  "data": [
    { "event_type": "lead_created", "count": 85 },
    { "event_type": "appointment_booked", "count": 42 },
    { "event_type": "payment_received", "count": 28 },
    { "event_type": "closed_won", "count": 15 }
  ],
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Campos de respuesta

| Campo        | Tipo   | Descripcion                                      |
| ------------ | ------ | ------------------------------------------------ |
| `event_type` | string | Tipo de conversion (ej., `lead_created`, `payment_received`). |
| `count`      | number | Conversiones totales de este tipo en el periodo. |

---

## Conversiones Diarias

```http title="Endpoint"
GET /api/v1/conversions/timeseries
```

Devuelve conteos de conversiones diarios desglosados por tipo de evento. Usa esto para graficar tendencias de conversiones a lo largo del tiempo.

**Permiso:** `conversions:read`

### Parametros

| Parametro   | Tipo   | Requerido | Descripcion                        |
| ----------- | ------ | --------- | ---------------------------------- |
| `date_from` | string | Si        | Fecha de inicio en formato `YYYY-MM-DD`. |
| `date_to`   | string | Si        | Fecha de fin en formato `YYYY-MM-DD`.   |

### Solicitud

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/conversions/timeseries?date_from=2026-03-20&date_to=2026-03-25"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/conversions/timeseries?date_from=2026-03-20&date_to=2026-03-25",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/conversions/timeseries",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={"date_from": "2026-03-20", "date_to": "2026-03-25"},
)
data = res.json()["data"]
```
</Tab>
</Tabs>

### Respuesta

```json title="Respuesta exitosa (200 OK)"
{
  "data": [
    { "date": "2026-03-20", "event_type": "lead_created", "count": 12 },
    { "date": "2026-03-20", "event_type": "payment_received", "count": 3 },
    { "date": "2026-03-21", "event_type": "lead_created", "count": 15 },
    { "date": "2026-03-21", "event_type": "appointment_booked", "count": 7 }
  ],
  "meta": {
    "date_from": "2026-03-20",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Campos de respuesta

| Campo        | Tipo   | Descripcion                                    |
| ------------ | ------ | ---------------------------------------------- |
| `date`       | string | Fecha del calendario (`YYYY-MM-DD`).           |
| `event_type` | string | Tipo de conversion.                            |
| `count`      | number | Numero de conversiones de este tipo en este dia.|

---

## Ingresos por Dia

```http title="Endpoint"
GET /api/v1/revenue
```

Devuelve ingresos atribuidos diarios y gasto en anuncios. Los ingresos se calculan usando el modelo de atribucion seleccionado e incluyen solo pagos en efectivo.

**Permiso:** `conversions:read`

### Parametros

| Parametro   | Tipo   | Requerido | Descripcion                                                                 |
| ----------- | ------ | --------- | --------------------------------------------------------------------------- |
| `date_from` | string | Si        | Fecha de inicio en formato `YYYY-MM-DD`.                                    |
| `date_to`   | string | Si        | Fecha de fin en formato `YYYY-MM-DD`.                                       |
| `model`     | string | No        | Modelo de atribucion. Predeterminado `last_touch`. Opciones: `first_touch`, `linear`, `position_based`, `time_decay`, `last_non_direct`. |

### Solicitud

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/revenue?date_from=2026-03-20&date_to=2026-03-25&model=last_touch"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/revenue?date_from=2026-03-20&date_to=2026-03-25&model=last_touch",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/revenue",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-20",
        "date_to": "2026-03-25",
        "model": "last_touch",
    },
)
data = res.json()["data"]
```
</Tab>
</Tabs>

### Respuesta

```json title="Respuesta exitosa (200 OK)"
{
  "data": [
    { "date": "2026-03-20", "revenue": 2400.00, "spend": 680.00 },
    { "date": "2026-03-21", "revenue": 1800.00, "spend": 720.00 },
    { "date": "2026-03-22", "revenue": 3200.00, "spend": 650.00 }
  ],
  "meta": {
    "date_from": "2026-03-20",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Campos de respuesta

| Campo     | Tipo   | Descripcion                                      |
| --------- | ------ | ------------------------------------------------ |
| `date`    | string | Fecha del calendario (`YYYY-MM-DD`).             |
| `revenue` | number | Ingresos en efectivo atribuidos en moneda de reporte. |
| `spend`   | number | Gasto total en anuncios en todas las plataformas.|

---

## Efectivo Cobrado

```http title="Endpoint"
GET /api/v1/revenue/cash
```

Devuelve pagos totales en efectivo agrupados por fuente de pago y moneda. A diferencia del endpoint de ingresos atribuidos anterior, este muestra los totales de pagos brutos independientemente del modelo de atribucion.

**Permiso:** `conversions:read`

### Parametros

| Parametro   | Tipo   | Requerido | Descripcion                        |
| ----------- | ------ | --------- | ---------------------------------- |
| `date_from` | string | Si        | Fecha de inicio en formato `YYYY-MM-DD`. |
| `date_to`   | string | Si        | Fecha de fin en formato `YYYY-MM-DD`.   |

### Solicitud

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/revenue/cash?date_from=2026-03-01&date_to=2026-03-25"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/revenue/cash?date_from=2026-03-01&date_to=2026-03-25",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/revenue/cash",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={"date_from": "2026-03-01", "date_to": "2026-03-25"},
)
data = res.json()["data"]
```
</Tab>
</Tabs>

### Respuesta

```json title="Respuesta exitosa (200 OK)"
{
  "data": [
    { "source": "stripe", "currency": "USD", "total": 9800.00, "count": 35 },
    { "source": "mercadopago", "currency": "MXN", "total": 45000.00, "count": 12 }
  ],
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Campos de respuesta

| Campo      | Tipo   | Descripcion                                          |
| ---------- | ------ | ---------------------------------------------------- |
| `source`   | string | Proveedor de pagos (`stripe`, `mercadopago`).        |
| `currency` | string | Codigo de moneda ISO 4217.                           |
| `total`    | number | Suma de pagos en la moneda original.                 |
| `count`    | number | Numero de pagos.                                     |

<Callout type="info" title="Efectivo cobrado vs. ingresos atribuidos">
  **Efectivo cobrado** (`/revenue/cash`) es la suma bruta de pagos, independiente
  de cualquier modelo de atribucion. **Ingresos atribuidos** (`/revenue`) distribuyen
  el valor del pago entre los puntos de contacto de marketing que contribuyeron a la
  conversion, basado en el modelo seleccionado. Usa efectivo cobrado para
  conciliacion financiera e ingresos atribuidos para analisis de rendimiento de marketing.
</Callout>

## Relacionado

<Cards>
  <Card title="Autenticacion" href="/docs/es/api/authentication">
    Configuracion de claves de API y permisos
  </Card>
  <Card title="Tipos de Ingresos" href="/docs/es/concepts/revenue-types">
    Por que solo los pagos en efectivo cuentan para ingresos y ROAS
  </Card>
  <Card title="Objetivos de Conversion" href="/docs/es/settings/goals">
    Configura que eventos cuentan como conversiones
  </Card>
</Cards>

---

# Clientes y Recorrido

<Callout type="error" title="Se requiere permiso de PII">
  Ambos endpoints en esta pagina devuelven informacion de identificacion personal (nombres,
  emails). Tu clave de API debe tener el permiso `customers:read` otorgado explicitamente.
  Las claves sin este permiso recibiran una respuesta `403 Forbidden`.
</Callout>

---

## Lista de Clientes

```http title="Endpoint"
GET /api/v1/customers
```

Devuelve una lista paginada de clientes que alcanzaron un objetivo de conversion especifico dentro del rango de fechas. Cada fila incluye la identidad del cliente, los detalles de la conversion, atribucion de canal y metricas de vida util.

**Permiso:** `customers:read`

### Parametros

| Parametro     | Tipo   | Requerido | Descripcion                                                        |
| ------------- | ------ | --------- | ------------------------------------------------------------------ |
| `date_from`   | string | Si        | Fecha de inicio en formato `YYYY-MM-DD`.                           |
| `date_to`     | string | Si        | Fecha de fin en formato `YYYY-MM-DD`.                              |
| `goal`        | string | Si        | Tipo de conversion: `payment_received`, `lead_created`, `appointment_booked`, `closed_won`, etc. |
| `search`      | string | No        | Buscar por nombre o email del cliente.                             |
| `limit`       | number | No        | Resultados por pagina. Predeterminado `10`, maximo `100`.          |
| `cursor_time` | string | No        | Cursor de paginacion timestamp (de la respuesta anterior).         |
| `cursor_id`   | string | No        | Cursor de paginacion ID (de la respuesta anterior).                |

### Solicitud

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/customers?date_from=2026-03-01&date_to=2026-03-25&goal=payment_received&limit=5"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/customers?date_from=2026-03-01&date_to=2026-03-25&goal=payment_received&limit=5",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data, pagination } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/customers",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "goal": "payment_received",
        "limit": 5,
    },
)
body = res.json()
data = body["data"]
has_next = body["pagination"]["has_next"]
```
</Tab>
</Tabs>

### Respuesta

```json title="Respuesta exitosa (200 OK)"
{
  "data": [
    {
      "conversion_id": "uuid",
      "customer_profile_id": "uuid",
      "name": "Jane Smith",
      "email": "jane@example.com",
      "country": "US",
      "device": "mobile",
      "channel": "Paid Social",
      "source": "ig",
      "revenue": 299.00,
      "revenue_type": "cash",
      "conversion_time": "2026-03-22T14:30:00Z",
      "time_to_complete_seconds": 172800,
      "touch_count": 3,
      "touch_channels": ["Paid Social", "Direct"],
      "conversion_count": 2,
      "total_revenue": 598.00
    }
  ],
  "pagination": {
    "has_next": true,
    "cursor": "2026-03-22T14:30:00Z|uuid"
  },
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Campos de respuesta

| Campo                      | Tipo     | Descripcion                                                     |
| -------------------------- | -------- | --------------------------------------------------------------- |
| `conversion_id`            | string   | ID unico de este evento de conversion.                          |
| `customer_profile_id`      | string   | UUID del perfil del cliente. Usa esto para el endpoint de Recorrido. |
| `name`                     | string   | Nombre completo del cliente (puede ser `null` para visitantes anonimos). |
| `email`                    | string   | Direccion de email del cliente (puede ser `null`).              |
| `country`                  | string   | Codigo de pais ISO de la sesion de conversion.                  |
| `device`                   | string   | Tipo de dispositivo: `desktop`, `mobile`, `tablet`.             |
| `channel`                  | string   | Canal de marketing del ultimo toque antes de la conversion.     |
| `source`                   | string   | Fuente de trafico (ej., `ig`, `fb`, `google`).                  |
| `revenue`                  | number   | Ingresos atribuidos a esta conversion especifica.               |
| `revenue_type`             | string   | `cash`, `pipeline` o `gross`.                                   |
| `conversion_time`          | string   | Timestamp ISO 8601 de la conversion.                            |
| `time_to_complete_seconds` | number   | Segundos entre el primer toque y la conversion.                 |
| `touch_count`              | number   | Numero de puntos de contacto de marketing antes de la conversion. |
| `touch_channels`           | string[] | Canales distintos a traves de todos los puntos de contacto.     |
| `conversion_count`         | number   | Conversiones totales de este cliente (vida util).               |
| `total_revenue`            | number   | Ingresos totales de este cliente (vida util).                   |

### Paginacion

Este endpoint usa paginacion basada en cursor. El campo `pagination.cursor` en la respuesta contiene los valores necesarios para obtener la siguiente pagina.

<Steps>
<Step>
Obtener la primera pagina con el `limit` deseado.
</Step>
<Step>
Verificar `pagination.has_next`. Si es `true`, dividir `pagination.cursor` por `|` para obtener `cursor_time` y `cursor_id`.
</Step>
<Step>
Pasar ambos valores en la siguiente solicitud.
</Step>
</Steps>

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL — obtener pagina 2"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/customers?date_from=2026-03-01&date_to=2026-03-25&goal=payment_received&cursor_time=2026-03-22T14:30:00Z&cursor_id=uuid"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript — paginar todos los resultados"
let allCustomers = [];
let cursorTime = undefined;
let cursorId = undefined;

while (true) {
  const url = new URL("https://www.atribu.app/api/v1/customers");
  url.searchParams.set("date_from", "2026-03-01");
  url.searchParams.set("date_to", "2026-03-25");
  url.searchParams.set("goal", "payment_received");
  url.searchParams.set("limit", "50");
  if (cursorTime) url.searchParams.set("cursor_time", cursorTime);
  if (cursorId) url.searchParams.set("cursor_id", cursorId);

  const res = await fetch(url, {
    headers: { Authorization: "Bearer atb_live_YOUR_KEY" },
  });
  const body = await res.json();
  allCustomers.push(...body.data);

  if (!body.pagination.has_next) break;
  [cursorTime, cursorId] = body.pagination.cursor.split("|");
}
```
</Tab>
<Tab value="Python">
```python title="Python — paginar todos los resultados"
import requests

all_customers = []
cursor_time = None
cursor_id = None

while True:
    params = {
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "goal": "payment_received",
        "limit": 50,
    }
    if cursor_time:
        params["cursor_time"] = cursor_time
        params["cursor_id"] = cursor_id

    body = requests.get(
        "https://www.atribu.app/api/v1/customers",
        headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
        params=params,
    ).json()

    all_customers.extend(body["data"])

    if not body["pagination"]["has_next"]:
        break
    cursor_time, cursor_id = body["pagination"]["cursor"].split("|")
```
</Tab>
</Tabs>

---

## Recorrido del Cliente

```http title="Endpoint"
GET /api/v1/customers/{id}/journey
```

Devuelve la linea de tiempo completa de eventos para un solo cliente -- cada vista de pagina, envio de formulario, reserva, pago y punto de contacto de marketing en orden cronologico.

**Permiso:** `customers:read`

### Parametros

| Parametro | Tipo   | Requerido | Descripcion                                   |
| --------- | ------ | --------- | --------------------------------------------- |
| `id`      | string | Si        | UUID del perfil del cliente (parametro de ruta). |
| `offset`  | number | No        | Saltar los primeros N eventos. Predeterminado `0`. |
| `limit`   | number | No        | Maximo de eventos a devolver. Predeterminado `50`, maximo `100`. |

<Callout type="info" title="Proteccion BOLA">
  Este endpoint aplica autorizacion a nivel de objeto. Si el ID del cliente no
  pertenece a tu perfil, devuelve datos vacios (no un error). Esto previene
  la divulgacion de informacion sobre si IDs de clientes especificos existen en el sistema.
</Callout>

### Solicitud

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/customers/customer-uuid/journey?limit=20"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const customerId = "customer-uuid";
const res = await fetch(
  `https://www.atribu.app/api/v1/customers/${customerId}/journey?limit=20`,
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();
console.log(`${data.total_count} events in journey`);
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

customer_id = "customer-uuid"
res = requests.get(
    f"https://www.atribu.app/api/v1/customers/{customer_id}/journey",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={"limit": 20},
)
events = res.json()["data"]["events"]
```
</Tab>
</Tabs>

### Respuesta

```json title="Respuesta exitosa (200 OK)"
{
  "data": {
    "events": [
      {
        "event_type": "page_view",
        "event_name": "page_view",
        "event_time": "2026-03-18T10:15:00Z",
        "url": "https://example.com/pricing",
        "path": "/pricing",
        "channel": "Paid Social",
        "source": "ig",
        "medium": "paid",
        "campaign": "Spring Sale",
        "value_amount": null,
        "currency": null,
        "device": "mobile",
        "browser": "Safari",
        "os": "iOS",
        "country": "US",
        "city": "New York",
        "is_synthetic": false
      },
      {
        "event_type": "payment_received",
        "event_name": "payment_received",
        "event_time": "2026-03-20T16:45:00Z",
        "url": null,
        "path": null,
        "channel": null,
        "source": "stripe",
        "medium": null,
        "campaign": null,
        "value_amount": 299.00,
        "currency": "USD",
        "device": null,
        "browser": null,
        "os": null,
        "country": null,
        "city": null,
        "is_synthetic": false
      }
    ],
    "total_count": 12
  },
  "meta": {
    "profile_id": "uuid"
  }
}
```

### Campos de respuesta

| Campo          | Tipo    | Descripcion                                                   |
| -------------- | ------- | ------------------------------------------------------------- |
| `event_type`   | string  | Categoria del evento (`page_view`, `lead_created`, `payment_received`, etc.). |
| `event_name`   | string  | Nombre especifico del evento.                                 |
| `event_time`   | string  | Timestamp ISO 8601.                                           |
| `url`          | string  | URL completa de la pagina (solo eventos web).                 |
| `path`         | string  | Componente de ruta de la URL.                                 |
| `channel`      | string  | Canal de marketing clasificado.                               |
| `source`       | string  | Fuente de trafico o proveedor de pagos.                       |
| `medium`       | string  | Medio de marketing (`paid`, `organic`, `referral`, etc.).     |
| `campaign`     | string  | Nombre de la campana (resuelto del ID de plataforma).         |
| `value_amount` | number  | Valor monetario (para eventos de pago y negocio).             |
| `currency`     | string  | Codigo de moneda ISO 4217.                                    |
| `device`       | string  | Tipo de dispositivo.                                          |
| `browser`      | string  | Nombre del navegador.                                         |
| `os`           | string  | Sistema operativo.                                            |
| `country`      | string  | Codigo de pais ISO.                                           |
| `city`         | string  | Nombre de la ciudad.                                          |
| `is_synthetic` | boolean | `true` para conversiones fuera del sitio que no tuvieron visita al sitio web. |
| `total_count`  | number  | Total de eventos en el recorrido (usar con `offset`/`limit` para paginacion). |

<Callout type="info" title="Puntos de contacto sinteticos">
  Los eventos con `is_synthetic: true` representan conversiones fuera del sitio (ej., formularios
  de leads de Meta enviados via Instagram que fluyen a GoHighLevel sin una
  visita al sitio web). Atribu crea puntos de contacto sinteticos para estos para que puedan
  atribuirse a la campana de anuncios de origen. Consulta [Toques Sinteticos](/docs/es/concepts/synthetic-touches) para mas detalles.
</Callout>

## Relacionado

<Cards>
  <Card title="Autenticacion" href="/docs/es/api/authentication">
    Configuracion de claves de API, permisos (customers:read requiere otorgamiento explicito)
  </Card>
  <Card title="Recorridos de Clientes" href="/docs/es/features/customer-journey">
    La vista del dashboard para explorar rutas de clientes
  </Card>
  <Card title="Resolucion de Identidad" href="/docs/es/concepts/identity-resolution">
    Como se construyen los perfiles de clientes a partir de multiples identificadores
  </Card>
</Cards>

---

```http title="Endpoint"
GET /api/v1/overview
```

Devuelve KPIs para el periodo actual y un periodo anterior calculado automaticamente de igual duracion para comparacion.

**Permiso:** `analytics:read`

## Parametros

| Parametro | Tipo | Requerido | Descripcion |
|-----------|------|-----------|-------------|
| `date_from` | string | Si | Fecha de inicio (YYYY-MM-DD) |
| `date_to` | string | Si | Fecha de fin (YYYY-MM-DD) |
| `model` | string | No | Modelo de atribucion (predeterminado: `last_touch`) |
| `filter[dimension]` | string | No | Filtrar por dimension, ej., `filter[channel]=is:Paid Social` |

<Callout type="info" title="Modelos de atribucion">
`last_touch` · `first_touch` · `split_50_50` · `linear` · `position_based` · `time_decay` · `last_non_direct` · `custom_weighted`
</Callout>

## Solicitud

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/overview?date_from=2026-03-01&date_to=2026-03-25&model=last_touch"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/overview?date_from=2026-03-01&date_to=2026-03-25&model=last_touch",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/overview",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "model": "last_touch",
    },
)
data = resp.json()
```
</Tab>
</Tabs>

## Respuesta

```json title="Respuesta exitosa (200 OK)"
{
  "data": {
    "current": {
      "spend": 4250.00,
      "revenue": 12800.00,
      "roas": 3.01,
      "outcomes": 145,
      "attributed_outcomes": 132,
      "coverage_percent": 91.03,
      "visitors": 8420,
      "pageviews": 24100,
      "bounce_rate": 42.5,
      "avg_engaged_seconds": 185,
      "cash_revenue": 12800.00,
      "cash_payments": 48
    },
    "previous": {
      "spend": 3900.00,
      "revenue": 10200.00,
      "roas": 2.62,
      "outcomes": 128,
      "attributed_outcomes": 115,
      "coverage_percent": 89.84,
      "visitors": 7200,
      "pageviews": 20500,
      "bounce_rate": 45.2,
      "avg_engaged_seconds": 165,
      "cash_revenue": 10200.00,
      "cash_payments": 38
    }
  },
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

## Campos de respuesta

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `spend` | number | Gasto total en anuncios en la moneda de reporte |
| `revenue` | number | Ingresos en efectivo atribuidos (numerador del ROAS) |
| `roas` | number | Retorno sobre el gasto en anuncios (revenue / spend) |
| `outcomes` | number | Conversiones totales |
| `attributed_outcomes` | number | Conversiones con al menos un punto de contacto atribuido |
| `coverage_percent` | number | Cobertura de atribucion (atribuidos / total x 100) |
| `visitors` | number | Visitantes unicos (anonymous_id distintos) |
| `pageviews` | number | Vistas de pagina totales |
| `bounce_rate` | number | Porcentaje de sesiones de una sola pagina |
| `avg_engaged_seconds` | number | Tiempo promedio de engagement por sesion |
| `cash_revenue` | number | Pagos totales en efectivo (Stripe + MercadoPago) |
| `cash_payments` | number | Cantidad de eventos de pago en efectivo |

<Callout type="info" title="Periodo anterior">
El periodo anterior se calcula automaticamente para tener la misma duracion que el periodo actual, terminando el dia antes de `date_from`. Por ejemplo, si consultas del 1 al 25 de marzo (25 dias), el periodo anterior sera del 4 al 28 de febrero.
</Callout>

## Relacionado

<Cards>
  <Card title="Autenticacion" href="/docs/es/api/authentication">
    Configuracion de claves de API, permisos y limites de tasa
  </Card>
  <Card title="Modelos de Atribucion" href="/docs/es/concepts/attribution-models">
    Como el parametro model afecta los ingresos y el ROAS
  </Card>
  <Card title="Tipos de Ingresos" href="/docs/es/concepts/revenue-types">
    Por que los campos de ingresos solo incluyen pagos en efectivo
  </Card>
</Cards>

---

<Steps>
<Step>

## Obtener tu clave de API

1. Inicia sesion en [Atribu](https://www.atribu.app)
2. Ve a **Configuracion > Desarrollador**
3. Haz clic en **Crear Clave de API**
4. Selecciona tus permisos y copia la clave

<Callout type="warn" title="Guarda tu clave">
La clave comienza con `atb_live_` y se muestra solo una vez. Guardala en un lugar seguro.
</Callout>

</Step>
<Step>

## Hacer tu primera solicitud

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY_HERE" \
  "https://www.atribu.app/api/v1/overview?date_from=2026-03-01&date_to=2026-03-25"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/overview?date_from=2026-03-01&date_to=2026-03-25",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY_HERE",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/overview",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY_HERE"},
    params={"date_from": "2026-03-01", "date_to": "2026-03-25"},
)
data = resp.json()
```
</Tab>
</Tabs>

</Step>
<Step>

## Respuesta de ejemplo

```json title="Respuesta exitosa (200 OK)"
{
  "data": {
    "current": {
      "spend": 4250.00,
      "revenue": 12800.00,
      "roas": 3.01,
      "outcomes": 145,
      "attributed_outcomes": 132,
      "coverage_percent": 91.03,
      "visitors": 8420,
      "pageviews": 24100,
      "bounce_rate": 42.5,
      "avg_engaged_seconds": 185,
      "cash_revenue": 12800.00,
      "cash_payments": 48
    },
    "previous": { "..." : "..." }
  },
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "your-profile-id"
  }
}
```

</Step>
</Steps>

## Siguientes pasos

<Cards>
  <Card title="Autenticacion" href="/docs/es/api/authentication">
    Permisos de claves de API, limites de tasa y mejores practicas de seguridad
  </Card>
  <Card title="Referencia de API" href="/docs/es/api/overview">
    Documentacion completa de endpoints
  </Card>
  <Card title="Modelos de Atribucion" href="/docs/es/concepts/attribution-models">
    Como se asigna el credito
  </Card>
</Cards>

---

```http title="Endpoint"
GET /api/v1/timeseries
```

Devuelve puntos de datos diarios para visitantes, vistas de pagina, rebotes, ingresos en efectivo y gasto en anuncios a lo largo del rango de fechas solicitado. Usa este endpoint para alimentar graficos de lineas, barras y comparaciones de tendencias en tus dashboards.

**Permiso:** `analytics:read`

## Parametros

| Parametro            | Tipo   | Requerido | Descripcion                                                       |
| -------------------- | ------ | --------- | ----------------------------------------------------------------- |
| `date_from`          | string | Si        | Fecha de inicio en formato `YYYY-MM-DD`.                          |
| `date_to`            | string | Si        | Fecha de fin en formato `YYYY-MM-DD`.                             |
| `filter[dimension]`  | string | No        | Filtrar resultados por una dimension. Ver [Desgloses](/docs/es/api/breakdowns#filtros) para la sintaxis. |

## Solicitud

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="cURL"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/timeseries?date_from=2026-03-20&date_to=2026-03-25"
```
</Tab>
<Tab value="JavaScript">
```js title="JavaScript"
const res = await fetch(
  "https://www.atribu.app/api/v1/timeseries?date_from=2026-03-20&date_to=2026-03-25",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data, meta } = await res.json();
```
</Tab>
<Tab value="Python">
```python title="Python"
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/timeseries",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={"date_from": "2026-03-20", "date_to": "2026-03-25"},
)
data = res.json()["data"]
```
</Tab>
</Tabs>

## Respuesta

```json title="Respuesta exitosa (200 OK)"
{
  "data": [
    {
      "date": "2026-03-20",
      "visitors": 1250,
      "pageviews": 3600,
      "bounces": 520,
      "cash_revenue": 2400.00,
      "spend": 680.00
    },
    {
      "date": "2026-03-21",
      "visitors": 1180,
      "pageviews": 3400,
      "bounces": 490,
      "cash_revenue": 1800.00,
      "spend": 720.00
    },
    {
      "date": "2026-03-22",
      "visitors": 980,
      "pageviews": 2800,
      "bounces": 410,
      "cash_revenue": 3200.00,
      "spend": 650.00
    }
  ],
  "meta": {
    "date_from": "2026-03-20",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Campos de respuesta

| Campo          | Tipo   | Descripcion                                          |
| -------------- | ------ | ---------------------------------------------------- |
| `date`         | string | Fecha del calendario (`YYYY-MM-DD`).                 |
| `visitors`     | number | Visitantes unicos (`anonymous_id` distintos) ese dia.|
| `pageviews`    | number | Vistas de pagina totales registradas.                |
| `bounces`      | number | Sesiones con una sola vista de pagina.               |
| `cash_revenue` | number | Ingresos en efectivo atribuidos (Stripe / MercadoPago) en tu moneda de reporte. |
| `spend`        | number | Gasto total en anuncios en todas las plataformas conectadas. |

<Callout type="info" title="Los ingresos son solo efectivo">
  `cash_revenue` incluye solo pagos confirmados de Stripe y MercadoPago.
  Los valores de pipeline de GHL (etapas de negocio, proyecciones de closed-won) se excluyen porque
  no son efectivo confirmado. Consulta [Conversiones e Ingresos](/docs/es/api/conversions) para
  el desglose completo.
</Callout>

## Relacionado

<Cards>
  <Card title="Autenticacion" href="/docs/es/api/authentication">
    Configuracion de claves de API y permisos
  </Card>
  <Card title="Dashboard" href="/docs/es/features/dashboard">
    El grafico principal usa los mismos datos que este endpoint devuelve
  </Card>
</Cards>

---

# Visitantes y Tiempo Real

Cuatro endpoints para datos de visitantes, presencia en tiempo real, palabras clave de busqueda y metricas de calidad de atribucion.

---

## Lista de Visitantes

```http title="Endpoint"
GET /api/v1/visitors
```

Lista paginada de todos los visitantes (identificados y anonimos). Devuelve campos de PII como nombre y email.

**Permiso:** `visitors:read` (debe ser otorgado explicitamente)

<Callout type="error" title="Se requiere acceso a PII">
Este endpoint devuelve informacion de identificacion personal (nombre, email). Tu clave de API debe tener el permiso `visitors:read` otorgado explicitamente. Las claves con solo `analytics:read` recibiran una respuesta `403 Forbidden`.
</Callout>

### Parametros

| Parametro | Tipo | Requerido | Descripcion |
|-----------|------|-----------|-------------|
| `date_from` | string | Si | Fecha de inicio (YYYY-MM-DD) |
| `date_to` | string | Si | Fecha de fin (YYYY-MM-DD) |
| `search` | string | No | Buscar por nombre o email |
| `limit` | number | No | Resultados por pagina (predeterminado 10, maximo 100) |
| `cursor_time` | string | No | Cursor de paginacion (timestamp ISO) |
| `cursor_id` | string | No | Cursor de paginacion ID |

### Solicitud

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/visitors?date_from=2026-03-01&date_to=2026-03-25&limit=5"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/visitors?date_from=2026-03-01&date_to=2026-03-25&limit=5",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/visitors",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "limit": 5,
    },
)
data = resp.json()
```
</Tab>
</Tabs>

### Respuesta

```json title="Respuesta exitosa (200 OK)"
{
  "data": [
    {
      "visitor_id": "uuid",
      "customer_profile_id": "uuid",
      "name": "John Doe",
      "email": "john@example.com",
      "country": "US",
      "device": "desktop",
      "browser": "Chrome",
      "os": "macOS",
      "channel": "Organic Search",
      "source": "google",
      "total_revenue": 450.00,
      "last_seen_at": "2026-03-25T08:30:00Z",
      "session_count": 5,
      "total_pageviews": 18,
      "touch_channels": ["Organic Search", "Direct", "Paid Social"]
    }
  ],
  "pagination": {
    "has_next": true,
    "cursor": "2026-03-25T08:30:00Z|uuid"
  },
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Campos de respuesta

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `visitor_id` | string | Identificador unico de visitante (anonymous_id) |
| `customer_profile_id` | string \| null | Perfil de cliente vinculado (null para visitantes anonimos) |
| `name` | string | Nombre para mostrar o nombre anonimo deterministico (ej. "Teal Falcon") |
| `email` | string \| null | Direccion de email si fue identificado |
| `country` | string | Codigo de pais de dos letras |
| `device` | string | Tipo de dispositivo: `desktop`, `mobile`, `tablet` |
| `browser` | string | Nombre del navegador |
| `os` | string | Sistema operativo |
| `channel` | string | Canal de trafico clasificado (ver [Canales](/docs/es/concepts/channels)) |
| `source` | string | Fuente de trafico |
| `total_revenue` | number | Ingresos totales en efectivo atribuidos |
| `last_seen_at` | string | Timestamp ISO de la ultima actividad |
| `session_count` | number | Sesiones totales en el rango de fechas |
| `total_pageviews` | number | Vistas de pagina totales en el rango de fechas |
| `touch_channels` | string[] | Todos los canales con los que este visitante ha interactuado |

---

## Tiempo Real

```http title="Endpoint"
GET /api/v1/realtime
```

Devuelve el numero de visitantes actualmente en tu sitio (activos en los ultimos 5 minutos).

**Permiso:** `realtime:read`

<Callout type="info" title="Sin parametros de fecha">
Este endpoint devuelve una captura en vivo. No se necesitan parametros `date_from` ni `date_to`.
</Callout>

### Solicitud

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/realtime"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/realtime",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/realtime",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
)
data = resp.json()
```
</Tab>
</Tabs>

### Respuesta

```json title="Respuesta exitosa (200 OK)"
{
  "data": {
    "visitors_online": 24
  },
  "meta": {
    "profile_id": "uuid"
  }
}
```

---

## Palabras Clave

```http title="Endpoint"
GET /api/v1/keywords
```

Devuelve datos de rendimiento de palabras clave de Google Search Console. Requiere una integracion activa con GSC.

**Permiso:** `analytics:read`

### Parametros

| Parametro | Tipo | Requerido | Descripcion |
|-----------|------|-----------|-------------|
| `date_from` | string | Si | Fecha de inicio (YYYY-MM-DD) |
| `date_to` | string | Si | Fecha de fin (YYYY-MM-DD) |
| `limit` | number | No | Maximo de resultados (predeterminado 50, maximo 100) |

### Solicitud

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/keywords?date_from=2026-03-01&date_to=2026-03-25&limit=10"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/keywords?date_from=2026-03-01&date_to=2026-03-25&limit=10",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/keywords",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "limit": 10,
    },
)
data = resp.json()
```
</Tab>
</Tabs>

### Respuesta

```json title="Respuesta exitosa (200 OK)"
{
  "data": [
    {
      "keyword": "marketing attribution tool",
      "impressions": 2400,
      "clicks": 180,
      "ctr": 7.5,
      "avg_position": 4.2
    },
    {
      "keyword": "roas calculator",
      "impressions": 1800,
      "clicks": 95,
      "ctr": 5.3,
      "avg_position": 6.8
    }
  ],
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Campos de respuesta

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `keyword` | string | Termino de busqueda |
| `impressions` | number | Veces que tu sitio aparecio en los resultados de busqueda |
| `clicks` | number | Veces que los usuarios hicieron clic hacia tu sitio |
| `ctr` | number | Tasa de clics (clicks / impressions x 100) |
| `avg_position` | number | Posicion promedio en los rankings de resultados de busqueda |

---

## Calidad

```http title="Endpoint"
GET /api/v1/quality
```

Devuelve metricas de calidad de datos de atribucion -- que tan bien tu tracking captura datos de fuentes de marketing.

**Permiso:** `analytics:read`

### Parametros

| Parametro | Tipo | Requerido | Descripcion |
|-----------|------|-----------|-------------|
| `date_from` | string | Si | Fecha de inicio (YYYY-MM-DD) |
| `date_to` | string | Si | Fecha de fin (YYYY-MM-DD) |

### Solicitud

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/quality?date_from=2026-03-01&date_to=2026-03-25"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/quality?date_from=2026-03-01&date_to=2026-03-25",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/quality",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
    },
)
data = resp.json()
```
</Tab>
</Tabs>

### Respuesta

```json title="Respuesta exitosa (200 OK)"
{
  "data": {
    "total_events": 1450,
    "with_full_utms": 890,
    "with_fbclid_only": 320,
    "with_no_tracking": 240,
    "coverage_percent": 83.45
  },
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}
```

### Campos de respuesta

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `total_events` | number | Total de eventos rastreados en el periodo |
| `with_full_utms` | number | Eventos con parametros UTM completos |
| `with_fbclid_only` | number | Eventos con solo un click ID (fbclid/gclid) pero sin UTMs |
| `with_no_tracking` | number | Eventos sin ningun dato de fuente de marketing |
| `coverage_percent` | number | Porcentaje de eventos que tienen al menos algunos datos de atribucion |

<Callout type="info" title="Mejorar la cobertura">
Un `coverage_percent` bajo generalmente significa que faltan parametros UTM en las URLs de tus anuncios. Asegurate de que todas las campanas incluyan parametros `utm_source`, `utm_medium` y `utm_campaign`. Los click IDs (`fbclid`, `gclid`) son agregados automaticamente por las plataformas de anuncios.
</Callout>

## Relacionado

<Cards>
  <Card title="Autenticacion" href="/docs/es/api/authentication">
    Configuracion de claves de API, permisos (visitors:read requiere otorgamiento explicito)
  </Card>
  <Card title="Calidad de Datos" href="/docs/es/features/data-quality">
    Monitorea y mejora tu cobertura de tracking
  </Card>
  <Card title="Recorridos de Clientes" href="/docs/es/features/customer-journey">
    El equivalente en el dashboard del endpoint de recorrido
  </Card>
</Cards>

---

# Modelos de Atribucion

Atribu soporta 8 modelos de atribucion que determinan como se distribuye el credito de conversion entre los puntos de contacto de marketing. Cada modelo responde una pregunta diferente sobre el recorrido de tu cliente.

<VideoEmbed src="/docs/videos/how-attribution-works.mp4" title="Como funciona la atribucion — los 8 modelos explicados" />

## Como funciona la atribucion

```mermaid
flowchart LR
    A["Ad Click"] --> B["Page Views"]
    B --> C["Form Fill"]
    C --> D["identify()"]
    D --> E["Customer Profile"]
    E --> F["Payment"]
    F --> G["Attribution Engine"]
    G --> H["Credit to Campaign"]
```

## Modelos de un vistazo

<Cards>
<Card title="Ultimo Toque" href="#ultimo-toque">
Todo el credito al punto de contacto final antes de la conversion
</Card>
<Card title="Primer Toque" href="#primer-toque">
Todo el credito al primer punto de contacto que inicio el recorrido
</Card>
<Card title="Division 50/50" href="#division-5050">
Division equitativa entre el primer y ultimo punto de contacto
</Card>
<Card title="Lineal" href="#lineal">
Credito equitativo en cada punto de contacto del recorrido
</Card>
<Card title="Basado en Posicion (Forma de U)" href="#basado-en-posicion-forma-de-u">
40% primero, 40% ultimo, 20% dividido entre los toques intermedios
</Card>
<Card title="Decaimiento Temporal" href="#decaimiento-temporal">
Mas credito a los toques mas cercanos a la conversion
</Card>
<Card title="Ultimo No Directo" href="#ultimo-no-directo">
Ignora las visitas directas para encontrar el verdadero impulsor
</Card>
<Card title="Ponderacion Personalizada" href="#ponderacion-personalizada">
Pesos configurables por posicion de punto de contacto
</Card>
</Cards>

---

## Ultimo Toque

Todo el credito va al **ultimo punto de contacto** antes de la conversion.

**Ideal para:** Entender que cierra negocios e impulsa la decision final.

**Valor en API:** `last_touch`

```
Ad Click -> Email -> Blog Visit -> [Purchase]
  0%          0%        100%
```

---

## Primer Toque

Todo el credito va al **primer punto de contacto** que inicio el recorrido del cliente.

**Ideal para:** Entender que genera el conocimiento inicial y atrae personas.

**Valor en API:** `first_touch`

```
Ad Click -> Email -> Blog Visit -> [Purchase]
  100%        0%        0%
```

---

## Division 50/50

Credito dividido equitativamente entre el **primer** y **ultimo** punto de contacto. Los toques intermedios no reciben credito.

**Ideal para:** Equilibrar el conocimiento y el cierre sin complicar el analisis.

**Valor en API:** `split_50_50`

```
Ad Click -> Email -> Blog Visit -> [Purchase]
  50%          0%        50%
```

---

## Lineal

Credito distribuido **equitativamente** entre todos los puntos de contacto en la ruta de conversion.

**Ideal para:** Representacion justa de cada paso en el recorrido del cliente.

**Valor en API:** `linear`

```
Ad Click -> Email -> Blog Visit -> [Purchase]
  33.3%      33.3%     33.3%
```

---

## Basado en Posicion (Forma de U)

**40%** al primer toque, **40%** al ultimo toque, y el **20%** restante dividido equitativamente entre los toques intermedios.

**Ideal para:** Enfatizar el descubrimiento y el cierre mientras se reconoce la fase de nurturing.

**Valor en API:** `position_based`

```
Ad Click -> Email -> Blog Visit -> [Purchase]
  40%         20%       40%
```

---

## Decaimiento Temporal

Mas credito a los puntos de contacto **mas cercanos a la conversion**. Usa decaimiento exponencial desde el primer hasta el ultimo toque.

**Ideal para:** Ciclos de venta cortos donde las interacciones recientes importan mas.

**Valor en API:** `time_decay`

```
Ad Click -> Email -> Blog Visit -> [Purchase]
  10%         30%       60%
```

---

## Ultimo No Directo

Todo el credito al ultimo punto de contacto que **no es trafico "Directo"**. Si un cliente hace clic en un anuncio y luego escribe tu URL directamente, el anuncio recibe el credito.

**Ideal para:** Eliminar el ruido de las visitas directas para identificar el verdadero impulsor de marketing.

**Valor en API:** `last_non_direct`

```
Ad Click -> Email -> Direct Visit -> [Purchase]
  0%          100%       0% (ignorado)
```

---

## Ponderacion Personalizada

Pesos personalizados por posicion de punto de contacto. Se configura en los ajustes de atribucion de tu perfil.

**Ideal para:** Negocios con un proceso de venta bien entendido que quieren control total.

**Valor en API:** `custom_weighted`

---

## Uso de modelos en la API

Pasa el parametro `model` a cualquier endpoint que soporte atribucion:

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/overview?date_from=2026-03-01&date_to=2026-03-25&model=position_based"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/overview?date_from=2026-03-01&date_to=2026-03-25&model=position_based",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/overview",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "model": "position_based",
    },
)
data = resp.json()
```
</Tab>
</Tabs>

<Callout type="info" title="Valores validos de modelo">
`last_touch` (predeterminado) `first_touch` `split_50_50` `linear` `position_based` `time_decay` `last_non_direct` `custom_weighted`
</Callout>

---

## Ventanas de atribucion

Las ventanas de atribucion definen que tan atras en el tiempo un punto de contacto puede recibir credito por una conversion. Son configurables por perfil.

| Ventana | Predeterminado | Descripcion |
|---------|----------------|-------------|
| **Ventana de clic** | 30 dias | Cuanto tiempo despues de un clic puede atribuirse una conversion |
| **Ventana de vista** | 24 horas | Cuanto tiempo despues de una impresion de anuncio (sin clic) |
| **Ventana de primer toque** | 90 dias | Retrospectiva maxima para atribucion de primer toque |

<Callout type="info" title="Comportamiento de ventanas">
Si un cliente hace clic en un anuncio el 1 de marzo y convierte el 28 de marzo, el clic esta dentro de la ventana predeterminada de 30 dias y recibe credito. Si convierte el 5 de abril (35 dias despues), el clic queda fuera de la ventana y se excluye de la atribucion.
</Callout>

---

## Ingresos y ROAS

<Callout type="warn" title="El ROAS solo usa ingresos en efectivo">
Solo `revenue_type = 'cash'` (pagos confirmados de Stripe y MercadoPago) se usa para calculos de ROAS. Los valores de pipeline de GHL (closed_won, appointment_booked) se rastrean por separado como **valor de pipeline** -- son proyecciones, no ingresos confirmados. Consulta [Tipos de Ingresos](/docs/es/concepts/revenue-types) para mas detalles.
</Callout>

Los campos `revenue` y `roas` en las respuestas de la API siempre reflejan ingresos en efectivo atribuidos. Los valores de pipeline aparecen en los conteos de conversiones pero nunca se incluyen en el calculo de ROAS.

## Relacionado

<Cards>
  <Card title="Ventanas de Atribucion" href="/docs/es/settings/attribution-windows">
    Configura ventanas de retrospectiva de clic, vista y primer toque
  </Card>
  <Card title="Rendimiento de Anuncios" href="/docs/es/features/ads-performance">
    Ve como los diferentes modelos cambian los rankings de campanas
  </Card>
  <Card title="Recorridos de Clientes" href="/docs/es/features/customer-journey">
    Visualiza las rutas reales de puntos de contacto que los modelos evaluan
  </Card>
</Cards>

---

# Clasificacion de Canales

Atribu clasifica automaticamente cada visita en un canal de marketing basado en parametros UTM, URL de referencia y click IDs. Esta clasificacion impulsa el desglose de Fuentes de Trafico en tu dashboard y la dimension de canal en las respuestas de la API.

---

## Canales predeterminados

<Cards>
<Card title="Social de Pago">
Meta, TikTok y otros anuncios sociales detectados por click IDs o parametros UTM de pago
</Card>
<Card title="Social Organico">
Trafico no pagado desde plataformas sociales como Facebook, Instagram, X, LinkedIn
</Card>
<Card title="Busqueda de Pago">
Google Ads, Microsoft Ads y otros anuncios de busqueda detectados por click IDs o UTMs de pago
</Card>
<Card title="Busqueda Organica">
Trafico no pagado de motores de busqueda como Google, Bing, Yahoo, DuckDuckGo
</Card>
<Card title="Email">
Trafico de campanas de email identificado por utm_medium
</Card>
<Card title="Referencia">
Trafico entrante desde otros sitios web (no busqueda ni social)
</Card>
<Card title="Directo">
Sin referencia, sin UTMs, sin click IDs -- URL escrita o marcador
</Card>
<Card title="Display">
Trafico de banners y anuncios de display identificado por utm_medium
</Card>
<Card title="Afiliado">
Trafico de socios afiliados identificado por utm_medium
</Card>
</Cards>

---

## Reglas de clasificacion

| Canal | Logica de deteccion |
|-------|---------------------|
| **Social de Pago** | `fbclid` o `ttclid` presente, O `utm_medium` contiene `paid`, `cpc` o `ppc` con una fuente social |
| **Social Organico** | Referencia desde una plataforma social (facebook.com, instagram.com, twitter.com, linkedin.com, etc.) sin indicadores de pago |
| **Busqueda de Pago** | `gclid` o `msclkid` presente, O `utm_medium` es `cpc`/`ppc` con una fuente de motor de busqueda |
| **Busqueda Organica** | Referencia desde un motor de busqueda (google, bing, yahoo, duckduckgo, baidu, etc.) |
| **Email** | `utm_medium` es `email` |
| **Referencia** | Tiene una referencia que no es un motor de busqueda ni plataforma social conocida |
| **Directo** | Sin referencia, sin parametros UTM, sin click IDs |
| **Display** | `utm_medium` es `display`, `banner` o `cpm` |
| **Afiliado** | `utm_medium` es `affiliate` |

<Callout type="info" title="Prioridad de clasificacion">
Los click IDs tienen precedencia sobre los parametros UTM. Si una visita tiene `fbclid` en la URL, se clasifica como **Social de Pago** sin importar lo que diga `utm_medium`. Esto previene clasificaciones erroneas cuando faltan etiquetas UTM o son incorrectas.
</Callout>

---

## Deteccion de click IDs

Los click IDs son agregados automaticamente a las URLs por las plataformas de anuncios. Atribu los detecta para identificar trafico de pago incluso cuando faltan los parametros UTM.

| Click ID | Plataforma | Canal |
|----------|------------|-------|
| `fbclid` | Meta (Facebook / Instagram) | Social de Pago |
| `gclid` | Google Ads | Busqueda de Pago |
| `msclkid` | Microsoft Advertising (Bing) | Busqueda de Pago |
| `ttclid` | TikTok Ads | Social de Pago |

<Callout type="warn" title="Siempre usa UTMs junto con click IDs">
Los click IDs identifican la plataforma pero no la campana especifica. Para atribucion completa hasta el nivel de anuncio, siempre incluye `utm_campaign`, `utm_source`, `utm_medium` y `utm_content` en las URLs de tus anuncios.
</Callout>

---

## Uso de canales en la API

### Desglose por canal

Obtener la distribucion de visitantes e ingresos en todos los canales:

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/breakdowns?date_from=2026-03-01&date_to=2026-03-25&dimensions=channel"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/breakdowns?date_from=2026-03-01&date_to=2026-03-25&dimensions=channel",
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/breakdowns",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "dimensions": "channel",
    },
)
data = resp.json()
```
</Tab>
</Tabs>

```json title="Response"
{
  "data": {
    "channel": [
      { "value": "Paid Social", "visitors": 3200, "sessions": 4100, "pageviews": 9800, "bounce_rate": 38.2 },
      { "value": "Organic Search", "visitors": 2800, "sessions": 3400, "pageviews": 8200, "bounce_rate": 45.1 },
      { "value": "Direct", "visitors": 1500, "sessions": 1800, "pageviews": 4200, "bounce_rate": 52.3 }
    ]
  }
}
```

### Filtrar por canal

Filtra cualquier endpoint a un canal especifico usando el parametro `filter[channel]`:

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">
```bash title="Request"
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/overview?date_from=2026-03-01&date_to=2026-03-25&filter[channel]=is:Paid+Social"
```
</Tab>
<Tab value="JavaScript">
```ts title="Request"
const response = await fetch(
  "https://www.atribu.app/api/v1/overview?" + new URLSearchParams({
    date_from: "2026-03-01",
    date_to: "2026-03-25",
    "filter[channel]": "is:Paid Social",
  }),
  {
    headers: {
      Authorization: "Bearer atb_live_YOUR_KEY",
    },
  }
);
const data = await response.json();
```
</Tab>
<Tab value="Python">
```python title="Request"
import requests

resp = requests.get(
    "https://www.atribu.app/api/v1/overview",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "filter[channel]": "is:Paid Social",
    },
)
data = resp.json()
```
</Tab>
</Tabs>

<Callout type="info" title="Sintaxis de filtros">
Los filtros de canal usan el prefijo `is:` seguido del nombre exacto del canal. Los nombres de canales distinguen mayusculas y minusculas. Usa `+` o `%20` para espacios en las URLs.
</Callout>

## Relacionado

<Cards>
  <Card title="Parametros UTM" href="/docs/es/tracking/utm-parameters">
    Etiqueta tus enlaces para una clasificacion de canales precisa
  </Card>
  <Card title="Fuentes de Trafico" href="/docs/es/features/traffic-sources">
    Ve los datos de canal en los tableros de desglose de tu dashboard
  </Card>
</Cards>

---

La resolucion de identidad es como Atribu conecta los puntos entre un visitante anonimo del sitio web y un cliente conocido que te paga.

<VideoEmbed src="/docs/videos/identity-resolution.mp4" title="Resolucion de identidad — de visitante anonimo a cliente conocido" />

```mermaid
flowchart TD
    A["Anonymous Visitor\n(anonymous_id)"] -->|fills form| B["identify(email)"]
    B --> C["Customer Profile"]
    D["Stripe Payment\n(same email)"] --> C
    E["GHL Contact\n(same email)"] --> C
    F["GHL Opportunity\n(same contact)"] --> C
    C --> G["All events linked\nto one person"]
```

## El problema

El recorrido de tu cliente esta fragmentado:

1. Hace clic en un anuncio de Facebook desde su telefono
2. Visita tu sitio, revisa los precios y se va
3. Dos dias despues, busca el nombre de tu marca en Google desde su laptop
4. Llena tu formulario de contacto
5. Una semana despues, paga a traves de Stripe

Sin resolucion de identidad, estos parecen 5 eventos separados de 3 personas diferentes. Con ella, Atribu sabe que es una sola persona -- y atribuye el pago al anuncio original de Facebook.

## Como funciona

<Steps>
<Step>

### El visitante anonimo llega

Cuando alguien visita tu sitio, Atribu le asigna un **ID de Visitante** (llamado `anonymous_id`). Este ID se almacena en su navegador y permanece igual en todas sus visitas desde ese dispositivo.

</Step>
<Step>

### El visitante se identifica

Cuando el visitante llena un formulario, agenda una cita o inicia sesion, la funcion `identify()` de Atribu se ejecuta con su email (o telefono). Esto crea un **perfil de cliente** que vincula su ID de Visitante con su identidad real.

</Step>
<Step>

### Los eventos del servidor coinciden

Cuando un pago llega de Stripe con el mismo email, o se crea un negocio en GoHighLevel para el mismo contacto, Atribu lo vincula al perfil de cliente existente. El recorrido completo -- desde el clic en el anuncio hasta el pago -- ahora esta conectado.

</Step>
</Steps>

## El grafo de identidad

Atribu construye un **grafo de identidad** para cada cliente:

| Tipo de Identificador | Ejemplo | Fuente |
|----------------------|---------|--------|
| Email | jane@example.com | Formulario, pago de Stripe |
| Telefono | +1-555-0123 | Formulario, contacto de GHL |
| ID de Visitante | `abc123-def456` | Cookie del navegador |
| ID de Cliente Stripe | `cus_abc123` | Pago de Stripe |
| ID de Contacto GHL | `contact_xyz` | Sincronizacion de GoHighLevel |

Todos los identificadores apuntan al mismo perfil de cliente. Cualquier evento futuro con cualquiera de estos identificadores se vincula a la misma persona.

## Prioridad de identificadores

Al resolver quien es una persona, Atribu usa esta prioridad:

1. **Email** (coincidencia mas fuerte)
2. **Numero de telefono**
3. **IDs externos** (ID de cliente Stripe, ID de contacto GHL)

Los nombres se usan solo para visualizacion -- nunca para coincidencia (hay demasiados "Juan Perez").

<Callout type="info" title="Seguro de llamar repetidamente">
El sistema de resolucion de identidad usa logica "COALESCE" -- llamar a `identify()` multiples veces con nuevos datos rellena los campos faltantes sin sobrescribir los existentes. Si un cliente primero da su email y luego su telefono, ambos se almacenan.
</Callout>

## Por que `identify()` es critico

<Callout type="error" title="Sin identify(), la atribucion se rompe">
Si un visitante nunca se identifica (sin formulario, sin reserva, sin inicio de sesion), su clic en el anuncio y las vistas de pagina se rastrean -- pero nunca pueden conectarse a un pago de Stripe o evento de CRM. El pago aparece como "sin atribucion".
</Callout>

Asegurate de que cada punto de conversion en tu sitio active la identificacion:

- **Formularios**: Atribu los captura automaticamente (sin codigo necesario)
- **Widgets de reserva**: GHL, Calendly, Cal.com capturados automaticamente
- **Flujos personalizados**: Llama `window.atribuTracker.identify({ email: "..." })` manualmente

Consulta [Identificar Usuarios](/docs/es/tracking/identify-users) para detalles de implementacion.

## Relacionado

<Cards>
  <Card title="Captura Automatica" href="/docs/es/tracking/auto-capture">
    Como los formularios y widgets de reserva activan la identificacion automaticamente
  </Card>
  <Card title="Calidad de Datos" href="/docs/es/features/data-quality">
    Monitorea la cobertura de identidad y repara datos faltantes
  </Card>
  <Card title="Recorridos de Clientes" href="/docs/es/features/customer-journey">
    Ve la ruta completa desde visitante anonimo hasta cliente identificado
  </Card>
</Cards>

---

# Tipos de Ingresos

Atribu rastrea tres tipos distintos de valor monetario. Cada uno sirve un proposito diferente, y mezclarlos produce metricas sin sentido.

```mermaid
flowchart TD
    A["Stripe / MercadoPago\nPayment"] -->|"revenue_type = cash"| B["Cash Revenue"]
    C["GHL Pipeline\nDeal Value"] -->|"revenue_type = pipeline"| D["Pipeline Value"]
    E["Shopify Order"] -->|"revenue_type = gross"| F["Gross Value"]
    B --> G["ROAS = Cash / Spend"]
    D -.->|"NOT included"| G
    F -.->|"NOT included"| G
```

---

## Los tres tipos

<Cards>
<Card title="Efectivo">
Pagos confirmados de Stripe y MercadoPago. El unico valor usado para el ROAS.
</Card>
<Card title="Pipeline">
Valores de negocios de oportunidades de GoHighLevel. Proyecciones, no ingresos confirmados.
</Card>
<Card title="Bruto">
Valores de pedidos de plataformas de comercio electronico. Puede incluir reembolsos o cancelaciones.
</Card>
</Cards>

---

## Efectivo (para ROAS)

`revenue_type = 'cash'`

Pagos reales confirmados por Stripe o MercadoPago. Este es el **unico** valor usado para calculos de ROAS.

| Propiedad | Detalle |
|-----------|---------|
| **Fuente** | Eventos `payment_received` de webhooks de Stripe/MercadoPago |
| **Confiabilidad** | 100% -- son transacciones bancarias reales |
| **Campo en API** | `revenue` en `/api/v1/overview` y `/api/v1/campaigns` |
| **Usado en ROAS** | Si |

---

## Pipeline

`revenue_type = 'pipeline'`

Valores de negocios de oportunidades de GoHighLevel (lead_created, appointment_booked, closed_won). Son **proyecciones**, no efectivo confirmado.

| Propiedad | Detalle |
|-----------|---------|
| **Fuente** | Sincronizacion de oportunidades de GHL |
| **Confiabilidad** | Variable -- los negocios pueden perderse, renegociarse o nunca cerrarse |
| **Campo en API** | Visible en conteos de conversiones, excluido de `revenue` y `roas` |
| **Usado en ROAS** | No |

---

## Bruto

`revenue_type = 'gross'`

Valores de pedidos de plataformas de comercio electronico (por ejemplo, eventos order_placed). Puede incluir pedidos que luego se reembolsan, cancelan o nunca se cumplen.

| Propiedad | Detalle |
|-----------|---------|
| **Fuente** | Plataformas de comercio electronico (Shopify, etc.) |
| **Confiabilidad** | Media -- representa intencion de pago, no cobro confirmado |
| **Campo en API** | Rastreado por separado de los ingresos en efectivo |
| **Usado en ROAS** | No |

---

## Por que esto importa

<Callout type="error" title="Nunca mezcles valores de pipeline en el ROAS">
Si los valores de pipeline se incluyeran en el ROAS, una sola proyeccion de negocio de $50,000 podria hacer que una campana de $500 en anuncios muestre **100x ROAS** -- completamente enganoso. Ese negocio podria nunca cerrarse, renegociarse a $5,000, o perderse por completo. Solo los pagos en efectivo confirmados producen numeros de ROAS confiables.
</Callout>

Considera este ejemplo:

| Metrica | Solo con efectivo | Con pipeline incluido |
|---------|-------------------|----------------------|
| Gasto en anuncios | $2,000 | $2,000 |
| Ingresos | $6,000 (3 pagos) | $56,000 (3 pagos + 1 negocio) |
| ROAS | **3.0x** | **28.0x** |
| Preciso? | Si | No -- el negocio de $50K no se ha cerrado |

---

## Como aparece en la API

### Endpoint de resumen

Los campos `revenue` y `roas` en `/api/v1/overview` siempre usan **solo efectivo**:

```json title="Respuesta de resumen (revenue = solo efectivo)"
{
  "data": {
    "current": {
      "spend": 4250.00,
      "revenue": 12800.00,
      "roas": 3.01,
      "cash_revenue": 12800.00,
      "cash_payments": 48
    }
  }
}
```

### Endpoint de campanas

Los campos `outcome_value` y `roas` a nivel de campana tambien reflejan solo ingresos en efectivo:

```json title="Respuesta de campana"
{
  "data": [
    {
      "campaign_name": "Spring Sale - Conversions",
      "spend": 1800.00,
      "outcome_count": 45,
      "outcome_value": 6200.00,
      "roas": 3.44
    }
  ]
}
```

<Callout type="info" title="Los conteos de conversiones incluyen todos los tipos">
El campo `outcome_count` incluye conversiones de **todos** los tipos de ingreso (efectivo, pipeline y bruto). Esto te da visibilidad del volumen total de conversiones. Solo los campos monetarios (`revenue`, `outcome_value`, `roas`) estan restringidos a efectivo.
</Callout>

### Endpoint de conversiones

El endpoint `/api/v1/conversions` devuelve eventos de conversion individuales con su `revenue_type`, para que puedas filtrar y analizar cada tipo de forma independiente:

```json title="Conversion con revenue_type"
{
  "conversion_type": "payment_received",
  "revenue_type": "cash",
  "value": 450.00,
  "customer_name": "John Doe"
}
```

```json title="Conversion de pipeline"
{
  "conversion_type": "closed_won",
  "revenue_type": "pipeline",
  "value": 15000.00,
  "customer_name": "Acme Corp"
}
```

## Relacionado

<Cards>
  <Card title="Integracion con Stripe" href="/docs/es/integrations/stripe">
    Como se rastrean los pagos en efectivo desde Stripe
  </Card>
  <Card title="Integracion con GoHighLevel" href="/docs/es/integrations/gohighlevel">
    Como se sincronizan los valores de pipeline desde negocios de GHL
  </Card>
  <Card title="Objetivos de Conversion" href="/docs/es/settings/goals">
    Configura que eventos cuentan como conversiones y su tipo de ingreso
  </Card>
</Cards>

---

No todos los leads visitan tu sitio web. Algunas conversiones ocurren completamente fuera del sitio -- y Atribu aun las atribuye.

```mermaid
flowchart TD
    A["Meta Lead Form\n(no website visit)"] --> B["GHL Contact Created"]
    B --> C["Outcome Event\n(has campaign data)"]
    C --> D{"Web touches\nexist?"}
    D -->|No| E["Create Synthetic Touch"]
    D -->|Yes| F["Use existing touches"]
    E --> G["Attribution Engine"]
    F --> G
    G --> H["Credit assigned\nto campaign"]
```

## El problema

Cuando alguien llena un **formulario de leads de Meta** (un formulario mostrado directamente en Facebook o Instagram, sin visitar tu sitio web), el lead va directo a GoHighLevel. No hay visita al sitio, ni vista de pagina, ni evento del tracker. Pero el lead SI vino de una campana de anuncios especifica.

Sin toques sinteticos, estos leads aparecerian como "sin atribucion" -- aunque Meta sabe exactamente que campana los genero.

<Callout type="warn" title="Esto afecta ~60% de los leads de GHL">
Para negocios que usan anuncios de leads de Meta con GoHighLevel, la mayoria de los leads llegan a traves de formularios nativos. Sin toques sinteticos, mas de la mitad de tus leads no pueden atribuirse a sus campanas.
</Callout>

## Como funcionan los toques sinteticos

Cuando Atribu detecta una conversion que tiene datos de campana (de la atribucion de GHL) pero no tiene visita al sitio, crea un **toque sintetico** -- un punto de contacto artificial que representa la interaccion fuera del sitio.

<Steps>
<Step>

### El lead llena el formulario de Meta

Un cliente potencial ve tu anuncio de Instagram y llena el formulario de leads directamente en Instagram. Nunca visita tu sitio web.

</Step>
<Step>

### GHL recibe el lead

GoHighLevel crea un contacto y una oportunidad. La oportunidad incluye datos de atribucion de Meta: ID de campana, ID de conjunto de anuncios, ID de anuncio.

</Step>
<Step>

### Atribu sincroniza los datos

Atribu sincroniza la oportunidad de GHL y crea un evento de conversion. Ve los datos de campana pero no encuentra toques web coincidentes.

</Step>
<Step>

### Se crea el toque sintetico

Atribu crea un toque sintetico con los datos de campana de GHL. Esto permite al motor de atribucion asignar credito a la campana correcta.

</Step>
</Steps>

## Reglas

Los toques sinteticos siguen reglas estrictas para evitar doble conteo:

- **Solo se crean cuando no existen toques web** para ese cliente antes de la conversion
- **Idempotentes**: ejecutar el proceso de nuevo no crea duplicados
- **Sin datos de sesion**: los toques sinteticos no tienen sesion, vistas de pagina ni informacion de dispositivo (el cliente nunca visito el sitio)
- **Metricas del dashboard no afectadas**: los conteos de visitantes, sesiones y vistas de pagina provienen de datos web reales, no de toques sinteticos

## Donde aparecen

En la linea de tiempo del recorrido del cliente, los toques sinteticos se marcan con `is_synthetic: true`. Muestran la campana que genero el lead pero no tienen datos de pagina/URL.

<Callout type="info" title="Visibilidad en el dashboard">
Los toques sinteticos afectan los datos de atribucion (ROAS, ingresos por campana) pero NO las metricas de trafico (visitantes, sesiones, vistas de pagina). Tus conteos de visitantes permanecen precisos -- solo cuentan visitas reales al sitio web.
</Callout>

## Impacto en la atribucion

Sin toques sinteticos, un negocio que ejecuta anuncios de leads de Meta podria ver:
- 100 leads en GHL
- Solo 40 atribuidos a campanas (los que visitaron el sitio web)
- 60 leads "sin atribucion"

Con toques sinteticos:
- 100 leads en GHL
- 95+ atribuidos a campanas
- ROAS preciso mostrando el verdadero retorno de tu gasto en anuncios de Meta

## Relacionado

<Cards>
  <Card title="Integracion con GoHighLevel" href="/docs/es/integrations/gohighlevel">
    Como los leads de GHL llevan datos de campana para toques sinteticos
  </Card>
  <Card title="Resolucion de Identidad" href="/docs/es/concepts/identity-resolution">
    Como se crean perfiles de clientes para leads fuera del sitio
  </Card>
  <Card title="Calidad de Datos" href="/docs/es/features/data-quality">
    Monitorea la cobertura de atribucion para ver el impacto de los toques sinteticos
  </Card>
</Cards>

---

Ads Explorer te permite monitorear la Biblioteca de Anuncios de Meta en busca de anuncios de cualquier competidor. Ve que creativos estan publicando, cuanto tiempo lleva activo cada anuncio, y usa IA para desglosar que hace efectivos sus anuncios.

<VideoEmbed src="/docs/videos/ads-explorer.mp4" title="Ads Explorer — inteligencia competitiva" />

## Que hace

Atribu escanea la Biblioteca de Anuncios de Meta en busca de anuncios de los competidores que especifiques. Descubre nuevos anuncios, rastrea cuanto tiempo se publican, y almacena todo para que puedas explorar y analizar el creativo en cualquier momento.

## Agregar competidores

<Steps>
<Step>

### Ve a Ads Explorer

Navega a **Ads Explorer** en la barra lateral, luego haz clic en la pestana **Fuentes**.

</Step>
<Step>

### Haz clic en Agregar Fuente

Haz clic en el boton **Agregar Fuente** para abrir el formulario de configuracion de fuente.

</Step>
<Step>

### Ingresa la pagina del competidor

Proporciona el nombre de la pagina de Facebook del competidor o su URL. Atribu usa el nombre del anunciante para buscar en la Biblioteca de Anuncios de Meta.

</Step>
<Step>

### Selecciona paises objetivo

Elige en cuales paises buscar anuncios. Los anuncios frecuentemente estan geo-segmentados, asi que seleccionar los paises correctos asegura que veas los creativos relevantes.

</Step>
<Step>

### Inicia el escaneo

Guarda la fuente y Atribu comenzara a escanear sus anuncios. Los nuevos anuncios se descubren automaticamente de forma continua.

</Step>
</Steps>

## Explorar anuncios descubiertos

La vista principal de Ads Explorer muestra una cuadricula de tarjetas para cada anuncio descubierto. Cada tarjeta muestra:

- **Miniatura** — una vista previa del creativo del anuncio (imagen o cuadro de video)
- **Nombre del anunciante** — quien esta publicando el anuncio
- **Plataformas** — Facebook, Instagram o ambas
- **Estado** — si el anuncio esta actualmente activo o ha dejado de publicarse
- **Dias activo** — cuanto tiempo lleva el anuncio en vivo
- **Tipo de medio** — imagen o video

Puedes filtrar por fuente, estado (activo/inactivo), tipo de medio, y ordenar por mas reciente, mas antiguo o puntuacion de IA.

<Callout type="info" title="Los anuncios de larga duracion suelen ser ganadores">
Si un competidor ha estado publicando un anuncio por mas de 90 dias, probablemente es rentable. Los anunciantes no siguen gastando dinero en anuncios que no funcionan. Estudia que hace efectivos a los anuncios de larga duracion — el gancho, la oferta, el formato creativo.
</Callout>

## Vista de detalle del anuncio

Haz clic en cualquier tarjeta de anuncio para abrir la hoja de detalle. Esto muestra:

- **Vista previa completa del creativo** — la imagen o video completo
- **Texto principal** — el copy del anuncio
- **URL de destino** — a donde envia el anuncio a las personas
- **Llamada a la accion** — el texto del boton CTA (Comprar Ahora, Mas Informacion, etc.)
- **Plataformas** — en cuales plataformas aparece el anuncio (Facebook, Instagram)
- **Paises** — todos los paises donde se publica el anuncio
- **Fechas de primera y ultima aparicion** — cuando se detecto el anuncio por primera vez y la vez mas reciente

## Analisis creativo con IA

Atribu puede analizar cualquier anuncio descubierto usando IA para extraer insights estructurados:

- **Gancho** — que captura la atencion en los primeros 3 segundos (para video) o a primera vista (para imagenes)
- **CTA** — la llamada a la accion y que tan convincente es
- **Oferta** — que se esta promocionando y como esta enmarcado
- **Guion** — para anuncios de video, el texto hablado completo extraido y transcrito
- **Puntuacion** — una puntuacion general de calidad con explicacion
- **Sugerencias de mejora** — recomendaciones especificas de como el creativo podria ser mas fuerte

Para ejecutar el analisis, haz clic en el boton **Enrich** en cualquier anuncio. La IA procesa el creativo y los resultados aparecen en la vista de detalle.

## Enviar a Ads Lab

Encontraste un anuncio de competidor del que quieres aprender? Haz clic en **Enviar a Ads Lab** para usarlo como inspiracion para generar tus propias variaciones de anuncios. El analisis creativo del anuncio se pasa al pipeline de IA para que pueda estudiar que funciona y crear algo original para tu marca.

<Callout type="info" title="Inteligencia competitiva, no copia">
El objetivo no es copiar los anuncios de los competidores. Es entender que patrones, ganchos y ofertas resuenan en tu mercado, y luego crear tu propio creativo original informado por esos insights.
</Callout>

## Relacionado

<Cards>
  <Card title="Ads Lab" href="/docs/es/features/ads-lab">
    Genera tu propio creativo publicitario usando insights del analisis de competidores
  </Card>
  <Card title="Rendimiento de Anuncios" href="/docs/es/features/ads-performance">
    Compara el rendimiento de tus propias campanas junto con insights de competidores
  </Card>
</Cards>

---

Ads Lab es un espacio de trabajo creativo impulsado por IA que genera copy de anuncios y guiones de video usando datos del rendimiento real de tus anuncios. En lugar de plantillas genericas, cada sugerencia esta respaldada por evidencia de tus campanas con mejor rendimiento.

<VideoEmbed src="/docs/videos/ads-lab-pipeline.mp4" title="Ads Lab — pipeline de creacion de anuncios con IA" />

## El flujo de trabajo guiado

Ads Lab te guia a traves de un proceso estructurado para generar creativos publicitarios de alta calidad.

<Steps>
<Step>

### Selecciona los mejores anuncios

Atribu recomienda tus mejores anuncios ordenados por ROAS. Selecciona de cuales la IA debe aprender. Cuantos mas anuncios ganadores selecciones, mas patrones puede identificar la IA.

</Step>
<Step>

### Elige tu estrategia

Decide la direccion creativa:

- **Remix de mejores anuncios** — nuevas variaciones basadas en lo que ya funciona
- **Inspirado en competidores** — aprende de anuncios de competidores descubiertos en Ads Explorer
- **Nuevo angulo** — explora enfoques frescos informados por tus datos

</Step>
<Step>

### Configura la generacion

Establece tus preferencias:

- **Plataformas** — en cuales plataformas se publicaran los anuncios (Facebook, Instagram, etc.)
- **Palabras clave obligatorias** — terminos o frases que deben aparecer en el copy
- **Palabras clave excluidas** — terminos a evitar
- **Contexto adicional** — cualquier guia de marca, requisitos de tono o instrucciones especificas

</Step>
<Step>

### El pipeline de IA genera tu creativo

Siete agentes de IA especializados trabajan en secuencia para producir tus anuncios:

| Agente | Que hace |
|--------|----------|
| **Research** | Analiza patrones de tus anuncios ganadores — que ganchos, CTAs y formatos funcionan mejor |
| **Psychology** | Identifica tacticas de persuasion y sesgos cognitivos que resuenan con tu audiencia |
| **Copywriting** | Crea 3-4 variaciones de copy con titulares, texto del cuerpo y CTAs |
| **Video Script** | Escribe guiones de video estructurados con ganchos y dialogos escena por escena |
| **Compliance** | Verifica limites de caracteres y reglas especificas de cada plataforma, reescribe si es necesario |
| **Critic** | Puntua todo en 5 dimensiones de calidad y solicita reescrituras si la puntuacion esta por debajo del umbral |

Si el Critic encuentra problemas de calidad, el agente de Copywriting automaticamente revisa y vuelve a enviar.

</Step>
<Step>

### Revisa y aprueba

Cada borrador generado aparece para tu revision. Para cada borrador puedes:

- **Aprobar** — marcarlo como listo para usar
- **Editar** — modificar el texto directamente
- **Descartar** — eliminarlo del lote
- **Regenerar** — pedir a la IA que cree una nueva version

</Step>
</Steps>

## Chat libre

Mas alla del flujo de trabajo guiado, puedes hablar con la IA en lenguaje natural para:

- Refinar un borrador especifico ("haz el titular mas corto y contundente")
- Solicitar cambios en el tono o angulo
- Pedir variaciones completamente nuevas
- Descartar o aprobar multiples borradores a la vez ("descarta el resto de los borradores")

El chat entiende el contexto de tu sesion — sabe en que paso estas, que borradores existen y que dice tu brief.

## Gestion de sesiones

Cada conversacion de Ads Lab se guarda como una sesion. Puedes:

- Iniciar nuevas sesiones para diferentes campanas o briefs
- Cambiar entre sesiones existentes desde el panel de historial
- Cada sesion preserva todos los mensajes, borradores y decisiones

<Callout type="info" title="Generacion basada en evidencia">
Cada sugerencia de Ads Lab esta respaldada por datos del rendimiento real de tus anuncios — no consejos genericos de marketing. El agente de Research identifica patrones especificos de tus anuncios ganadores (estilos de gancho, formatos de CTA, estructuras de oferta) y el agente de Copywriting usa esos patrones como bloques de construccion.
</Callout>

## Seguimiento de costos

Cada ejecucion del pipeline de IA rastrea el uso de tokens y el costo. El pipeline tiene un limite de costo configurable para prevenir gastos descontrolados. Puedes ver el costo total de cada generacion en la sesion.

## Relacionado

<Cards>
  <Card title="Ads Explorer" href="/docs/es/features/ads-explorer">
    Descubre anuncios de competidores para usar como inspiracion en Ads Lab
  </Card>
  <Card title="Rendimiento de Anuncios" href="/docs/es/features/ads-performance">
    Ve como rinden tus campanas para seleccionar los mejores anuncios para la IA
  </Card>
</Cards>

---

La pagina de Anuncios muestra cada campana, conjunto de anuncios y anuncio de tus plataformas de anuncios conectadas, ordenados por rendimiento. Aqui es donde descubres cuales anuncios estan generando dinero y cuales estan desperdiciando presupuesto.

<VideoEmbed src="/docs/videos/ads-performance.mp4" title="Rendimiento de Anuncios — clasifica campanas por lo que importa" />

## Jerarquia de anuncios

Las plataformas de anuncios organizan los anuncios en una jerarquia de tres niveles. Usa el selector de nivel en la parte superior de la pagina para cambiar entre ellos:

- **Campana** — el nivel superior. Agrupa conjuntos de anuncios bajo un objetivo y presupuesto compartido.
- **Conjunto de Anuncios** — el nivel medio. Controla la segmentacion, ubicacion y programacion.
- **Anuncio** — el creativo individual. La imagen, video o texto que las personas realmente ven.

Cada nivel muestra metricas agregadas. El gasto de una campana es la suma de todos sus conjuntos de anuncios; los clics de un conjunto de anuncios son la suma de todos sus anuncios.

## Metricas clave explicadas

| Metrica | Que significa | Formula |
|---------|--------------|---------|
| **Gasto** | Dinero total gastado en esta campana, conjunto de anuncios o anuncio. | Suma del gasto diario |
| **Impresiones** | Cuantas veces se mostro tu anuncio a las personas. | Suma de impresiones diarias |
| **Clics** | Cuantas personas hicieron clic en tu anuncio para visitar tu sitio. | Suma de clics diarios |
| **CTR** | Tasa de Clics — que porcentaje de personas que vieron tu anuncio hicieron clic. Mayor es mejor. | Clics / Impresiones x 100 |
| **CPM** | Costo Por Mil — cuanto cuesta mostrar tu anuncio 1,000 veces. | Gasto / Impresiones x 1,000 |
| **CPC** | Costo Por Clic — cuanto te cuesta cada clic. | Gasto / Clics |
| **Alcance** | Numero de personas unicas que vieron tu anuncio (una persona contada una vez). | Suma del alcance diario |
| **Frecuencia** | Numero promedio de veces que cada persona vio tu anuncio. | Impresiones / Alcance |
| **ROAS** | Retorno sobre Gasto en Anuncios — cuantos ingresos genera cada dolar de gasto en anuncios. | Ingresos / Gasto |
| **CAC** | Costo de Adquisicion de Cliente — cuanto cuesta adquirir una conversion. | Gasto / Conversiones |

<Callout type="info" title="Calculo en tiempo real">
Todas las metricas derivadas (CTR, CPM, CPC, ROAS, CAC) se calculan en tiempo real a partir de datos crudos. Nunca se almacenan como valores obsoletos, por lo que siempre reflejan los numeros mas recientes incluyendo cualquier normalizacion de moneda.
</Callout>

## Metricas de video

Para anuncios de video, hay metricas adicionales de engagement disponibles:

| Metrica | Que significa |
|---------|--------------|
| **Hook Rate** | Porcentaje de impresiones que comenzaron a ver el video. Un hook rate alto significa que el primer cuadro captura la atencion. |
| **Retention Rate** | Porcentaje de espectadores que vieron al menos el 75% del video. Muestra que tan atractivo es el contenido. |
| **Completion Rate** | Porcentaje de espectadores que vieron el video completo. |
| **Thumb Stop Ratio** | Porcentaje de personas unicas alcanzadas que dejaron de hacer scroll para ver. |
| **Tiempo Promedio de Visualizacion** | Tiempo promedio que los espectadores pasaron viendo el video. |

## Grafico de tendencia diaria

Haz clic en cualquier campana, conjunto de anuncios o anuncio para ver su rendimiento a lo largo del tiempo. El grafico de tendencia diaria muestra gasto, impresiones y clics graficados dia a dia, facilitando identificar patrones, detectar cuando cambio el rendimiento o ver el impacto de cambios creativos.

## Cambio de modelo de atribucion

El mismo anuncio puede mostrar numeros de ROAS muy diferentes bajo diferentes modelos de atribucion. Usa el selector de modelo para comparar:

- **Ultimo Toque** asigna todo el credito al ultimo anuncio con el que el cliente interactuo antes de convertir
- **Primer Toque** asigna todo el credito al primer anuncio que presento al cliente
- **Lineal** divide el credito equitativamente entre todos los puntos de contacto
- **Basado en Posicion** asigna 40% al primer toque, 40% al ultimo toque, y divide el 20% restante entre los toques intermedios

Cambiar de modelo te ayuda a entender si un anuncio es mejor introduciendo nuevos clientes (ROAS fuerte de primer toque) o cerrando ventas (ROAS fuerte de ultimo toque).

<Callout type="info" title="El ROAS solo cuenta pagos reales">
Los calculos de ROAS solo incluyen ingresos de pagos confirmados (Stripe/MercadoPago). Los valores de pipeline de negocios del CRM nunca se incluyen, porque representan proyecciones y no dinero en mano.
</Callout>

## Pagina de detalle de entidad

Haz clic en el nombre de cualquier campana, conjunto de anuncios o anuncio para abrir su pagina de detalle. La pagina de detalle muestra:

- Todas las metricas en formato de tarjeta con los numeros exactos
- El grafico de tendencia diaria para esa entidad especifica
- Para anuncios individuales: la miniatura del creativo, texto del cuerpo y titulo
- Para anuncios de video: el reproductor de video completo mas metricas de engagement de video
- Datos de atribucion mostrando a cuales conversiones contribuyo esta entidad

## Relacionado

<Cards>
  <Card title="Modelos de Atribucion" href="/docs/es/concepts/attribution-models">
    Como diferentes modelos cambian el ROAS y los rankings de campanas
  </Card>
  <Card title="Tipos de Ingresos" href="/docs/es/concepts/revenue-types">
    Por que solo los pagos en efectivo aparecen en los calculos de ROAS
  </Card>
  <Card title="Ventanas de Atribucion" href="/docs/es/settings/attribution-windows">
    Configura que tan atras los puntos de contacto pueden recibir credito
  </Card>
</Cards>

---

La pagina de Calendario muestra tus citas, reservas y eventos de conversion en una linea de tiempo visual. Cuando GoHighLevel esta conectado, fusiona datos en vivo del CRM con los datos de atribucion de Atribu para que puedas ver cuales campanas generaron cada cita.

## Que muestra

El calendario muestra eventos de multiples fuentes, fusionados y deduplicados:

| Fuente | Que proporciona |
|--------|----------------|
| **Eventos en vivo de GHL** | Estado de cita en tiempo real, representante asignado, nombre del calendario |
| **Eventos rastreados** | Datos de atribucion — que campana, canal y anuncio genero esta cita |
| **Eventos fusionados** | Ambas fuentes combinadas, mostrando estado en vivo junto con atribucion |

Cuando una cita de GHL coincide con un evento de conversion rastreado en la base de datos de Atribu, el calendario los fusiona en una sola entrada con los mejores datos de ambas fuentes.

## Dos vistas

<Tabs items={["Vista de cuadricula", "Vista de agenda"]}>
<Tab value="Vista de cuadricula">

La vista de cuadricula muestra un diseno de calendario semanal. Los eventos aparecen como bloques en su dia y hora programados. Los eventos superpuestos se organizan lado a lado.

El rango de tiempo se ajusta automaticamente segun tus horarios reales de citas — si tu cita mas temprana es a las 9 AM y la mas tardia a las 6 PM, la cuadricula muestra ese rango en lugar de un dia completo de 24 horas.

</Tab>
<Tab value="Vista de agenda">

La vista de agenda muestra los eventos como una lista cronologica simple. Cada evento muestra la hora, nombre del cliente, estado e informacion de atribucion en un formato compacto de fila. Esta vista es mejor para escanear rapidamente un gran numero de citas.

</Tab>
</Tabs>

## Filtros

Filtra cuales eventos aparecen en el calendario:

- **Usuario asignado** — filtra por el miembro del equipo asignado a la cita
- **Calendario** — filtra por calendario especifico de GHL (si tienes multiples)
- **Tipo de fuente** — muestra solo eventos en vivo de GHL, solo eventos rastreados, o eventos fusionados

## Atribucion en citas

Cada cita en el calendario muestra que canal de marketing y campana trajo al cliente. Esto significa que puedes ver de un vistazo:

- Cuantas citas vinieron de Social de Pago vs. Organico
- Que campana especifica de Meta o Google Ads genero cada reserva
- Si el cliente tuvo interacciones previas antes de agendar

Los datos de atribucion provienen de la resolucion de identidad de Atribu — el email o telefono del cliente de la cita se cruza con sus visitas anteriores al sitio web y clics en anuncios.

<Callout type="info" title="GHL no conectado?">
Si GoHighLevel no esta conectado, el calendario muestra solo eventos rastreados de la base de datos de Atribu. Aun veras citas capturadas via auto-captura del tracker (deteccion de widget de reservas) y cualquier otro evento de conversion rastreado, pero sin el estado en vivo de GHL o informacion del representante asignado.
</Callout>

## Fuentes de datos explicadas

Los eventos pueden tener diferentes etiquetas de fuente:

- **merged** — tanto datos en vivo de GHL como datos de rastreo de Atribu estan disponibles para este evento
- **db_tracked** — el evento existe en la base de datos de Atribu (de auto-captura del tracker o sincronizacion del CRM) pero no se encontro en la consulta en vivo actual de GHL
- **db_fallback** — GHL no esta disponible, asi que el calendario recurre a mostrar solo eventos de la base de datos
- **ghl_live** — el evento viene de la API en vivo de GHL pero no tiene evento rastreado coincidente en Atribu

## Relacionado

<Cards>
  <Card title="Integracion con GoHighLevel" href="/docs/es/integrations/gohighlevel">
    Conecta GHL para sincronizar citas y datos de pipeline
  </Card>
  <Card title="Resolucion de Identidad" href="/docs/es/concepts/identity-resolution">
    Como los contactos de citas se cruzan con visitantes del sitio web
  </Card>
</Cards>

---

# Conversion Sync

Conversion Sync (tambien llamado **Senales**) envia tus resultados de negocio reales de vuelta a las plataformas publicitarias para que optimicen lo que realmente importa — ingresos, no solo clics.

La mayoria de las herramientas de atribucion se detienen en el reporte. Conversion Sync cierra el ciclo: cuando un cliente paga, agenda una cita o se convierte en un lead calificado, Atribu le dice a Meta y Google para que sus algoritmos aprendan cuales audiencias y creativos generan resultados reales.

## Por que importa

Las plataformas publicitarias optimizan segun las senales que reciben. Si solo envias vistas de pagina y envios de formulario, sus algoritmos optimizan por volumen. Si envias eventos de pago reales con montos de ingresos, optimizan por clientes que gastan dinero.

Conversion Sync no es un reenviador generico de eventos. Es un **sistema de retroalimentacion con puertas de calidad** que solo envia eventos de alta confianza con contexto de identidad verificado.

## Como funciona

```mermaid
flowchart LR
    A[Cliente paga / agenda / convierte] --> B[Atribu registra el evento]
    B --> C{Reglas de senal coinciden?}
    C -->|Si| D{Puertas de calidad pasan?}
    C -->|No| E[Evento queda solo interno]
    D -->|Si| F{Filtros de privacidad aplicados}
    D -->|No| G[Omitido — registrado con razon]
    F --> H[Meta CAPI / Google Ads]
```

1. **Ocurre una conversion** — un pago, envio de lead, cita o cambio de pipeline se registra en Atribu
2. **Las reglas de senal evaluan** — tus reglas configuradas determinan que eventos enviar y a donde
3. **Las puertas de calidad filtran** — solo eventos con suficiente contexto de identidad pasan
4. **Los filtros de privacidad depuran** — PII se hashea, campos sensibles se eliminan, URLs se sanitizan
5. **La plataforma recibe la senal** — Meta CAPI o Google Ads recibe un evento de conversion limpio y de alta confianza

## Primeros pasos

Cuando visitas la pagina de Senales por primera vez, una guia te lleva por tres pasos:

<Steps>

<Step>
### Conecta tus destinos

Vincula al menos una plataforma publicitaria (Meta o Google Ads) en Configuracion > Integraciones. Conversion Sync usa estas conexiones existentes.
</Step>

<Step>
### Crea una regla de senal

Una regla define: que evento dispara una senal, a donde va, y que valor adjuntar. Haz clic en **Nueva Senal** para abrir el constructor.
</Step>

<Step>
### Activa y monitorea

Una vez guardada la regla y configurado el destino, las senales comienzan a fluir. Monitorea las entregas en la pestana Entregas.
</Step>

</Steps>

## Creando reglas de senal

El constructor de reglas tiene 4 pasos.

### Paso 1: Fuente — que dispara la senal

| Tipo de fuente | Cuando usar | Ejemplo |
|---------------|-------------|---------|
| **Evento rastreado** | Un resultado de tu sitio web, proveedor de pago o CRM | Pago recibido, lead creado, orden colocada |
| **Cambio de etapa de pipeline** | Un cliente avanza a una nueva etapa en tu CRM | Cita agendada, calificado, cerrado ganado |

**Modo de valor** controla el valor monetario enviado:

| Modo | Comportamiento |
|------|---------------|
| **Usar monto real** | Envia el valor real del pago/deal |
| **Monto fijo** | Envia un valor fijo que tu eliges (ej. $50 por lead) |
| **Sin valor** | Sin valor monetario adjunto |

### Paso 2: Destinos — a donde enviar

Activa Meta y/o Google para cada regla. Opcionalmente puedes personalizar el nombre del evento enviado a cada plataforma.

Una regla puede enviar a ambas plataformas simultaneamente. Cada plataforma evalua independientemente si tiene suficiente contexto de coincidencia.

### Paso 3: Anulaciones de privacidad

Cada regla puede agregar restricciones de privacidad adicionales sobre la configuracion del perfil. En [Modo Salud](/docs/healthcare/overview), todos los controles estan bloqueados y aplicados por el piso de privacidad HIPAA.

### Paso 4: Revisar y guardar

Confirma tu configuracion y guarda. La regla comienza a evaluar eventos inmediatamente.

## Configurando destinos

### Meta CAPI

| Configuracion | Descripcion |
|--------------|-------------|
| **Conexion** | Tu cuenta de Meta Business (conectada en Configuracion) |
| **Pixel ID** | Identificador de tu Meta Pixel |
| **Preset de calidad** | Controla cuan estrictos son los requisitos de identidad |
| **Envio de prueba** | Envia un evento de prueba para verificar |

**Presets de calidad:**

| Preset | Requiere | Mejor para |
|--------|----------|-----------|
| **Flexible** | Email/telefono O click ID de Meta | Maximo volumen |
| **Balanceado** (default) | Identidad fuerte — email/telefono hasheado + identificadores Meta | Recomendado |
| **Estricto** | Email/telefono Y click ID de Meta | Maxima confianza |

### Google Ads

| Configuracion | Descripcion |
|--------------|-------------|
| **Conexion** | Tu cuenta de Google Ads |
| **Accion de conversion** | La ruta de accion de conversion de Google Ads |
| **Preset de calidad** | Solo balanceado — requiere `gclid` |

## Monitoreando entregas

La pestana Entregas muestra un feed en tiempo real de cada evento de senal.

### Filtrar y buscar

Filtra por:
- **Plataforma** — Todas, Meta o Google
- **Estado** — Elegible, Enviado, Omitido, Fallido o Dead Letter
- **Regla** — Eventos de una regla especifica

### Detalle del evento

Haz clic en cualquier fila para ver el detalle completo: estado, razon, senales de calidad, filtros de privacidad aplicados, contexto del cliente y payload.

### Reintentar y reproducir

Los eventos fallidos pueden reintentarse individual o masivamente. La reproduccion reevalua eventos contra tus reglas y configuracion actual.

## Razones de omision

| Razon | Que significa | Que hacer |
|-------|---------------|-----------|
| **Datos de coincidencia Meta faltantes** | Sin email, telefono o identificadores Meta | Asegurar que los formularios llamen [identify()](/docs/tracking/identify-users) |
| **Click ID de Google faltante** | Sin `gclid` para este cliente | Agregar parametros UTM — ver [configuracion UTM](/docs/tracking/utm-parameters) |
| **Baja confianza** | Confianza de identidad muy baja | Mejorar captura de formularios o integracion CRM |
| **Cerrado ganado sin pago** | Deal marcado como ganado pero sin pago confirmado | Conectar Stripe o MercadoPago |
| **Desajuste de plataforma** | Evento vino de otra plataforma | Por diseno — eventos de Meta van a Meta, de Google a Google |
| **Bloqueado por lista de permitidos HIPAA** | Tipo de evento no aprobado en [Modo Salud](/docs/healthcare/export-allowlist) | Agregar a la lista o mantener bloqueado |
| **Compuerta legal bloqueada** | Requisitos legales de [Modo Salud](/docs/healthcare/overview) no cumplidos | Completar aceptacion DPA/BAA |

## Privacidad y cumplimiento

### Modos de privacidad

| Modo | Comportamiento |
|------|---------------|
| **Estandar** | Eliminacion de campos configurable con listas de bloqueo |
| **HIPAA** | Solo exportacion por lista de permitidos — ver [Modo Salud](/docs/healthcare/overview) |
| **Personalizado** | Reservado para uso futuro |

En modo HIPAA, el panel de privacidad cambia de listas de bloqueo a [checkboxes de lista de permitidos](/docs/healthcare/export-allowlist). Los dos enfoques son mutuamente excluyentes.

### Que se elimina

| Campo | Estandar (default) | HIPAA |
|-------|-------------------|-------|
| IP del cliente | Opcional | Siempre |
| User agent | Opcional | Siempre |
| Referrer HTTP | Opcional | Siempre |
| Titulo de pagina | Opcional | Siempre |
| IDs externos | Opcional | Siempre |
| Ruta de URL | Configurable | Solo origen |
| Flags LDU de Meta | Opcional | Siempre habilitado |

### Cumplimiento legal

Las cuentas de salud deben completar un flujo legal antes de exportar senales. Ver [Onboarding de Salud](/docs/healthcare/onboarding).

## Registro de cambios

La pestana Registro de Cambios registra cada accion: reglas creadas/editadas/archivadas, destinos configurados, configuracion de privacidad cambiada, actualizaciones legales, envios de prueba y reproducciones.

## Diferencias entre plataformas

| Aspecto | Meta CAPI | Google Ads |
|---------|-----------|------------|
| Coincidencia de identidad | Email + telefono hasheado + identificadores Meta | `gclid` + email/telefono hasheado |
| Presets de calidad | Flexible, Balanceado, Estricto | Solo Balanceado |
| Nombre de evento | Personalizable | Etiqueta personalizable |
| Envios de prueba | Soportado | Proximamente |
| Limite de antiguedad | 7 dias | 90 dias |
| Transiciones de etapa | Soportado | Soportado |

## Relacionado

- [Instalar el tracker](/docs/getting-started/install-tracker) — requerido para identidad web
- [Conectar Meta Ads](/docs/integrations/meta) — configurar la conexion Meta
- [Conectar Google Ads](/docs/integrations/google-ads) — configurar la conexion Google
- [Metas (Definiciones de Conversion)](/docs/settings/goals) — definir que resultados puede usar Conversion Sync
- [Modelos de Atribucion](/docs/concepts/attribution-models) — como Atribu distribuye el credito
- [Modo Salud](/docs/healthcare/overview) — controles de exportacion compatibles con HIPAA
- [Resolucion de Identidad](/docs/concepts/identity-resolution) — como los visitantes anonimos se vuelven clientes conocidos

---

La mayoria de los clientes no compran en la primera visita. Ven un anuncio, navegan tu sitio, se van, regresan desde una busqueda en Google, y quiza pagan una semana despues al hacer clic en un enlace de email. La funcion de Recorridos de Clientes te muestra este camino completo para cada cliente individual.

<VideoEmbed src="/docs/videos/customer-journey.mp4" title="Recorrido del cliente — desde el clic en el anuncio hasta el pago" />

## Selector de objetivo

Comienza eligiendo que objetivo de conversion quieres analizar. Los objetivos comunes incluyen:

- **payment_received** — clientes que realizaron un pago (Stripe o MercadoPago)
- **lead_created** — contactos que se convirtieron en leads en tu CRM
- **appointment_booked** — personas que agendaron una cita
- **closed_won** — negocios que se cerraron exitosamente

El selector de objetivo muestra todos los tipos de conversion definidos para tu perfil, junto con cuantas conversiones ocurrieron en el rango de fechas seleccionado.

## Lista de clientes

Despues de seleccionar un objetivo, ves una lista de cada cliente que completo esa conversion. Cada fila muestra:

| Campo | Que muestra |
|-------|------------|
| **Nombre y email** | El nombre y correo electronico del cliente (si fue identificado) |
| **Canal y fuente** | Como te encontro por primera vez (ej., Social de Pago desde Instagram) |
| **Ingresos** | El monto en efectivo de esta conversion |
| **Tiempo hasta convertir** | Cuanto tardo desde su primer contacto de marketing hasta la conversion |
| **Puntos de contacto** | Numero de interacciones de marketing antes de convertir |
| **Canales de contacto** | La secuencia de canales en su recorrido (ej., Social de Pago, Directo, Busqueda Organica) |

Puedes buscar clientes especificos por nombre o email, y la lista se pagina automaticamente conforme haces scroll.

## Linea de tiempo detallada del cliente

Haz clic en cualquier cliente para ver la linea de tiempo completa de su recorrido. Esto muestra cada interaccion individual en orden cronologico:

- **Vistas de pagina** — cada pagina que visitaron, con URL y referente
- **Envios de formulario** — cuando enviaron un formulario (lo que activo la identificacion)
- **Reservas** — eventos de agendamiento de citas
- **Pagos** — transacciones de Stripe o MercadoPago con montos
- **Eventos del CRM** — cambios de estado de leads, actualizaciones de oportunidades desde GoHighLevel

Cada evento en la linea de tiempo muestra:

- Marca de tiempo
- Canal y fuente
- Nombre de campana (si proviene de un anuncio pagado)
- Tipo de dispositivo, navegador y pais
- Detalles especificos del evento (URL de pagina, monto de pago, etc.)

<Callout type="info" title="Toques sinteticos">
Algunos eventos estan marcados como "sinteticos" — estos representan conversiones fuera del sitio como formularios de leads de Meta donde el cliente interactuo con tu anuncio sin visitar tu sitio web. Atribu crea estos puntos de contacto automaticamente para que puedan ser atribuidos a la campana correcta.
</Callout>

## Por que importan los recorridos multi-toque

Observar los recorridos individuales de clientes revela patrones que los datos agregados no pueden:

- **Cuantos toques se necesitan?** Si la mayoria de los clientes necesitan 4-5 interacciones antes de pagar, sabes que no debes juzgar una campana solo por su rendimiento de primer clic.
- **Que rol juega cada canal?** Social de Pago podria presentar clientes (primer toque), mientras que Email cierra el negocio (ultimo toque). Ambos merecen credito.
- **Donde se pierden las personas?** Si los clientes visitan 3 veces pero nunca convierten, puede que necesites una mejor oferta o estrategia de retargeting.
- **Que tan largo es el ciclo de compra?** Un ciclo de 2 dias significa que el retargeting agresivo funciona. Un ciclo de 30 dias significa que las secuencias de nurturing son esenciales.

## Pestana de Usuarios

Cambia a la pestana de Usuarios para ver todos los visitantes — no solo los que convirtieron. Esta vista muestra:

- Tanto clientes identificados (con nombres y emails) como visitantes anonimos
- Conteo de sesiones, vistas de pagina totales e ingresos por visitante
- Ultima vez visto
- Los canales por los que llegaron

<Callout type="info" title="Nombres de visitantes anonimos">
Los visitantes que no se han identificado aparecen con nombres coloridos de animales como "Coral Fox" o "Jade Penguin." El mismo visitante siempre recibe el mismo nombre, para que puedas rastrearlo entre sesiones incluso antes de que proporcione su email.
</Callout>

## Relacionado

<Cards>
  <Card title="Resolucion de Identidad" href="/docs/es/concepts/identity-resolution">
    Como los visitantes anonimos se convierten en clientes conocidos
  </Card>
  <Card title="Modelos de Atribucion" href="/docs/es/concepts/attribution-models">
    Como se distribuye el credito entre los puntos de contacto en el recorrido
  </Card>
  <Card title="Toques Sinteticos" href="/docs/es/concepts/synthetic-touches">
    Como las conversiones fuera del sitio aparecen en las lineas de tiempo de clientes
  </Card>
</Cards>

---

El dashboard es tu centro de control. Reune todas las metricas importantes de marketing en una sola vista para que puedas entender como estan rindiendo tus campanas ahora y como se comparan con el periodo anterior.

## Barra de metricas KPI

La parte superior del dashboard muestra tus indicadores clave de rendimiento lado a lado. Cada metrica compara el periodo actual con el periodo anterior de igual duracion.

| Metrica | Que significa |
|---------|--------------|
| **Visitantes** | Personas unicas que visitaron tu sitio web en este periodo. |
| **Ingresos** | Dinero efectivo proveniente de conversiones atribuidas a anuncios (solo pagos de Stripe o MercadoPago). |
| **Gasto** | Monto total gastado en anuncios en todas las plataformas conectadas. |
| **CAC** | Costo de Adquisicion de Cliente — tu gasto en anuncios dividido entre el numero de conversiones. Menor es mejor. |
| **Tasa de Rebote** | Porcentaje de visitantes que se fueron despues de ver solo una pagina. |
| **En linea ahora** | Personas navegando tu sitio en este momento (se actualiza cada 30 segundos). |

Cada metrica muestra un cambio porcentual comparado con el periodo anterior. Verde significa que el numero mejoro; rojo significa que fue en la direccion equivocada.

<Callout type="info" title="Los ingresos solo cuentan pagos reales">
Los ingresos para calculos de ROAS solo incluyen pagos confirmados en efectivo de Stripe y MercadoPago. Los valores de pipeline de tu CRM (como estimaciones de negocios) se muestran por separado y nunca se mezclan en el ROAS.
</Callout>

## Selector de rango de fechas

Selecciona cualquier periodo de tiempo y Atribu automaticamente lo compara con el periodo anterior de la misma duracion. Por ejemplo, si seleccionas los ultimos 7 dias, el periodo de comparacion son los 7 dias anteriores.

Hay presets rapidos disponibles para rangos comunes: ultimos 7 dias, ultimos 14 dias, ultimos 30 dias, o un rango personalizado.

## Selector de modelo de atribucion

Elige como se asigna el credito a tus puntos de contacto de marketing usando el dropdown en el encabezado. Cambiar de modelo recalcula instantaneamente los ingresos, ROAS y rankings de campanas en todo el dashboard.

<Callout type="info" title="Conoce mas sobre los modelos">
Consulta [Modelos de Atribucion](/docs/es/concepts/attribution-models) para una explicacion detallada de cada modelo y cuando usarlo.
</Callout>

## Grafico principal

El grafico principal es una visualizacion de doble eje que muestra dos metricas a lo largo del tiempo:

- **Linea azul**: Visitantes por dia (eje izquierdo)
- **Barras ambar**: Ingresos en efectivo por dia (eje derecho)

Pasa el cursor sobre cualquier dia para ver los numeros exactos en un tooltip. El tooltip tambien muestra el gasto en anuncios para ese dia cuando esta disponible. Puedes activar o desactivar cualquier serie haciendo clic en el cuadro de color junto a su etiqueta en la barra de metricas.

## Tableros de desglose

Debajo del grafico principal, cuatro tableros desglosan tu trafico por diferentes dimensiones. Cada tablero tiene multiples pestanas:

| Tablero | Pestanas |
|---------|----------|
| **Fuentes de Trafico** | Canal, Referente, Campana, Palabra clave |
| **Geografia** | Pais, Region, Ciudad |
| **Contenido** | Pagina, Pagina de entrada, Pagina de salida |
| **Tecnologia** | Navegador, SO, Dispositivo |

Cada fila muestra visitantes, visitas, vistas de pagina, tasa de rebote, conversiones e ingresos. Haz clic en cualquier fila para ver un dialogo de desglose detallado con todos los datos.

El tablero de Fuentes de Trafico tambien incluye un grafico de dona que visualiza la distribucion de canales de un vistazo.

## Mejores campanas

La parte inferior del dashboard muestra tus campanas con mejor rendimiento, ordenadas por ingresos atribuidos. Cada campana muestra:

- Gasto total e ingresos atribuidos
- Numero de resultados de conversion
- ROAS (Retorno sobre Gasto en Anuncios)

Haz clic en **Ver Todas** para ir a la pagina completa de [Rendimiento de Anuncios](/docs/es/features/ads-performance).

## Filtros del dashboard

Usa la barra de filtros debajo del selector de rango de fechas para filtrar todos los datos del dashboard. Las dimensiones de filtro disponibles incluyen:

- **Canal** — Social de Pago, Busqueda Organica, Directo, etc.
- **Pais** — filtrar por pais del visitante
- **Dispositivo** — Escritorio, Movil, Tablet
- **Campana** — campanas de anuncios especificas
- **Objetivo** — filtrar por tipo de conversion (ej., solo mostrar payment_received)

Los filtros se aplican a cada seccion del dashboard simultaneamente — KPIs, graficos, desgloses y mejores campanas se actualizan juntos.

## Banner de salud de conexiones

Cuando tienes integraciones conectadas (Meta, Google Ads, Stripe, GHL), un banner de salud aparece junto al nombre de tu perfil. Indica el estado de sincronizacion de cada conexion, permitiendote saber si los datos estan frescos o si alguna conexion necesita atencion.

## Tarjeta de Recorridos y Usuarios

En la parte inferior del dashboard, una tarjeta con pestanas te permite explorar rapidamente los recorridos de clientes, listas de visitantes y embudos de conversion sin salir de la pagina. Consulta la pagina de [Recorridos de Clientes](/docs/es/features/customer-journey) para todos los detalles.

## Relacionado

<Cards>
  <Card title="Modelos de Atribucion" href="/docs/es/concepts/attribution-models">
    Como se distribuye el credito entre puntos de contacto
  </Card>
  <Card title="Tipos de Ingresos" href="/docs/es/concepts/revenue-types">
    Por que solo los pagos en efectivo aparecen en los calculos de ROAS
  </Card>
  <Card title="Reportes" href="/docs/es/features/reports">
    Genera reportes de rendimiento compartibles a partir de los datos de tu dashboard
  </Card>
</Cards>

---

La calidad de datos afecta directamente la precision de la atribucion. Si los visitantes no son identificados, si faltan click IDs, o si los parametros UTM son inconsistentes, tus numeros de ROAS estaran incompletos. La pagina de Calidad de Datos te ayuda a monitorear y corregir estos problemas.

## Resumen de perfiles de identidad

La pagina de Calidad de Datos muestra un resumen de tus perfiles de identidad de clientes:

| Metrica | Que significa |
|---------|--------------|
| **Perfiles totales** | Numero de perfiles de clientes en tu grafo de identidad |
| **Sin email** | Perfiles que no tienen email principal — son mas dificiles de cruzar entre puntos de contacto |
| **Sin telefono** | Perfiles que no tienen numero de telefono principal |

Cada perfil se lista con su fuente (GoHighLevel, Stripe, MercadoPago, Meta), nombre, email, telefono e identificadores. Puedes filtrar para ver solo perfiles con datos faltantes.

## Reparacion de identidad

Cuando Atribu detecta que un perfil de cliente le falta un email o numero de telefono, pero esa informacion existe en los datos crudos del evento (como una oportunidad de GHL o cargo de Stripe), sugiere una operacion de reparacion.

<Steps>
<Step>

### Escanear problemas

La pagina de Calidad de Datos escanea automaticamente tus perfiles e identifica aquellos con informacion de contacto faltante que podria ser rellenada desde datos existentes.

</Step>
<Step>

### Revisar sugerencias

Cada sugerencia muestra el perfil, que datos faltan, y el valor sugerido con su fuente. Por ejemplo: "El perfil Juan Perez no tiene email. Sugerido: juan@ejemplo.com (de contacto GoHighLevel)."

</Step>
<Step>

### Previsualizar y aplicar

Previsualiza las operaciones de reparacion para ver exactamente que cambiara, luego aplicalas en lote. El proceso de reparacion rellena datos faltantes sin sobrescribir valores existentes.

</Step>
</Steps>

## Que afecta la calidad de datos

Varios factores pueden reducir la precision de tus datos de atribucion:

### Bloqueadores de anuncios

Algunos visitantes usan bloqueadores de anuncios que impiden que el tracker de Atribu se cargue. Esto significa que sus visitas no se registran y no pueden ser atribuidas. **Solucion**: Configura un [dominio personalizado](/docs/es/tracking/custom-domain) para tu tracker para evitar la mayoria de bloqueadores de anuncios.

### Llamadas identify faltantes

Si los visitantes navegan tu sitio pero nunca envian un formulario, completan una reserva o se identifican de alguna manera, su visita anonima no puede vincularse a un pago o evento del CRM posterior. **Solucion**: Habilita la [auto-captura de formularios](/docs/es/tracking/auto-capture) para que la identificacion ocurra automaticamente cuando los visitantes llenen cualquier formulario.

### Parametros UTM faltantes

El trafico de campanas de email, publicaciones en redes sociales o enlaces de socios sin parametros UTM se clasifica como "Directo" — haciendo imposible atribuir conversiones a esas fuentes. **Solucion**: Usa parametros UTM consistentes en todos los enlaces de marketing.

### Expiracion de cookies

Los visitantes que regresan despues de un largo periodo (cookies del navegador borradas, dispositivo diferente) apareceran como nuevos visitantes. **Solucion**: Esta es una limitacion natural. El grafo de identidad aun los cruza si proporcionan el mismo email o numero de telefono.

### Integraciones incompletas

Si tu proveedor de pagos o CRM no esta conectado, esos eventos de conversion no pueden ser atribuidos a campanas de anuncios. **Solucion**: Conecta todas tus fuentes de datos.

## Como mejorar la calidad de datos

<Steps>
<Step>

### Instala el tracker en todas las paginas

Asegurate de que el script del tracker de Atribu este en cada pagina de tu sitio web, no solo en las paginas de destino. Los visitantes frecuentemente navegan multiples paginas antes de convertir, y cada vista de pagina proporciona datos de atribucion.

</Step>
<Step>

### Habilita la auto-captura de formularios

Activa la deteccion automatica de formularios para que el tracker capture email y telefono de cualquier envio de formulario. Esto activa la llamada `identify()` que conecta visitantes anonimos con clientes conocidos.

</Step>
<Step>

### Configura un dominio personalizado

Configura un dominio de rastreo personalizado (como `track.tudominio.com`) para evitar la mayoria de bloqueadores de anuncios y aumentar las tasas de recoleccion de datos.

</Step>
<Step>

### Usa parametros UTM consistentes

Agrega UTMs a todos tus enlaces de marketing — campanas de email, publicaciones en redes sociales, referencias de socios. Sin UTMs, Atribu no puede distinguir estas fuentes del trafico directo.

Para anuncios de Meta, usa esta plantilla de parametros URL:
```text title="Meta Ads UTM Template"
utm_source=facebook&utm_medium=paid&utm_campaign={{campaign.id}}&utm_content={{ad.id}}&utm_term={{adset.id}}
```

</Step>
<Step>

### Conecta todos los proveedores de pago

Conecta tanto Stripe como MercadoPago (si usas ambos) para que cada pago sea capturado y atribuido. Fuentes de pago no conectadas crean vacios en tus datos de ingresos.

</Step>
<Step>

### Conecta tu CRM

Conecta GoHighLevel para visibilidad completa del pipeline. Esto permite a Atribu rastrear leads, citas y oportunidades — no solo los pagos finales.

</Step>
</Steps>

<Callout type="info" title="Que es una buena cobertura de atribucion?">
80% o mas de cobertura de atribucion es bueno — significa que la mayoria de tus conversiones pueden rastrearse hasta un punto de contacto de marketing. 90% o mas es excelente. Por debajo del 60% generalmente indica falta de instalacion del tracker o falta de auto-captura de formularios.
</Callout>

## Relacionado

<Cards>
  <Card title="Instalar el Tracker" href="/docs/es/getting-started/install-tracker">
    Configura el script de rastreo en tu sitio web
  </Card>
  <Card title="Resolucion de Identidad" href="/docs/es/concepts/identity-resolution">
    Como los visitantes anonimos se convierten en clientes conocidos
  </Card>
  <Card title="Toques Sinteticos" href="/docs/es/concepts/synthetic-touches">
    Como las conversiones fuera del sitio se atribuyen sin visitas web
  </Card>
</Cards>

---

Los Enlaces de Pago te permiten crear URLs de pago de Stripe que incluyen los metadatos de rastreo de Atribu. Cuando un cliente paga a traves de uno de estos enlaces, Atribu puede rastrear el pago hasta la campana de anuncios exacta que lo trajo.

## El problema

Los pagos normales de Stripe pierden la conexion con el clic original del anuncio. Un cliente podria hacer clic en tu anuncio el lunes, navegar tu sitio, y luego pagar el jueves despues de recibir un email de seguimiento. Para cuando llega el pago, no hay un enlace directo entre el cargo de Stripe y el clic original del anuncio.

## Como los Enlaces de Pago resuelven esto

Atribu crea Enlaces de Pago de Stripe que incluyen metadatos de rastreo. Estos metadatos conectan el pago con la sesion del visitante, haciendo que la atribucion sea automatica.

### Tres capas de atribucion

Los Enlaces de Pago usan tres metodos para asegurar que la atribucion funcione, incluso cuando una capa falla:

<Steps>
<Step>

### Transferencia de cookies

Las cookies de rastreo (ID de visitante e ID de sesion) se incrustan en los metadatos de la sesion de checkout de Stripe. Cuando el cliente completa el pago, Atribu lee estas cookies del payload del webhook y vincula instantaneamente el pago con la visita original.

</Step>
<Step>

### Redireccion post-pago

Despues del pago, el cliente es redirigido de vuelta a tu sitio web con un ID de sesion de Stripe en la URL. El tracker de Atribu en tu sitio detecta esto y dispara un evento `stripe_checkout_completed`, creando un vinculo directo entre el pago y la sesion de navegacion.

</Step>
<Step>

### Respaldo del grafo de identidad

Incluso sin cookies o redirecciones, el email del cliente del pago de Stripe se cruza con su envio de formulario o reserva anterior a traves del grafo de identidad de Atribu. Si el mismo email aparecio en un envio de formulario durante una sesion rastreada, el pago se atribuye al clic original del anuncio.

</Step>
</Steps>

## Crear un enlace de pago

<Steps>
<Step>

### Navega a Enlaces de Pago

Ve a la configuracion de tu perfil o pagina de desarrollador donde se gestionan los Enlaces de Pago.

</Step>
<Step>

### Configura el pago

Establece los campos requeridos:

- **Monto** — el monto del pago en centavos (ej., 5000 para $50.00)
- **Moneda** — el codigo de moneda (USD, MXN, EUR, etc.)
- **Nombre del producto** — una descripcion de lo que el cliente esta pagando

</Step>
<Step>

### Opcional: vincula a un cliente o campana

Puedes asociar el enlace de pago con:

- Un **perfil de cliente** especifico — pre-vincula el pago a un cliente conocido
- Una **campana** especifica — atribuye directamente el pago a una campana

</Step>
<Step>

### Opcional: establece una URL de redireccion

Proporciona una URL en tu sitio web rastreado a donde los clientes seran enviados despues del pago. Esto habilita la capa de atribucion por redireccion post-pago. La URL automaticamente recibe un parametro `?session_id={CHECKOUT_SESSION_ID}` agregado.

</Step>
<Step>

### Copia y comparte

Copia la URL del Enlace de Pago de Stripe generada y compartela con tu cliente — via email, SMS, WhatsApp, o incrustala en tu sitio web.

</Step>
</Steps>

<Callout type="info" title="Los ingresos de Enlaces de Pago cuentan como efectivo">
Los pagos realizados a traves de Enlaces de Pago siempre cuentan como ingresos "en efectivo" con `revenue_type = 'cash'`. Esto significa que se incluyen en los calculos de ROAS junto con tus otros pagos de Stripe y MercadoPago.
</Callout>

## Como funciona tecnicamente

Cuando creas un Enlace de Pago, Atribu:

1. Crea un Enlace de Pago de Stripe via la API de Stripe usando tu cuenta de Stripe conectada
2. Incrusta `atribu_profile_id`, `atribu_customer_profile_id` y `campaign_id` en los metadatos del enlace de pago
3. Si se proporciona una URL de redireccion, agrega el ID de sesion de checkout de Stripe para la vinculacion post-pago
4. Almacena el enlace en la tabla `payment_links` para auditoria y rastreo

Cuando el cliente paga, el flujo estandar de webhook de Stripe maneja el resto — el evento `checkout.session.completed` lleva todos los metadatos necesarios para la atribucion.

## Relacionado

<Cards>
  <Card title="Integracion con Stripe" href="/docs/es/integrations/stripe">
    Como se rastrean y atribuyen los pagos de Stripe
  </Card>
  <Card title="Tipos de Ingresos" href="/docs/es/concepts/revenue-types">
    Los ingresos de Enlaces de Pago cuentan como efectivo para ROAS
  </Card>
  <Card title="Resolucion de Identidad" href="/docs/es/concepts/identity-resolution">
    Como el grafo de identidad proporciona atribucion de respaldo
  </Card>
</Cards>

---

Los Reportes te permiten crear resumenes profesionales y compartibles del rendimiento de marketing. Son ideales para reportar a clientes, actualizar al equipo o revisiones mensuales de rendimiento.

## Que incluye un reporte

Cada reporte generado contiene:

- **Resumen de KPIs** — gasto, ingresos, ROAS, visitantes y conversiones del periodo
- **Mejores campanas** — tus campanas con mejor rendimiento ordenadas por ingresos atribuidos y ROAS
- **Desglose por canal** — fuentes de trafico y como cada canal contribuyo a los resultados
- **Analisis de atribucion** — como se distribuye el credito entre puntos de contacto bajo el modelo elegido
- **Resumen de conversiones** — conteos y valores para cada tipo de conversion

## Crear un reporte

<Steps>
<Step>

### Ve a la pagina de Reportes

Navega a **Reportes** en la barra lateral. Veras los reportes generados previamente listados aqui.

</Step>
<Step>

### Haz clic en Generar Reporte

Haz clic en el boton **Generar** en la esquina superior derecha para abrir el dialogo de configuracion del reporte.

</Step>
<Step>

### Selecciona tu rango de fechas

Elige un preset (Ultimos 7 dias, Ultimos 14 dias, Ultimos 30 dias) o selecciona un rango personalizado usando el calendario.

</Step>
<Step>

### Elige un modelo de atribucion

Selecciona que modelo de atribucion debe usar el reporte para los calculos de ingresos y ROAS. El modelo que elijas afecta como se distribuye el credito entre campanas.

</Step>
<Step>

### Genera y revisa

Haz clic en **Generar** y espera unos segundos. Atribu obtiene todos tus datos, ejecuta los calculos y crea el reporte. Una vez listo, seras llevado a la pagina de detalle del reporte.

</Step>
</Steps>

## Ver un reporte

Cada reporte tiene su propia pagina de detalle mostrando todo el contenido. Puedes desplazarte por todas las secciones — KPIs, rankings de campanas, analisis de canales — en un formato limpio listo para imprimir.

## Compartir reportes

Cada reporte obtiene un enlace publico para compartir. Cualquier persona con el enlace puede ver el reporte sin necesitar una cuenta de Atribu.

<Steps>
<Step>

### Copia el enlace para compartir

En la pagina de detalle del reporte, haz clic en el boton **Compartir**. La URL publica se copia a tu portapapeles.

</Step>
<Step>

### Envia a tu cliente o equipo

Pega el enlace en un email, mensaje de Slack o cualquier otro lugar. El destinatario ve una version de solo lectura del reporte.

</Step>
</Steps>

## Descargar como PDF

Haz clic en el boton **Descargar** en cualquier reporte para exportarlo como archivo PDF. El PDF preserva el diseno del reporte y puede adjuntarse a emails o imprimirse.

## Gestionar reportes

- Los reportes se listan en la pagina de Reportes con su rango de fechas, modelo y fecha de creacion
- Haz clic en cualquier reporte para ver su contenido completo
- Elimina los reportes que ya no necesites usando el icono de papelera

<Callout type="info" title="Los reportes capturan un momento en el tiempo">
Los reportes capturan tus datos en el momento en que se generan. Si llegan nuevas conversiones o los datos de atribucion cambian despues de la generacion, el reporte no se actualizara automaticamente. Genera un nuevo reporte para obtener los numeros mas recientes.
</Callout>

## Relacionado

<Cards>
  <Card title="Dashboard" href="/docs/es/features/dashboard">
    La vista en vivo de donde se generan los reportes
  </Card>
  <Card title="Modelos de Atribucion" href="/docs/es/concepts/attribution-models">
    Como el modelo que eliges afecta los numeros del reporte
  </Card>
</Cards>

---

Atribu clasifica automaticamente a cada visitante en un canal de trafico segun como llego a tu sitio. Esto te ayuda a entender cuales esfuerzos de marketing generan mas trafico y conversiones.

## Tipos de canales

Cada visita a tu sitio web se clasifica en uno de estos canales:

| Canal | Que significa |
|-------|--------------|
| **Social de Pago** | Visitantes que hicieron clic en un anuncio de pago en Facebook, Instagram, TikTok u otras plataformas sociales. Identificados por click IDs como `fbclid` o `ttclid`, o por parametros UTM con `medium=paid`. |
| **Social Organico** | Visitantes de publicaciones no pagadas en redes sociales — alguien compartio tu enlace en Facebook o publicaste en Instagram sin pagar por promocion. |
| **Busqueda de Pago** | Visitantes de Google Ads, Bing Ads u otras campanas de busqueda pagada. Identificados por `gclid`, `msclkid` o parametros UTM con `medium=cpc`. |
| **Busqueda Organica** | Visitantes que te encontraron a traves de un resultado de Google, Bing u otro motor de busqueda — sin que pagaras por el clic. |
| **Directo** | Visitantes que escribieron tu URL directamente en su navegador, usaron un marcador, o vinieron de una fuente que no pudo ser identificada. |
| **Referencia** | Visitantes que hicieron clic en un enlace a tu sitio desde otro sitio web (un articulo de blog, directorio, sitio de socio, etc.). |
| **Email** | Visitantes de campanas de email. Requiere parametros UTM con `medium=email` en tus enlaces de email. |
| **Display** | Visitantes de banners o redes de publicidad display. |

<Callout type="info" title="Conteo de visitantes del grafico de dona">
El grafico de dona de Fuentes de Trafico cuenta visitantes usando datos a nivel de sesion, que incluye tanto visitantes web como eventos del lado del servidor. Este numero puede diferir ligeramente del conteo de visitantes en la tarjeta KPI, que solo cuenta visitantes web unicos.
</Callout>

## Tableros de desglose

El dashboard incluye cuatro tableros de desglose que te permiten analizar tu trafico desde diferentes angulos. Cada tablero tiene multiples pestanas para un analisis mas profundo.

### Tablero de Fuentes de Trafico

| Pestana | Muestra |
|---------|---------|
| **Canal** | Visitantes agrupados por canal (Social de Pago, Directo, Busqueda Organica, etc.) |
| **Referente** | Los sitios web o dominios especificos que envian visitantes hacia ti |
| **Campana** | Trafico agrupado por nombre de campana UTM |
| **Palabra clave** | Palabras clave de busqueda que generan trafico (requiere conexion con Google Search Console) |

### Tablero de Geografia

| Pestana | Muestra |
|---------|---------|
| **Pais** | Donde estan ubicados tus visitantes por pais |
| **Region** | Desglose a nivel de estado o provincia |
| **Ciudad** | Desglose a nivel de ciudad |

### Tablero de Contenido

| Pestana | Muestra |
|---------|---------|
| **Pagina** | Cuales paginas reciben mas vistas |
| **Pagina de entrada** | La primera pagina donde llegan los visitantes |
| **Pagina de salida** | La ultima pagina que los visitantes ven antes de irse |

### Tablero de Tecnologia

| Pestana | Muestra |
|---------|---------|
| **Navegador** | Chrome, Safari, Firefox, etc. |
| **SO** | Windows, macOS, iOS, Android, etc. |
| **Dispositivo** | Escritorio, Movil o Tablet |

## Metricas en cada desglose

Cada fila en un tablero de desglose muestra estas metricas:

- **Visitantes** — personas unicas
- **Visitas** — sesiones totales (una persona puede tener multiples visitas)
- **Vistas de pagina** — total de paginas vistas
- **Tasa de Rebote** — porcentaje de visitas donde la persona se fue despues de una pagina
- **Conversiones** — numero de conversiones atribuidas
- **Ingresos** — ingresos en efectivo atribuidos

Haz clic en cualquier fila para expandir un dialogo de detalle que muestra el desglose completo de metricas y la tendencia a lo largo del tiempo. Tambien puedes cambiar el orden de clasificacion para ordenar por cualquiera de estas metricas.

## Filtrado

Usa los filtros del dashboard para enfocarte en segmentos de trafico especificos. Por ejemplo, filtra por **Canal = Social de Pago** para ver solo visitantes de tus anuncios pagados, y luego examina de que paises o dispositivos provienen.

Los filtros se aplican en los cuatro tableros simultaneamente, para que puedas combinar dimensiones — como ver solo visitantes moviles de Estados Unidos que llegaron a traves de Social de Pago.

<Callout type="info" title="Detalles de clasificacion de canales">
Para una explicacion tecnica detallada de como Atribu clasifica los canales (incluyendo deteccion de click IDs y reglas UTM), consulta [Canales](/docs/es/concepts/channels).
</Callout>

## Relacionado

<Cards>
  <Card title="Parametros UTM" href="/docs/es/tracking/utm-parameters">
    Etiqueta tus fuentes de trafico para una clasificacion de canales precisa
  </Card>
  <Card title="Dashboard" href="/docs/es/features/dashboard">
    Ve los datos de fuentes de trafico junto con KPIs y rendimiento de campanas
  </Card>
</Cards>

---

Conectar tus plataformas de anuncios permite a Atribu importar tus campanas, conjuntos de anuncios (o grupos de anuncios), anuncios individuales y datos de gasto diario. Esto es lo que hace posible calcular el **ROAS** (Retorno sobre la Inversion Publicitaria) -- la relacion entre los ingresos generados y el dinero gastado en publicidad. Sin esta conexion, Atribu puede seguir rastreando visitantes y conversiones, pero no puede decirte cuales campanas especificas son rentables.

## Que se sincroniza

Una vez conectado, Atribu importa automaticamente:

| Dato | Descripcion |
|------|-------------|
| **Campanas** | Todas las campanas en tu cuenta publicitaria, incluyendo nombre, estado y objetivo |
| **Conjuntos de anuncios / Grupos de anuncios** | Los grupos de segmentacion dentro de cada campana |
| **Anuncios** | Creatividades individuales, incluyendo miniaturas y referencias de video |
| **Gasto diario** | Cuanto gastaste cada dia, desglosado por campana, conjunto de anuncios y anuncio |
| **Impresiones** | Cuantas veces se mostraron tus anuncios |
| **Clics** | Cuantas veces las personas hicieron clic en tus anuncios |

Las metricas de rendimiento como CPM (costo por mil impresiones), CPC (costo por clic) y CTR (tasa de clics) se calculan automaticamente a partir de estos datos.

## Conecta tus plataformas

<Tabs items={["Meta (Facebook e Instagram)", "Google Ads"]}>
<Tab value="Meta (Facebook e Instagram)">

<Steps>
<Step>

### Abre Integraciones

En tu dashboard de Atribu, ve a **Configuracion > Integraciones**.

</Step>
<Step>

### Haz clic en Conectar Meta

Encuentra la tarjeta de Meta y haz clic en **Conectar**. Seras redirigido a Facebook.

</Step>
<Step>

### Autoriza en Facebook

Inicia sesion en Facebook (si aun no lo has hecho) y revisa los permisos que Atribu esta solicitando:

- **ads_read** -- Leer los datos de tu cuenta publicitaria (campanas, gasto, rendimiento)

Haz clic en **Continuar** para autorizar.

</Step>
<Step>

### Selecciona cuentas publicitarias

Despues de la autorizacion, elige cuales cuentas publicitarias quieres sincronizar. Puedes seleccionar multiples cuentas si gestionas anuncios para varios negocios.

</Step>
<Step>

### Listo

Atribu comienza a sincronizar tus datos inmediatamente. Veras las campanas y datos de gasto en tu dashboard en pocos minutos.

</Step>
</Steps>

<Callout type="info" title="Frecuencia de sincronizacion">
Atribu sincroniza los datos de tus anuncios de Meta automaticamente. Despues de la importacion inicial (que trae datos historicos), las actualizaciones llegan a lo largo del dia. Los datos de gasto del dia actual pueden tardar unas horas en reflejar los numeros mas recientes, ya que la API de reportes de Meta tiene sus propios retrasos.
</Callout>

<Callout type="warn" title="Re-autorizacion">
Si Atribu agrega nuevas funciones que requieren permisos adicionales, es posible que necesites reconectar tu cuenta de Meta. Cuando esto suceda, veras un banner en Configuracion. Facebook solo muestra el dialogo de permisos cuando Atribu lo solicita explicitamente, por lo que el proceso de reconexion es rapido -- tus datos existentes se conservan.
</Callout>

</Tab>
<Tab value="Google Ads">

<Steps>
<Step>

### Abre Integraciones

En tu dashboard de Atribu, ve a **Configuracion > Integraciones**.

</Step>
<Step>

### Haz clic en Conectar Google Ads

Encuentra la tarjeta de Google Ads y haz clic en **Conectar**. Seras redirigido a Google.

</Step>
<Step>

### Inicia sesion con Google

Inicia sesion con la cuenta de Google que tiene acceso a tu cuenta de Google Ads. Revisa los permisos y haz clic en **Permitir**.

</Step>
<Step>

### Selecciona tu cuenta de cliente

Si tu cuenta de Google tiene acceso a multiples cuentas de cliente de Google Ads (comun para agencias que usan una Cuenta de Administrador), selecciona la que deseas sincronizar.

</Step>
<Step>

### Listo

Atribu comienza a sincronizar tus datos de Google Ads. Las campanas, grupos de anuncios, anuncios y datos de gasto apareceran en tu dashboard en minutos.

</Step>
</Steps>

<Callout type="info" title="Cuentas de administrador">
Si usas una Cuenta de Administrador de Google Ads (MCC), puedes conectar cuentas de cliente individuales dentro de ella. Cada cuenta de cliente se sincroniza de forma independiente.
</Callout>

</Tab>
</Tabs>

## Solucion de problemas

### No veo mis cuentas publicitarias

- **Meta**: Asegurate de iniciar sesion con una cuenta de Facebook que tenga al menos acceso de "Anunciante" a la cuenta publicitaria. Si gestionas anuncios a traves de un Business Manager, la cuenta publicitaria debe estar compartida con tu cuenta personal de Facebook.
- **Google Ads**: Asegurate de iniciar sesion con la cuenta de Google que tiene acceso directo (o acceso a traves de Cuenta de Administrador) a la cuenta de cliente de Google Ads.

### Faltan datos o estan incompletos

- **Cambios recientes**: Despues de conectar, la sincronizacion inicial trae datos historicos. Esto puede tardar unos minutos para cuentas con muchas campanas. Revisa de nuevo en 10-15 minutos.
- **Campanas pausadas**: Atribu importa todas las campanas sin importar su estado (activas, pausadas, archivadas). Si no ves una campana especifica, verifica que conectaste la cuenta publicitaria correcta.
- **El gasto muestra cero**: Algunas campanas (especialmente las de reconocimiento de marca) pueden tener impresiones pero gasto muy bajo o cero registrado en ciertos dias. Esto es normal.

### Necesito reconectar

Si tu conexion deja de funcionar (por ejemplo, si cambiaste tu contrasena de Facebook o revocaste el acceso), ve a **Configuracion > Integraciones**, encuentra la plataforma y haz clic en **Reconectar**. Tus datos historicos se conservan.

## Siguientes pasos

<Cards>
  <Card title="Conectar Pagos" href="/docs/es/getting-started/connect-payments">
    Vincula Stripe o MercadoPago para atribuir ingresos reales a tus campanas
  </Card>
  <Card title="Conectar GoHighLevel" href="/docs/es/getting-started/connect-crm">
    Sincroniza contactos, oportunidades y citas desde tu CRM
  </Card>
  <Card title="Detalles de la Integracion con Meta" href="/docs/es/integrations/meta">
    Previsualizaciones de video, exportacion de conversiones CAPI y configuracion especifica de Meta
  </Card>
  <Card title="Detalles de la Integracion con Google Ads" href="/docs/es/integrations/google-ads">
    Exportacion de conversiones, soporte de Cuenta de Administrador y configuracion especifica de Google Ads
  </Card>
</Cards>

---

Si usas GoHighLevel (GHL) como tu CRM, conectarlo a Atribu te da visibilidad del recorrido completo del cliente -- desde el primer clic en un anuncio hasta una cita agendada y un negocio cerrado. Atribu sincroniza tus contactos, oportunidades (negocios) y eventos de calendario, y mapea las etapas de tu pipeline a tipos de conversion para que puedas seguir el progreso en cada paso.

## Que se sincroniza

| Dato | Descripcion |
|------|-------------|
| **Contactos** | Nombre, email, numero de telefono. Usados para construir el grafo de identidad -- vinculando contactos del CRM con visitantes del sitio web. |
| **Oportunidades** | Negocios en tu pipeline, incluyendo etapa, valor y datos de atribucion UTM. GHL almacena cual campana publicitaria origino cada negocio. |
| **Eventos de calendario** | Citas agendadas, incluyendo el nombre del calendario, miembro del equipo asignado y contacto vinculado. |
| **Etapas del pipeline** | La configuracion de tu pipeline, mapeada a tipos de conversion de Atribu (ver abajo). |

## Conectar GoHighLevel

<Steps>
<Step>

### Abre Integraciones

En tu dashboard de Atribu, ve a **Configuracion > Integraciones**.

</Step>
<Step>

### Haz clic en Conectar GoHighLevel

Encuentra la tarjeta de GoHighLevel y haz clic en **Conectar**. Seras redirigido al Marketplace de GHL.

</Step>
<Step>

### Selecciona tu ubicacion

GHL usa "ubicaciones" (tambien llamadas subcuentas) para organizar diferentes negocios. Selecciona la ubicacion que deseas conectar a este perfil de Atribu.

</Step>
<Step>

### Autoriza los permisos

Revisa y aprueba los permisos que Atribu esta solicitando:

- **Contactos** (solo lectura) -- Acceso a tus registros de contactos
- **Oportunidades** (solo lectura) -- Acceso a tus negocios y datos del pipeline
- **Ubicaciones** (solo lectura) -- Acceso a los detalles de tu ubicacion
- **Calendarios** (solo lectura) -- Acceso a tus calendarios y eventos agendados

Haz clic en **Autorizar** para completar la conexion.

</Step>
<Step>

### La sincronizacion comienza automaticamente

Atribu comienza a importar tus datos de GHL de inmediato. Los contactos, oportunidades y eventos de calendario apareceran en tu dashboard en pocos minutos.

</Step>
</Steps>

## Mapeo de etapas del pipeline

Cuando las oportunidades de GHL se mueven a traves de las etapas de tu pipeline, Atribu mapea cada etapa a un **tipo de conversion**. Asi es como Atribu sabe si un negocio representa un nuevo lead, una cita agendada o un negocio cerrado.

<Callout type="info" title="Tipos de conversion explicados">
Atribu usa estos tipos de conversion estandar:

- **lead_created** -- Un nuevo lead ingreso a tu pipeline (por ejemplo, alguien lleno un formulario o fue agregado como contacto)
- **appointment_booked** -- Se agendo una reunion o consulta
- **qualified** -- El lead fue evaluado y cumple tus criterios
- **closed_won** -- El negocio fue ganado y el cliente se comprometio
- **payment_received** -- Se cobro el pago real (esto viene de Stripe/MercadoPago, no de GHL)

Las etapas de tu pipeline de GHL se vinculan a estos tipos automaticamente segun los nombres de las etapas. Puedes personalizar el mapeo en **Configuracion > Conversiones**.
</Callout>

## Entendiendo valor del pipeline vs. ingresos en efectivo

<Callout type="warn" title="Los valores del pipeline no son ingresos reales">
Los valores de negocios en GHL representan el **valor proyectado** de un negocio -- lo que esperas ganar si el negocio se cierra. Estos son utiles para pronosticos, pero no son lo mismo que dinero en tu cuenta bancaria.

Atribu los separa claramente:

- **Valor del pipeline** (de GHL) -- Se muestra en la seccion "Pipeline" de tu dashboard. Util para seguir el flujo de negocios y el rendimiento de ventas, pero nunca se incluye en el ROAS.
- **Ingresos en efectivo** (de Stripe/MercadoPago) -- Se muestra en la seccion "Ingresos" y se usa para todos los calculos de ROAS. Este es el pago real cobrado.

Un negocio de $10,000 en tu pipeline de GHL solo cuenta para el ROAS una vez que el cliente realmente pague $10,000 a traves de Stripe o MercadoPago.
</Callout>

## Integracion de calendario

Una vez que GHL esta conectado, la pagina de **Calendario** en Atribu muestra tus citas agendadas junto con datos de atribucion. Para cada cita, puedes ver:

- Cual campana publicitaria trajo al cliente
- El recorrido completo del cliente (paginas vistas, formularios enviados)
- Valor del negocio y etapa del pipeline

Atribu fusiona los datos en vivo del calendario de GHL con sus propios registros de atribucion, para que obtengas el estado de las citas en tiempo real combinado con la atribucion de marketing en una sola vista.

## GHL y formularios nativos de leads de Meta

Muchos leads que llegan a traves de formularios nativos de leads de Meta (Facebook e Instagram) van directamente a GHL sin que la persona visite tu sitio web. Estos leads tienen datos de campana de la atribucion de Meta (en cual anuncio hicieron clic, cual conjunto de anuncios, etc.) pero no tienen una visita al sitio web que rastrear.

Atribu maneja esto automaticamente creando **puntos de contacto sinteticos** -- registros de atribucion basados en los datos de campana que GHL transmite. Esto significa que incluso los leads de formularios de leads de Instagram que nunca visitaron tu sitio se atribuyen a la campana publicitaria correcta.

## Solucion de problemas

### Contactos faltantes

La API de contactos de GHL puede no devolver todos los contactos en una sola sincronizacion. Los contactos que fueron creados a traves de oportunidades (negocios) en lugar de envios directos de formularios pueden tardar un ciclo de sincronizacion adicional en aparecer. Si faltan contactos especificos despues de 24 horas, intenta activar una re-sincronizacion manual desde **Configuracion > Integraciones**.

### Las etapas del pipeline no se mapean correctamente

Ve a **Configuracion > Conversiones** para revisar y ajustar tus mapeos de etapa a conversion. Atribu vincula las etapas por palabra clave (por ejemplo, una etapa llamada "Appointment Set" se mapea a `appointment_booked`). Si los nombres de tus etapas usan etiquetas no estandar, es posible que necesites mapearlas manualmente.

### Contactos duplicados

Si la misma persona aparece bajo multiples registros de contacto en GHL (por ejemplo, con diferentes direcciones de email), el sistema de resolucion de identidad de Atribu intentara fusionarlos basandose en identificadores compartidos (email, telefono, ID de contacto). En algunos casos, puede ser necesaria la deduplicacion manual en GHL.

## Siguientes pasos

<Cards>
  <Card title="Instalar el Tracker" href="/docs/es/getting-started/install-tracker">
    Asegurate de que el script de seguimiento este en tu sitio para atribucion completa
  </Card>
  <Card title="Tipos de Ingresos" href="/docs/es/concepts/revenue-types">
    Entiende la diferencia entre ingresos en efectivo, pipeline y brutos
  </Card>
  <Card title="Modelos de Atribucion" href="/docs/es/concepts/attribution-models">
    Aprende como Atribu asigna credito a traves de los puntos de contacto
  </Card>
  <Card title="Detalles de la Integracion con GoHighLevel" href="/docs/es/integrations/gohighlevel">
    Mapeo de pipeline, fusion de calendario y configuracion especifica de GHL
  </Card>
  <Card title="Puntos de Contacto Sinteticos" href="/docs/es/concepts/synthetic-touches">
    Como se atribuyen los leads fuera del sitio de formularios de leads de Meta
  </Card>
</Cards>

---

Conectar tu proveedor de pagos es lo que desbloquea la metrica mas valiosa en Atribu: **atribucion de ingresos reales**. Una vez conectado, cada pago se vincula con la campana publicitaria, conjunto de anuncios y anuncio especifico que originalmente trajo al cliente a tu sitio. Esto te permite calcular el **ROAS** (Retorno sobre la Inversion Publicitaria) -- la cantidad de ingresos que generas por cada dolar gastado en anuncios.

## Que se rastrea

Cuando un pago llega a traves de Stripe o MercadoPago, Atribu crea un **evento de resultado** -- un registro de que algo valioso sucedio. El motor de atribucion entonces trabaja hacia atras a traves del recorrido del cliente para encontrar cuales puntos de contacto publicitarios llevaron a ese pago.

Para cada pago, Atribu captura:

| Dato | Descripcion |
|------|-------------|
| **Monto** | El monto del pago en su moneda original |
| **Moneda** | La moneda de la transaccion (convertida automaticamente para reportes) |
| **Email del cliente** | Usado para vincular el pago con un visitante conocido a traves del grafo de identidad |
| **Datos de facturacion** | Nombre, telefono y direccion cuando estan disponibles |
| **Marca de tiempo** | Cuando ocurrio el pago |

## Conecta tu proveedor

<Tabs items={["Stripe", "MercadoPago"]}>
<Tab value="Stripe">

<Steps>
<Step>

### Abre Integraciones

En tu dashboard de Atribu, ve a **Configuracion > Integraciones**.

</Step>
<Step>

### Haz clic en Conectar Stripe

Encuentra la tarjeta de Stripe y haz clic en **Conectar**. Seras redirigido a la pagina de autorizacion de Stripe.

</Step>
<Step>

### Autoriza en Stripe

Revisa los permisos y haz clic en **Conectar** o **Autorizar**. Si gestionas multiples cuentas de Stripe, selecciona la que deseas conectar.

</Step>
<Step>

### Listo

Eso es todo. Los webhooks de Stripe se configuran automaticamente -- no necesitas configurar ningun endpoint de webhook manualmente. Los pagos comenzaran a aparecer en Atribu tan pronto como se procesen.

</Step>
</Steps>

<Callout type="info" title="Configuracion automatica de webhooks">
Cuando conectas Stripe, Atribu registra automaticamente listeners de webhook para eventos de pago. Escucha eventos de `charge.succeeded`, `payment_intent.succeeded` y `checkout.session.completed`. No necesitas configurar nada en el panel de Stripe.
</Callout>

### Como funciona la atribucion de Stripe

Los pagos de Stripe tipicamente no contienen parametros UTM o datos de campanas publicitarias. En su lugar, Atribu usa el **grafo de identidad** para conectar pagos con clics en anuncios:

1. Un visitante hace clic en tu anuncio y llega a tu sitio. El tracker registra su ID anonimo y los detalles de la campana desde la URL.
2. El visitante llena un formulario (o completa una reserva). El tracker automaticamente llama a `identify()` con su email, vinculando su ID de navegador anonimo con un cliente conocido.
3. Mas tarde, cuando pagan via Stripe, el pago llega con el mismo email. Atribu lo vincula con el mismo perfil de cliente.
4. El motor de atribucion rastrea hacia atras a traves de los puntos de contacto del cliente para encontrar cual anuncio lo trajo.

Por esto es tan importante instalar el tracker y tener formularios en tu sitio -- crea el puente entre clics anonimos en anuncios y pagos reales.

</Tab>
<Tab value="MercadoPago">

<Steps>
<Step>

### Abre Integraciones

En tu dashboard de Atribu, ve a **Configuracion > Integraciones**.

</Step>
<Step>

### Haz clic en Conectar MercadoPago

Encuentra la tarjeta de MercadoPago y haz clic en **Conectar**. Seras redirigido a la pagina de autorizacion de MercadoPago.

</Step>
<Step>

### Autoriza el acceso

Inicia sesion en tu cuenta de MercadoPago y aprueba la conexion.

</Step>
<Step>

### Listo

Los pagos comenzaran a fluir a Atribu automaticamente. Al igual que Stripe, la configuracion de webhooks se maneja por ti.

</Step>
</Steps>

</Tab>
</Tabs>

## Tipos de ingresos

<Callout type="warn" title="Ingresos en efectivo vs. pipeline">
Atribu distingue entre diferentes tipos de ingresos. Solo los **ingresos en efectivo** (pagos reales de Stripe o MercadoPago) cuentan para tu calculo de ROAS. Esto es importante por lo siguiente:

- **Ingresos en efectivo** -- Dinero real recibido a traves de Stripe o MercadoPago. Este es el unico numero usado para el ROAS.
- **Valor del pipeline** -- Valores de oportunidades de tu CRM (GoHighLevel). Estas son proyecciones de cuanto podria valer una oportunidad, no pagos confirmados. Una "oportunidad de $5,000" en tu pipeline no significa que se hayan cobrado $5,000.

Si los valores del pipeline se mezclaran en el ROAS, una sola estimacion de oportunidad grande podria hacer que una campana de bajo rendimiento parezca rentable. Atribu los mantiene separados para que tu ROAS siempre refleje efectivo real cobrado.
</Callout>

## Payment Links

Atribu puede generar Stripe Payment Links con seguimiento de atribucion integrado. Estos enlaces llevan metadata que conecta el pago directamente con el cliente y la campana, proporcionando la senal de atribucion mas fuerte posible.

Puedes crear Payment Links desde el dashboard de Atribu en **Payment Links**. Cada enlace incluye metadata de seguimiento para que cuando un cliente complete el pago, Atribu pueda atribuirlo sin depender unicamente del grafo de identidad.

## Solucion de problemas

### Los pagos no aparecen

- **Verifica el estado de la conexion**: Ve a **Configuracion > Integraciones** y verifica que tu conexion de Stripe o MercadoPago muestre "Conectado".
- **Espera un momento**: La entrega de webhooks es casi instantanea, pero puede haber un breve retraso (menos de un minuto) antes de que el pago aparezca en tu dashboard.
- **Modo de prueba vs modo en vivo**: Asegurate de haber conectado tu cuenta de Stripe en vivo, no una cuenta en modo de prueba. Los pagos de prueba no apareceran.

### Los ingresos muestran cero pero los pagos estan llegando

- **Revisa las definiciones de conversion**: Ve a **Configuracion > Conversiones** y asegurate de que `payment_received` este configurado. Esto se configura por defecto, pero puede haber sido modificado.
- **Coincidencia de identidad**: Si el email del cliente en el pago no coincide con ningun visitante conocido, el pago se registrara pero puede no ser atribuido a una campana. Asegurate de que el tracker este instalado y los formularios esten siendo capturados.

## Siguientes pasos

<Cards>
  <Card title="Conectar GoHighLevel" href="/docs/es/getting-started/connect-crm">
    Sincroniza contactos, oportunidades y citas desde tu CRM
  </Card>
  <Card title="Modelos de Atribucion" href="/docs/es/concepts/attribution-models">
    Aprende como se asigna el credito a traves de los puntos de contacto
  </Card>
  <Card title="Tipos de Ingresos" href="/docs/es/concepts/revenue-types">
    Entiende por que solo los pagos en efectivo cuentan para el ROAS
  </Card>
  <Card title="Detalles de la Integracion con Stripe" href="/docs/es/integrations/stripe">
    Payment Links, configuracion de webhooks y detalles de atribucion especificos de Stripe
  </Card>
</Cards>

---

El script de seguimiento de Atribu es un fragmento ligero de JavaScript que se ejecuta en tu sitio web. Captura:

- **Vistas de pagina** -- cuales paginas ven los visitantes y cuanto tiempo permanecen
- **Identidad del visitante** -- asigna a cada dispositivo un ID anonimo unico para que puedas seguir el recorrido a traves de las visitas
- **Fuentes de trafico** -- lee los parametros UTM e IDs de clic (fbclid, gclid) de tus URLs de anuncios para saber que campana trajo a cada visitante
- **Envios de formularios** -- detecta automaticamente cuando alguien llena un formulario y extrae su email o numero de telefono
- **Reservas completadas** -- detecta widgets de reserva de GoHighLevel, Calendly y Cal.com

## Instalacion

Ve a **Configuracion > Seguimiento** en tu dashboard de Atribu. Veras tu fragmento de seguimiento personalizado con tu clave de seguimiento unica ya incluida. Copialo e instalalo usando uno de los metodos a continuacion.

<Tabs items={["HTML (copiar y pegar)", "Google Tag Manager", "Shopify"]}>
<Tab value="HTML (copiar y pegar)">

Pega el fragmento en la seccion `<head>` de cada pagina de tu sitio. Si usas un CMS como WordPress, Webflow o Squarespace, busca la configuracion de "Codigo Personalizado" o "Inyeccion de Codigo en Header".

```html title="Fragmento de seguimiento"
<script>
  window.ATRIBU_TRACKING_KEY = "your-tracking-key-here";
  window.ATRIBU_TRACKING_ENDPOINT = "https://www.atribu.app/api/tracking/collect";
  window.ATRIBU_INTERCEPT_FBQ = true;
  window.ATRIBU_META_BRIDGE_PAGEVIEW = false;
  window.atribuTracker = window.atribuTracker || function() {
    (window.atribuTracker.q = window.atribuTracker.q || []).push(arguments);
  };
</script>
<script src="https://www.atribu.app/atribu-tracker.js" defer></script>
```

<Callout type="info" title="Usa tu fragmento real">
El codigo anterior es una plantilla. Copia tu fragmento real desde **Configuracion > Seguimiento** -- ya tiene tu clave de seguimiento incluida.
</Callout>

El atributo `defer` asegura que el script se cargue sin ralentizar tu pagina. Los eventos que ocurren antes de que el script termine de cargar se ponen en cola y se envian una vez que esta listo.

</Tab>
<Tab value="Google Tag Manager">

<Steps>
<Step>

### Crea una nueva etiqueta

En Google Tag Manager, ve a **Etiquetas > Nueva** y selecciona **HTML Personalizado** como tipo de etiqueta.

</Step>
<Step>

### Pega el fragmento

Copia tu fragmento de seguimiento desde **Configuracion > Seguimiento** en Atribu y pegalo en el campo HTML.

```html title="Contenido de la etiqueta HTML personalizada"
<script>
  window.ATRIBU_TRACKING_KEY = "your-tracking-key-here";
  window.ATRIBU_TRACKING_ENDPOINT = "https://www.atribu.app/api/tracking/collect";
  window.ATRIBU_INTERCEPT_FBQ = true;
  window.ATRIBU_META_BRIDGE_PAGEVIEW = false;
  window.atribuTracker = window.atribuTracker || function() {
    (window.atribuTracker.q = window.atribuTracker.q || []).push(arguments);
  };
</script>
<script src="https://www.atribu.app/atribu-tracker.js" defer></script>
```

</Step>
<Step>

### Configura el activador

Establece el activador en **Todas las Paginas** para que el tracker se cargue en cada pagina de tu sitio.

</Step>
<Step>

### Publica

Haz clic en **Enviar** para publicar el contenedor. El tracker comenzara a recopilar datos inmediatamente.

</Step>
</Steps>

</Tab>
<Tab value="Shopify">

El sistema Web Pixel de Shopify proporciona un entorno aislado para scripts de terceros.

<Steps>
<Step>

### Ve a Eventos del Cliente

En tu panel de administracion de Shopify, navega a **Configuracion > Eventos del Cliente**.

</Step>
<Step>

### Agrega un pixel personalizado

Haz clic en **Agregar pixel personalizado** y ponle un nombre como "Atribu Tracker".

</Step>
<Step>

### Pega el fragmento

Copia tu fragmento de seguimiento desde **Configuracion > Seguimiento** en Atribu y pegalo en el editor de codigo.

</Step>
<Step>

### Guarda y conecta

Haz clic en **Guardar**, luego activa el pixel a **Conectado**.

</Step>
</Steps>

<Callout type="warn" title="Limitaciones de Shopify">
El Web Pixel de Shopify se ejecuta en un iframe aislado. La captura automatica de formularios y la deteccion de widgets de reservas pueden no funcionar dentro del sandbox. Para funcionalidad completa, considera agregar el tracker a traves del archivo `theme.liquid` del tema (pegalo antes de la etiqueta de cierre `</head>`).
</Callout>

</Tab>
</Tabs>

## Como funciona el tracker

<Callout type="info" title="IDs y cookies">
El tracker crea dos identificadores para cada visitante:

- **ID Anonimo** (cookie `atribu_visitor_id`) -- Un ID unico para cada dispositivo/navegador. Persiste por 1 ano. Asi es como Atribu cuenta visitantes unicos.
- **ID de Sesion** (cookie `atribu_session_id`) -- Un ID unico para cada visita. Se reinicia despues de 30 minutos de inactividad o cuando el visitante llega desde una nueva fuente de marketing (diferentes parametros UTM o ID de clic). Asi es como Atribu cuenta sesiones.

Ambos IDs se almacenan como cookies de primera parte para que el codigo del lado del servidor (como paginas de checkout) pueda leerlos y pasarlos a proveedores de pago para mejor atribucion.
</Callout>

### Funciones automaticas

El tracker captura automaticamente estos eventos sin ningun codigo adicional:

| Funcion | Que hace |
|---------|----------|
| Vistas de pagina | Rastrea cada pagina que el visitante ve, incluyendo navegacion en aplicaciones de pagina unica |
| Envios de formularios | Detecta envios de formularios, extrae email/telefono y vincula al visitante anonimo con un cliente conocido |
| Widgets de reservas | Detecta reservas completadas de GoHighLevel, Calendly y Cal.com |
| Redirecciones de checkout de Stripe | Detecta `?session_id=cs_*` en la URL despues de una redireccion de Payment Link de Stripe |
| Clics en enlaces externos | Rastrea clics a dominios externos |
| Descargas de archivos | Rastrea descargas (PDF, ZIP, etc.) |
| Engagement | Mide profundidad de scroll y tiempo en pagina |

### La llamada identify()

Lo mas importante que hace el tracker es **vincular visitantes anonimos con clientes conocidos**. Cuando alguien llena un formulario en tu sitio, el tracker automaticamente llama a `identify()` con su email o numero de telefono. Esto crea una conexion entre el visitante anonimo del navegador y el registro del cliente en tu CRM o proveedor de pagos.

Sin este vinculo, Atribu no puede atribuir un pago de Stripe al clic original en el anuncio. La captura automatica de formularios maneja esto automaticamente para formularios HTML estandar. Si usas un formulario personalizado (React, Vue, etc.), puedes llamar a `identify()` manualmente:

```js title="Llamada manual de identify"
window.atribuTracker.identify({
  email: "customer@example.com",
  phone: "+1234567890"
});
```

## Verifica que esta funcionando

Despues de instalar el tracker:

1. Visita tu sitio web en un navegador (no en modo incognito -- algunos bloqueadores de anuncios son mas agresivos ahi)
2. Abre tu dashboard de Atribu
3. Revisa el contador **En Linea Ahora** en la esquina superior derecha -- deberias ver al menos 1 visitante
4. Despues de unos minutos, las vistas de pagina y sesiones apareceran en el dashboard

<Callout type="warn" title="Bloqueadores de anuncios">
Algunos bloqueadores de anuncios del navegador pueden bloquear el script de seguimiento. Si tu o tu equipo usan bloqueadores de anuncios, es posible que no vean sus propias visitas. Para asegurar que el tracker funcione para todos los visitantes -- incluyendo aquellos con bloqueadores de anuncios -- configura un **dominio de seguimiento personalizado**. Esto enruta las solicitudes de seguimiento a traves de tu propio dominio en lugar de `atribu.app`, que los bloqueadores de anuncios no bloquean.

Ve a **Configuracion > Seguimiento > Dominio Personalizado** para configurarlo.
</Callout>

## Siguientes pasos

<Cards>
  <Card title="Conectar Plataformas de Anuncios" href="/docs/es/getting-started/connect-ads">
    Vincula Meta y Google Ads para ver cuales campanas generan trafico
  </Card>
  <Card title="Conectar Pagos" href="/docs/es/getting-started/connect-payments">
    Vincula Stripe o MercadoPago para seguir ingresos
  </Card>
  <Card title="Como Funciona el Seguimiento" href="/docs/es/tracking/how-tracking-works">
    Profundiza en visitantes, sesiones, cookies y como el tracker captura datos
  </Card>
  <Card title="Resolucion de Identidad" href="/docs/es/concepts/identity-resolution">
    Como los visitantes anonimos se convierten en clientes conocidos a traves de dispositivos y canales
  </Card>
</Cards>

---

Atribu es una plataforma de atribucion de marketing que conecta tus plataformas de anuncios (Meta, Google Ads), CRMs (GoHighLevel) y proveedores de pago (Stripe, MercadoPago) en un solo lugar. En lugar de adivinar cuales campanas estan funcionando, ves exactamente cuales anuncios llevaron a pagos reales -- para que puedas dejar de desperdiciar presupuesto y duplicar lo que funciona.

<VideoEmbed src="/docs/videos/getting-started.mp4" title="Comienza con Atribu en 5 pasos" />

## Resumen de configuracion

Comenzar toma aproximadamente 15 minutos. Esto es lo que haras:

<Steps>
<Step>

### Crea tu cuenta

Registrate en [atribu.app](https://www.atribu.app) y crea tu primer workspace. Un workspace es tu contenedor principal -- la mayoria de los negocios solo necesitan uno. Dentro de cada workspace, creas un **perfil** para cada marca o cliente que gestionas.

</Step>
<Step>

### Instala el script de seguimiento

Agrega un pequeno fragmento de JavaScript a tu sitio web. Asi es como Atribu ve quien visita tu sitio, que paginas ven y de donde vinieron (Google, Instagram, un anuncio especifico, etc.). Funciona como Google Analytics pero esta disenado especificamente para atribucion.

[Instalar el tracker](/docs/es/getting-started/install-tracker)

</Step>
<Step>

### Conecta tus plataformas de anuncios

Vincula tus cuentas de Meta (Facebook e Instagram) o Google Ads. Atribu importa tus campanas, conjuntos de anuncios, anuncios individuales y datos de gasto diario automaticamente. Esto es lo que hace posible calcular tu retorno sobre la inversion publicitaria (ROAS) -- la relacion entre los ingresos generados y el dinero gastado en anuncios.

[Conectar plataformas de anuncios](/docs/es/getting-started/connect-ads)

</Step>
<Step>

### Conecta tu proveedor de pagos

Vincula Stripe o MercadoPago para que Atribu sepa cuando entra dinero real. Cada pago se vincula con el anuncio que originalmente trajo al cliente a tu sitio. Esta es la diferencia clave entre Atribu y las herramientas de analitica basicas: ves ingresos reales, no solo clics.

[Conectar pagos](/docs/es/getting-started/connect-payments)

</Step>
<Step>

### Conecta tu CRM (opcional)

Si usas GoHighLevel, conectalo para importar contactos, oportunidades, citas y etapas del pipeline. Esto te permite seguir todo el recorrido desde el clic en el anuncio hasta la cita agendada y el cierre del negocio.

[Conectar GoHighLevel](/docs/es/getting-started/connect-crm)

</Step>
</Steps>

## Que sucede despues

Una vez que tus integraciones estan conectadas y el tracker esta instalado, los datos comienzan a fluir a tu dashboard en minutos. Veras:

- **Visitantes y sesiones** desde tu script de seguimiento
- **Gasto publicitario y rendimiento** desde tus plataformas de anuncios conectadas
- **Ingresos y ROAS** una vez que los pagos comiencen a llegar
- **Recorridos completos del cliente** mostrando cada punto de contacto desde el primer clic en un anuncio hasta el pago

Cuantas mas fuentes de datos conectes, mas completa sera tu imagen de atribucion.

Para entender como Atribu asigna credito a los diferentes puntos de contacto, consulta [Modelos de Atribucion](/docs/es/concepts/attribution-models). Para detalles sobre como los visitantes anonimos se convierten en clientes conocidos, consulta [Resolucion de Identidad](/docs/es/concepts/identity-resolution).

## Siguientes pasos

<Cards>
  <Card title="Instalar el Tracker" href="/docs/es/getting-started/install-tracker">
    Agrega el script de seguimiento a tu sitio web en menos de 5 minutos
  </Card>
  <Card title="Conectar Plataformas de Anuncios" href="/docs/es/getting-started/connect-ads">
    Vincula Meta y Google Ads para importar campanas y gasto
  </Card>
  <Card title="Conectar Pagos" href="/docs/es/getting-started/connect-payments">
    Vincula Stripe o MercadoPago para seguir ingresos reales
  </Card>
  <Card title="Conectar GoHighLevel" href="/docs/es/getting-started/connect-crm">
    Sincroniza contactos, oportunidades y citas desde tu CRM
  </Card>
</Cards>

---

# Lista de Permitidos de Exportación

En Modo Salud, Atribu usa una **lista de permitidos** para controlar qué eventos de conversión se envían a plataformas publicitarias. Si un tipo de evento no está en la lista, se bloquea.

## Por qué lista de permitidos y no de bloqueo

Una lista de bloqueo dice "enviar todo excepto esto." Una lista de permitidos dice "no enviar nada excepto esto." Para datos de salud, la lista de permitidos es el único enfoque seguro porque:

- Los nuevos tipos de evento se bloquean por defecto hasta ser revisados
- No puedes exponer PHI accidentalmente por olvidar agregar algo a una lista de bloqueo
- El modelo mental es simple: si no lo marcaste, no sale

<Callout type="warn" title="Los campos de lista de bloqueo se ocultan en modo HIPAA">
Cuando el Modo Salud está activo, los campos de lista de bloqueo se ocultan completamente de la interfaz. Son mutuamente excluyentes con la lista de permitidos.
</Callout>

## Lista de permitidos predeterminada

| Tipo de Evento | Por Qué Es Seguro |
|---------------|-------------------|
| **Pago Recibido** | Datos de ingresos con identificadores hasheados. Sin información de condiciones de salud. |
| **Lead Creado** | Envío de contacto con email/teléfono hasheado. Sin contexto de diagnóstico o tratamiento. |

Todo lo demás — incluyendo citas agendadas, deals cerrados, checkout iniciado — está **bloqueado por defecto**.

## Configurar la lista

1. Ve a **Conversion Sync > Privacidad y Cumplimiento**
2. En Modo Salud, los checkboxes de la lista de permitidos aparecen
3. Marca los tipos de evento que deseas permitir
4. Desmarca los que deseas bloquear
5. Haz clic en **Guardar**

## Qué pasa con los eventos bloqueados

Los eventos bloqueados:
- **Se almacenan internamente** — atribución, dashboards y reportes no se afectan
- **Se registran en el ledger** — con estado `omitido` y razón `healthcare_allowlist_blocked`
- **Son visibles en el feed de Conversion Sync**
- **No se envían a ninguna plataforma publicitaria**

## Transiciones de pipeline

Las transiciones de etapa de pipeline están **siempre bloqueadas** en Modo Salud, sin importar la lista de permitidos. Pueden contener nombres de pipeline o etapas que revelen condiciones de salud.

## Guía de revisión por tipo de evento

| Pregunta | Si la respuesta es sí... |
|----------|-------------------------|
| ¿Podría el nombre o payload revelar una condición de salud? | No agregar a la lista |
| ¿Incluye campos de texto libre (notas, descripciones)? | No agregar |
| ¿Incluye tipo de cita o nombre de servicio? | No agregar |
| ¿Es puramente financiero (monto + moneda)? | Generalmente seguro |
| ¿Es un envío de formulario simple (solo email/teléfono)? | Generalmente seguro — PII se hashea |

---

# Respuesta a Incidentes

Este documento cubre qué hacer si Información de Salud Protegida (PHI) es enviada a una plataforma publicitaria o expuesta fuera del alcance previsto.

## Niveles de severidad

| Nivel | Descripción | Tiempo de Respuesta |
|-------|-------------|---------------------|
| P1 — Brecha confirmada | PHI confirmado enviado a Meta/Google | Dentro de 1 hora |
| P2 — Brecha sospechada | Posible exposición de PHI, no confirmada | Dentro de 4 horas |
| P3 — Violación de política | Cuenta de salud mal configurada, sin PHI confirmado | Dentro de 24 horas |

## Contención inmediata (P1/P2)

<Steps>

<Step>
### Detener todas las exportaciones (dentro de 15 minutos)

Deshabilitar exportaciones para el perfil afectado:
- Ir a **Configuración > Integraciones** y desconectar destinos
- O ir a **Conversion Sync > Destinos** y deshabilitar todas las configuraciones
- Si no está activo, cambiar a modo HIPAA
</Step>

<Step>
### Evaluar alcance (dentro de 1 hora)

Consultar el ledger de conversion_exports para determinar:
- Cuántos eventos se enviaron durante la ventana de exposición
- Qué datos contenían los payloads
- Cuántos individuos fueron afectados
- Qué categorías de PHI estuvieron involucradas
</Step>

<Step>
### Documentar hallazgos

Registrar: línea de tiempo, perfiles afectados, número de eventos, categorías de PHI, destinos que recibieron datos.
</Step>

</Steps>

## Reglas de notificación de brechas HIPAA

Bajo la Regla de Notificación de Brechas HIPAA (45 CFR 164.400-414):

### 500+ individuos afectados

| Notificar a | Plazo |
|-------------|-------|
| HHS | Dentro de 60 días |
| Fiscal general estatal | Dentro de 60 días |
| Individuos afectados | Dentro de 60 días |

### Menos de 500 individuos

| Notificar a | Plazo |
|-------------|-------|
| HHS | Registro anual antes del 1 de marzo del año siguiente |
| Individuos afectados | Dentro de 60 días |

## Obligaciones de Atribu como Asociado Comercial

Bajo el BAA (Anexo F, Sección F-6), Atribu debe:
1. Reportar la brecha al Cliente (Entidad Cubierta) sin demora irrazonable
2. Proporcionar: naturaleza del incidente, categorías de datos, consecuencias probables, pasos de mitigación
3. El Cliente es responsable de las notificaciones posteriores a HHS e individuos

<Callout type="error" title="Atribu reporta al cliente, no directamente a HHS">
Como Asociado Comercial, la obligación de notificación de Atribu es hacia la Entidad Cubierta (tu cliente). El cliente determina si notificar a HHS, autoridades estatales e individuos afectados.
</Callout>

## Revisión post-incidente

1. **Análisis de causa raíz** — ¿Por qué estaba el perfil en modo estándar? ¿Se deshabilitó el Modo Salud accidentalmente?
2. **Medidas preventivas** — ¿Debería el workspace estar en Modo Agencia de Salud? ¿Hay otros perfiles que necesitan migración?
3. **Mejoras al sistema** — Actualizar lógica de detección, revisar lista de permitidos predeterminada
4. **Documentación** — Registrar el incidente en audit_log_events, actualizar notas de cumplimiento legal

---

# Migración al Modo Salud

Esta guía cubre cómo cambiar un perfil existente de modo estándar a modo HIPAA, incluyendo qué cambia, qué no, y cómo revertir si es necesario.

## Lista de verificación pre-migración

- [ ] Listar todas las reglas de señales activas y sus tipos de evento
- [ ] Identificar reglas que envían eventos no monetarios
- [ ] Informar al cliente que algunas exportaciones se bloquearán
- [ ] Confirmar que el equipo legal del cliente conoce el DPA/BAA
- [ ] Registrar el volumen actual de exportaciones

## Pasos de migración

<Steps>

<Step>
### Auditar el estado actual

Anotar: modo de privacidad, reglas activas, volumen de exportaciones, destinos activos.
</Step>

<Step>
### Aceptar el DPA/BAA

Navegar a **Conversion Sync > Privacidad y Cumplimiento**, cambiar a HIPAA. El modal de aceptación aparece. Desplazarse y aceptar.
</Step>

<Step>
### Configurar la lista de permitidos

Revisar cada definición de conversión. Solo marcar los tipos que no contengan PHI.

| Tipo de Evento | Recomendación |
|---------------|---------------|
| Pago Recibido | Seguro |
| Lead Creado | Generalmente seguro |
| Cita Agendada | Revisar cuidadosamente |
| Deal Cerrado | Revisar cuidadosamente |
</Step>

<Step>
### Verificar exportaciones

Esperar nuevos eventos y verificar en Conversion Sync > Entregas que los tipos permitidos se envíen y los bloqueados se omitan.
</Step>

<Step>
### Comunicar al cliente

Informar qué tipos de evento se exportan vs. bloquean, que la atribución interna no se afecta, y que el dashboard muestra "HIPAA Activo".
</Step>

</Steps>

## Impacto en datos existentes

| Qué | Impacto |
|-----|---------|
| Eventos históricos | Sin cambio |
| Exportaciones históricas | Sin cambio |
| Reglas activas | Sujetas a la lista de permitidos |
| Transiciones de pipeline | Bloqueadas de exportación |
| Datos de atribución | Sin cambio |
| Métricas del dashboard | Sin cambio |

## Reversión

1. Ir a **Conversion Sync > Privacidad y Cumplimiento**
2. Cambiar modo de privacidad de HIPAA a Estándar
3. Los campos de lista de bloqueo reaparecen
4. La compuerta legal ya no se aplica

<Callout type="warn" title="Revertir elimina todas las protecciones HIPAA">
Solo revertir si el perfil ya no maneja datos de salud. El registro de aceptación DPA/BAA se preserva para auditoría.
</Callout>

## Migración masiva para agencias

1. Habilitar **Modo Agencia de Salud** en Configuración del Workspace > Cumplimiento
2. No cambia perfiles existentes automáticamente
3. Usar la tabla de cumplimiento para rastrear progreso
4. Los nuevos perfiles creados después usan HIPAA por defecto

---

# Onboarding de Salud

Esta guía te lleva paso a paso por habilitar el Modo Salud, aceptar los acuerdos legales, configurar la lista de permitidos y verificar tu configuración.

## Prerrequisitos

- Tienes un workspace de Atribu con al menos un perfil
- Has conectado al menos una plataforma publicitaria (Meta o Google Ads)
- Tienes acceso de propietario o administrador al workspace

## Activación

<Steps>

<Step>
### Habilitar Modo Salud

**Opción A — Vía Conversion Sync (recomendado):**
Navega a la página de Conversion Sync del perfil. Si está en modo estándar, aparece una tarjeta preguntando "¿Manejas datos de salud?" Haz clic en **Sí, habilitar Modo Salud**.

**Opción B — Vía Configuración:**
Ve a Configuración > Cumplimiento y activa **Habilitar Modo Salud**.
</Step>

<Step>
### Aceptar el DPA y BAA

El modal muestra el texto completo del Acuerdo de Procesamiento de Datos y el Acuerdo de Asociado Comercial HIPAA. Desplázate por todo el texto y haz clic en **Acepto**.
</Step>

<Step>
### Configurar la lista de permitidos

El panel de Privacidad muestra checkboxes de lista de permitidos. Por defecto solo **Pago Recibido** y **Lead Creado** están marcados. Revisa cada tipo de evento y solo marca los que no contienen PHI.
</Step>

<Step>
### Crear reglas de señales

Crea reglas normalmente. El asistente mostrará advertencias para tipos de eventos no monetarios y bloqueará transiciones de pipeline.
</Step>

<Step>
### Verificar con un envío de prueba

Usa la función de envío de prueba en Conversion Sync > Entregas para confirmar que los eventos llegan correctamente y los filtros de privacidad están aplicados.
</Step>

</Steps>

## Flujo para agencias

1. Ve a **Configuración del Workspace > Cumplimiento**
2. Habilita **Modo Agencia de Salud** — los nuevos perfiles usarán HIPAA por defecto
3. La tabla de cumplimiento muestra el estado de todos los perfiles
4. Cada perfil debe aceptar individualmente el DPA/BAA

<Callout type="info" title="Los perfiles existentes no se cambian automáticamente">
Habilitar el Modo Agencia de Salud solo afecta perfiles nuevos. Los existentes deben migrarse individualmente. Ver la [guía de migración](/docs/healthcare/migration).
</Callout>

---

# Modo Salud

El Modo Salud de Atribu permite atribución compatible con HIPAA para negocios dentales, médicos, de salud mental y otros negocios de salud que hacen publicidad online. Protege la Información de Salud Protegida (PHI) mientras te permite medir qué anuncios generan ingresos reales.

## Por qué existe el Modo Salud

Meta y Google bloquean cuentas publicitarias de salud que envían datos relacionados con salud a través de sus APIs de conversión. El Modo Salud asegura que solo señales de conversión seguras y depuradas lleguen a las plataformas publicitarias, mientras Atribu sigue rastreando el recorrido completo del cliente internamente para tus reportes de atribución.

## Cómo funciona

El Modo Salud cambia el comportamiento de exportación de **lista de bloqueo** (enviar todo, bloquear cosas específicas) a **lista de permitidos** (bloquear todo, enviar solo lo que apruebes explícitamente).

| Capa | Modo Estándar | Modo Salud |
|------|--------------|------------|
| Almacenamiento interno | Todos los eventos | Todos los eventos (sin cambio) |
| Política de exportación | Lista de bloqueo | Lista de permitidos |
| Eliminación de PII | Configurable por campo | Todos los campos eliminados |
| Meta LDU | Opcional | Siempre habilitado |
| Manejo de URL | URL completa | Solo origen |
| Transiciones de etapa | Exportables | Bloqueadas |
| Compuerta legal | Opcional | Requerida — BAA debe estar firmado |

## Stack legal

El Modo Salud incluye un stack legal completo:

- **Acuerdo de Procesamiento de Datos (DPA)** — gobierna cómo Atribu procesa datos personales. [Leer el DPA completo](/dpa).
- **Acuerdo de Asociado Comercial HIPAA (Anexo F)** — el anexo BAA que aplica cuando manejas PHI. [Leer Anexo F](/dpa/baa).
- **Aceptación click-wrap** — acepta ambos documentos electrónicamente al habilitar el Modo Salud.

## Quién debería usar el Modo Salud

Habilita el Modo Salud si tu negocio (o el cliente de tu agencia):

- Es un proveedor de salud (dental, médico, quiropráctico, salud mental, etc.)
- Es un plan de salud o entidad de seguros
- Maneja Información de Salud Protegida en cualquier forma
- Ejecuta anuncios donde los datos de conversión podrían revelar condiciones de salud

<Callout type="warn" title="En caso de duda, habilítalo">
La única desventaja del Modo Salud es una menor densidad de señales de optimización. La ventaja es evitar una violación de HIPAA o el bloqueo de tu cuenta de Meta.
</Callout>

## Siguiente

- [Configurar el Modo Salud](/docs/healthcare/onboarding) — guía de activación paso a paso
- [Configurar la lista de permitidos](/docs/healthcare/export-allowlist) — elegir qué eventos llegan a plataformas
- [Migrar cuentas existentes](/docs/healthcare/migration) — cambiar de modo estándar a HIPAA

---

# Guía de Soporte para Cuentas de Salud

## "¿Por qué mi exportación está bloqueada?"

<Steps>

<Step>
### Revisar el feed de Conversion Sync

Ve a **Conversion Sync > Entregas** y encuentra el evento bloqueado. La columna de estado muestra `omitido` y el panel de detalle muestra la razón.
</Step>

<Step>
### Leer la razón del bloqueo

| Razón | Significado | Solución |
|-------|-------------|----------|
| `healthcare_allowlist_blocked` | Tipo de evento no está en la lista de permitidos | Agregarlo en Privacidad y Cumplimiento |
| `healthcare_stage_transition_blocked` | Transiciones de pipeline bloqueadas en modo HIPAA | Por diseño — usar eventos de conversión |
| `healthcare_missing_conversion_key` | Regla sin definición de conversión válida | Recrear la regla con una fuente de definición correcta |
| `legal_gate_blocked` | Requisitos legales no cumplidos | Completar configuración: BAA Aprobado + HIPAA Elegible |
| `legal_compliance_missing` | No existe registro legal | Aceptar el DPA/BAA |
</Step>

<Step>
### Verificar la configuración

Confirmar:
1. Modo de privacidad es `hipaa`
2. Panel legal muestra BAA Aprobado, HIPAA Elegible
3. Lista de permitidos tiene los tipos de evento esperados marcados
</Step>

</Steps>

## Verificar filtros de privacidad

1. Ve a **Conversion Sync > Entregas** y haz clic en un evento enviado
2. Verifica en el panel de detalle:
   - `filters_applied` incluye: `ip_removed`, `user_agent_removed`, `url_sanitized`
   - `privacy_mode_snapshot.mode` es `"hipaa"`
   - `payload_redacted` muestra `{ redacted: true, hipaa_mode: true }`

## Qué se elimina en modo HIPAA

| Campo | ¿Eliminado? |
|-------|-------------|
| IP del cliente | Sí |
| User agent | Sí |
| Referrer HTTP | Sí |
| Título de página | Sí |
| IDs externos (fbclid, gclid, fbc, fbp) | Sí |
| Ruta de URL | Sí (solo origen) |
| Email hasheado (Meta) | Se envía (para matching) |
| Teléfono hasheado (Meta) | Se envía (para matching) |
| Flags LDU de Meta | Inyectados automáticamente |

## Matriz de escalamiento

| Severidad | Disparador | Respuesta |
|-----------|-----------|-----------|
| P1 | PHI confirmado enviado a Meta/Google | Deshabilitar exportaciones. Ver [respuesta a incidentes](/docs/healthcare/incident-response) |
| P2 | Cuenta de salud en modo estándar con exportaciones activas | Cambiar a HIPAA. Auditar exportaciones |
| P3 | Configuración legal incompleta | Guiar por [onboarding](/docs/healthcare/onboarding) |
| P4 | Pregunta sobre lista de permitidos | Explicar la [lista de permitidos](/docs/healthcare/export-allowlist) |

---

Atribu conecta tus plataformas de anuncios, CRM y proveedores de pago para mostrarte exactamente qué anuncios generan ingresos. Deja de adivinar qué campañas funcionan — ve el panorama completo desde el clic del anuncio hasta el pago.

<VideoEmbed src="/docs/videos/what-is-atribu.mp4" title="Qué es Atribu — resumen de 30 segundos" />

## Primeros pasos

<Cards>
  <Card title="Comenzar en 5 pasos" href="/docs/es/getting-started/overview">
    Crea tu cuenta, conecta tus fuentes de datos, instala el tracker y ve tus primeros datos de atribución
  </Card>
  <Card title="Instalar tracking" href="/docs/es/getting-started/install-tracker">
    Agrega el script de tracking de Atribu a tu sitio web en menos de 2 minutos
  </Card>
  <Card title="Conectar plataformas de anuncios" href="/docs/es/getting-started/connect-ads">
    Conecta Meta (Facebook/Instagram) y Google Ads para sincronizar datos de campañas
  </Card>
  <Card title="Conectar pagos" href="/docs/es/getting-started/connect-payments">
    Conecta Stripe o MercadoPago para rastrear la atribución de ingresos
  </Card>
</Cards>

## Funcionalidades

<Cards>
  <Card title="Dashboard" href="/docs/es/features/dashboard">
    Tu centro de control de marketing — KPIs, gráficos, fuentes de tráfico, mejores campañas
  </Card>
  <Card title="Rendimiento de Anuncios" href="/docs/es/features/ads-performance">
    Clasifica campañas por ROAS, profundiza en métricas a nivel de anuncio (CTR, CPC, CPM, CAC)
  </Card>
  <Card title="Recorrido del Cliente" href="/docs/es/features/customer-journey">
    Ve la ruta completa desde el clic del anuncio hasta el pago para cada cliente
  </Card>
  <Card title="Explorador de Anuncios" href="/docs/es/features/ads-explorer">
    Espía los anuncios de la competencia — ve qué están publicando y por cuánto tiempo
  </Card>
  <Card title="Laboratorio de Anuncios" href="/docs/es/features/ads-lab">
    Generador creativo con IA — crea copys y scripts a partir de tus anuncios ganadores
  </Card>
  <Card title="Calendario" href="/docs/es/features/calendar">
    Ve citas y conversiones en una línea de tiempo unificada
  </Card>
</Cards>

## Conceptos

<Cards>
  <Card title="Modelos de Atribución" href="/docs/es/concepts/attribution-models">
    8 modelos para asignar crédito — last touch, first touch, linear, y más
  </Card>
  <Card title="Tipos de Ingresos" href="/docs/es/concepts/revenue-types">
    Cash vs pipeline vs gross — por qué solo los pagos confirmados cuentan para el ROAS
  </Card>
  <Card title="Resolución de Identidad" href="/docs/es/concepts/identity-resolution">
    Cómo los visitantes anónimos se convierten en clientes conocidos
  </Card>
</Cards>

## API

<Cards>
  <Card title="Quickstart de la API" href="/docs/api/quickstart">
    Haz tu primera llamada a la API en 2 minutos
  </Card>
  <Card title="Referencia de la API" href="/docs/api/overview">
    Documentación completa de endpoints — analytics, campañas, conversiones, clientes
  </Card>
</Cards>

## Para agentes de IA

| Recurso | Descripción |
|---------|-------------|
| [`/llms.txt`](/llms.txt) | Índice estructurado en markdown de toda la documentación |
| [`/llms-full.txt`](/llms-full.txt) | Documentación completa en un solo archivo markdown |
| [`/api/v1/openapi.json`](/api/v1/openapi.json) | Especificación OpenAPI 3.1 |

---

# GoHighLevel (GHL)

La integración con GoHighLevel trae los datos de tu CRM a Atribu. Sincroniza contactos, oportunidades (negocios) y citas del calendario para que puedas rastrear cada paso de tu embudo de ventas -- desde el clic en el anuncio hasta el cierre del negocio.

---

## Qué obtienes

- Contactos sincronizados con el grafo de identidad de Atribu (nombre, correo, teléfono)
- Oportunidades con valores de negocio, etapas del pipeline y datos de atribución UTM
- Citas del calendario integradas en la vista de Calendario de Atribu
- Seguimiento de embudo completo desde el primer clic en el anuncio hasta el cierre del negocio

---

## Conectar GoHighLevel

<Steps>
<Step>

## Abrir Integraciones

Ve a **Configuración > Integraciones** en tu espacio de trabajo de Atribu.

</Step>
<Step>

## Iniciar la conexión

Haz clic en **Conectar** junto a la tarjeta de GoHighLevel. Serás redirigido al Marketplace de GHL.

</Step>
<Step>

## Seleccionar tu ubicación

Elige la ubicación de GHL (subcuenta) que deseas conectar. Cada ubicación se conecta a un perfil de Atribu.

</Step>
<Step>

## Autorizar permisos

Otorga a Atribu los permisos solicitados:

- **Contacts** -- leer datos de contacto (nombre, correo, teléfono)
- **Opportunities** -- leer datos de negocios (etapa, valor, atribución)
- **Locations** -- identificar tu subcuenta
- **Calendars & Events** -- leer datos de citas

</Step>
<Step>

## Verificar la conexión

De vuelta en Atribu, la tarjeta de GHL debería mostrar **Conectado**. Atribu comenzará a importar tus contactos y oportunidades.

</Step>
</Steps>

---

## Qué se sincroniza

### Contactos

Cada contacto de GHL se vincula al grafo de identidad de Atribu usando correo electrónico y número de teléfono. Esto crea un perfil de cliente unificado que conecta al contacto con sus visitas al sitio web, clics en anuncios y pagos.

| Dato | Descripción |
|------|-------------|
| **Nombre** | Nombre y apellido |
| **Correo** | Dirección de correo principal |
| **Teléfono** | Número de teléfono principal |
| **Origen** | De dónde provino el contacto (formulario, anuncio, referido) |

### Oportunidades (negocios)

Las oportunidades de GHL contienen datos de atribución enriquecidos que Atribu lee para conectar los negocios con las campañas publicitarias.

| Dato | Descripción |
|------|-------------|
| **Etapa del pipeline** | Etapa actual (ej., Nuevo Lead, Calificado, Cerrado Ganado) |
| **Valor del negocio** | Valor monetario de la oportunidad |
| **Contacto** | Contacto vinculado con datos de identidad |
| **Datos UTM** | Campaña, fuente, medio y click IDs del origen del lead |
| **Historial de etapas** | Línea de tiempo de cambios de etapa |

### Eventos del calendario

Las citas de GHL se sincronizan y muestran en la vista de Calendario de Atribu, integradas con los eventos rastreados de tu sitio web.

| Dato | Descripción |
|------|-------------|
| **Hora de la cita** | Hora de inicio y fin |
| **Contacto** | Información del contacto vinculado |
| **Usuario asignado** | Miembro del equipo responsable |
| **Nombre del calendario** | A qué calendario pertenece la cita |
| **Estado** | Confirmada, cancelada, no asistió, etc. |

---

## Mapeo de etapas del pipeline

GHL organiza los negocios en pipelines con etapas. Por ejemplo:

```txt title="Ejemplo de pipeline de ventas"
New Lead  -->  Qualified  -->  Proposal Sent  -->  Closed Won
```

Atribu mapea cada etapa a un tipo de conversión para poder rastrear tu embudo:

| Palabra clave de la etapa | Tipo de conversión |
|---------------------------|-------------------|
| Contiene "lead" o es la primera etapa | `lead_created` |
| Contiene "appointment" o "booked" | `appointment_booked` |
| Contiene "won" o "closed" | `closed_won` |

Puedes personalizar este mapeo en **Configuración > Resultados** para que coincida con las etapas específicas de tu pipeline.

---

## Valores del pipeline vs ingresos reales

<Callout type="warn" title="Los valores del pipeline NO se usan para el ROAS">
Los valores de negocio de GHL representan lo que **esperas** ganar -- no lo que realmente has cobrado. Un negocio de $5,000 en tu pipeline podría cerrarse en $3,000, renegociarse o caerse por completo.

Atribu rastrea los valores del pipeline por separado como **ingresos de pipeline** y los muestra en su propia sección del dashboard. **Nunca** se incluyen en los cálculos de ROAS. Solo los pagos confirmados de Stripe o MercadoPago cuentan para el ROAS.
</Callout>

---

## Integración con el calendario

Las citas de GHL aparecen en la vista de Calendario de Atribu junto con los eventos rastreados de tu sitio web. Atribu fusiona datos de ambas fuentes:

- **Datos de GHL**: estado de la cita en tiempo real, nombre del calendario, miembro del equipo asignado
- **Datos de Atribu**: información de atribución (qué campaña publicitaria trajo a este cliente)

Cuando ambas fuentes tienen datos para la misma cita, se fusionan en una sola entrada con lo mejor de ambos mundos.

---

## Atribución UTM desde GHL

Cuando un lead proviene de un anuncio de Meta o Google, GHL captura la información de la campaña del clic. Atribu lee estos datos para atribuir el lead a la campaña, conjunto de anuncios y anuncio correctos.

Esto significa que incluso si un lead llena un formulario directamente en Facebook (usando un Formulario de Lead de Meta) y va directo a GHL sin visitar tu sitio web, Atribu aún puede identificar qué campaña publicitaria generó el lead.

<Callout type="info" title="Toques sintéticos para leads fuera del sitio">
Cuando un lead entra a GHL desde un formulario de lead de Meta (no desde tu sitio web), no hay visita al sitio web que rastrear. Atribu crea automáticamente **toques sintéticos** -- puntos de contacto virtuales que representan la interacción con el anuncio. Esto asegura que estos leads se incluyan en tus reportes de atribución, incluso sin una sesión en el sitio web.

Aprende más en [Toques Sintéticos](/docs/es/concepts/synthetic-touches).
</Callout>

---

## Solución de problemas

### Los contactos no tienen nombre

Algunos contactos de GHL se crean a través de oportunidades en lugar de la lista de contactos. En estos casos, el nombre puede no estar disponible durante la sincronización inicial. Atribu completará los nombres a medida que haya más datos disponibles en sincronizaciones posteriores.

### Las oportunidades no se sincronizan

- Verifica que el estado de la conexión muestre **Conectado** en **Configuración > Integraciones**
- Asegúrate de que la ubicación de GHL tenga al menos un pipeline con oportunidades
- Si agregaste un nuevo pipeline recientemente, activa una sincronización manual

### Las citas no aparecen en el Calendario

La sincronización del calendario requiere los permisos de **Calendars** y **Calendar Events**. Si estos no fueron otorgados durante la configuración:

1. Ve a **Configuración > Integraciones**
2. Desconecta GoHighLevel
3. Reconecta y asegúrate de que todos los permisos estén otorgados

---

## Relacionado

<Cards>
  <Card title="Tipos de Ingresos" href="/docs/es/concepts/revenue-types">
    Por qué los valores del pipeline se rastrean por separado de los ingresos en efectivo
  </Card>
  <Card title="Calendario" href="/docs/es/features/calendar">
    Ve las citas de GHL con datos de atribución en una línea de tiempo visual
  </Card>
  <Card title="Objetivos de Conversión" href="/docs/es/settings/goals">
    Personaliza cómo las etapas del pipeline se mapean a tipos de conversión
  </Card>
</Cards>

---

# Google Ads

La integración con Google Ads conecta tus campañas de búsqueda, display y video a Atribu. Sincroniza toda tu jerarquía de campañas con datos de rendimiento diarios y te permite exportar eventos de conversión de vuelta a Google para la optimización de pujas.

---

## Qué obtienes

- Estructura completa de campañas: campañas, grupos de anuncios y anuncios individuales
- Gasto diario, impresiones y clics para cada anuncio
- Clasificación automática de tráfico -- las visitas desde Google Ads se etiquetan como **Paid Search**
- Exportación de conversiones para que Google pueda optimizar tus pujas hacia resultados reales de negocio

---

## Conectar Google Ads

<Steps>
<Step>

## Abrir Integraciones

Ve a **Configuración > Integraciones** en tu espacio de trabajo de Atribu.

</Step>
<Step>

## Iniciar la conexión

Haz clic en **Conectar** junto a la tarjeta de Google Ads. Serás redirigido a Google.

</Step>
<Step>

## Iniciar sesión con Google

Inicia sesión con la cuenta de Google que tiene acceso a tus cuentas de Google Ads. Otorga a Atribu permiso para leer y administrar los datos de tus anuncios.

<Callout type="info" title="Acceso sin conexión">
Atribu solicita acceso sin conexión para poder sincronizar datos en segundo plano sin requerir que estés conectado.
</Callout>

</Step>
<Step>

## Seleccionar tu cuenta de cliente

Elige qué cuenta de cliente de Google Ads (también llamada CID) deseas sincronizar. Si usas una Cuenta de Administrador (MCC), verás todas las cuentas vinculadas.

</Step>
</Steps>

---

## Qué se sincroniza

| Dato | Descripción |
|------|-------------|
| **Campañas** | Nombre de campaña, estado, tipo (Search, Display, Video, Performance Max) |
| **Grupos de anuncios** | Nombre del grupo de anuncios, segmentación, estrategia de puja |
| **Anuncios** | Copy del anuncio, títulos, descripciones, URLs finales |
| **Gasto diario** | Monto de gasto, impresiones, clics por día |

Las métricas de rendimiento como CPC y CTR se calculan en tiempo real a partir de los datos crudos en lugar de almacenarse como números estáticos.

---

## Exportación de conversiones de Google Ads

Atribu puede enviar eventos de conversión de vuelta a Google Ads. Esto le indica a Google qué clics en anuncios generaron resultados reales de negocio (leads, citas, compras), para que sus algoritmos de Smart Bidding puedan optimizar hacia los resultados que importan para tu negocio.

Por ejemplo, si la mayoría de tus clientes que pagan provienen de un grupo específico de palabras clave, Google aprenderá a pujar de manera más agresiva por esas palabras clave.

---

## Seguimiento de Click ID

Google agrega automáticamente un parámetro `gclid` a la URL cuando alguien hace clic en tu anuncio:

```txt title="URL de ejemplo con gclid"
https://tusitio.com/landing-page?gclid=Cj0KCQj...
```

Atribu detecta este click ID y lo usa para:

1. Clasificar la visita como **Paid Search** en tu desglose de tráfico
2. Vincular al visitante con la campaña y grupo de anuncios específico de Google Ads
3. Rastrear si ese visitante luego convierte

No necesitas configurar parámetros UTM manualmente -- el manejo de `gclid` es automático.

---

## Solución de problemas

### No veo mis cuentas de anuncios

Si tus cuentas de Google Ads no aparecen durante la configuración:

- Asegúrate de que la cuenta de Google con la que iniciaste sesión tenga acceso **Estándar** o **Administrador** a la cuenta de Google Ads
- Si usas una Cuenta de Administrador (MCC), verifica que la cuenta esté vinculada y activa
- Intenta cerrar sesión en Google y volver a iniciar sesión con la cuenta correcta

### Los datos no se sincronizan

Si tus campañas aparecen pero los datos de gasto faltan o están desactualizados:

1. Ve a **Configuración > Integraciones** y verifica el estado de la conexión
2. Si el estado muestra un error, intenta desconectar y reconectar
3. Los datos de Google Ads generalmente están disponibles dentro de unas pocas horas después de que termina cada día

---

## Relacionado

<Cards>
  <Card title="Instalar el Tracker" href="/docs/es/getting-started/install-tracker">
    Necesario para conectar los clics en anuncios con las visitas al sitio web
  </Card>
  <Card title="Rendimiento de Anuncios" href="/docs/es/features/ads-performance">
    Ve tus campañas de Google Ads ordenadas por ROAS
  </Card>
  <Card title="Clasificación de Canales" href="/docs/es/concepts/channels">
    Cómo el tráfico de Google Ads se clasifica como Paid Search
  </Card>
  <Card title="Conversion Sync" href="/docs/es/features/conversion-sync">
    Envia conversiones a Google Ads para mejor optimizacion
  </Card>
</Cards>

---

# Google Search Console

La integración con Google Search Console (GSC) te muestra qué consultas de búsqueda traen visitantes a tu sitio de forma orgánica -- sin anuncios pagados. Esto te ayuda a entender tu rendimiento SEO junto con tus campañas pagadas.

---

## Qué obtienes

- Consultas de búsqueda que generan tráfico orgánico a tu sitio
- Métricas de rendimiento: impresiones, clics, CTR y posición promedio por consulta
- Visibilidad de qué palabras clave traen tráfico gratuito vs tráfico pagado

---

## Conectar Google Search Console

<Steps>
<Step>

## Abrir Integraciones

Ve a **Configuración > Integraciones** en tu espacio de trabajo de Atribu.

</Step>
<Step>

## Iniciar la conexión

Haz clic en **Conectar** junto a la tarjeta de Google Search Console. Serás redirigido a Google.

</Step>
<Step>

## Iniciar sesión con Google

Inicia sesión con la cuenta de Google que tiene acceso a tus propiedades de Search Console. Otorga a Atribu permiso para leer tus datos de búsqueda.

</Step>
<Step>

## Seleccionar tu propiedad

Elige la propiedad de Search Console (sitio web) que deseas sincronizar. Debe coincidir con el dominio que estás rastreando con Atribu.

<Callout type="info" title="Tipos de propiedad">
Si tienes tanto una **propiedad de dominio** (ej., `ejemplo.com`) como una **propiedad de prefijo de URL** (ej., `https://www.ejemplo.com/`), elige la propiedad de dominio para obtener los datos más completos.
</Callout>

</Step>
</Steps>

---

## Qué se sincroniza

| Dato | Descripción |
|------|-------------|
| **Consultas de búsqueda** | Las palabras clave reales que las personas buscan en Google |
| **Impresiones** | Cuántas veces apareció tu sitio en los resultados de búsqueda para cada consulta |
| **Clics** | Cuántas veces alguien hizo clic para llegar a tu sitio |
| **CTR** | Tasa de clics (clics / impresiones) |
| **Posición promedio** | Dónde tu sitio generalmente se posiciona para cada consulta (1 = primer resultado) |

---

## Dónde aparecen los datos

Los datos de Search Console aparecen en la sección de **desglose por palabras clave** de tu dashboard. Esto te da una imagen clara de qué términos de búsqueda traen visitantes orgánicos, separados de tus fuentes de tráfico pagado.

<Callout type="info" title="Búsqueda Orgánica vs Pagada">
Google Search Console muestra tu tráfico de búsqueda **orgánico** (gratuito). Esto es independiente de **Google Ads**, que muestra tus campañas de búsqueda pagada. Juntos, te dan una imagen completa de tu presencia en la búsqueda de Google -- tanto el tráfico por el que pagas como el tráfico que ganas a través del SEO.
</Callout>

---

## Solución de problemas

### No aparecen datos

Los datos de Google Search Console pueden tardar 2-3 días en estar disponibles -- esto es una limitación de los reportes de Google, no de Atribu. Si tu propiedad fue verificada recientemente, espera unos días para que se acumulen datos.

### No veo mi propiedad

- Asegúrate de haber iniciado sesión con la cuenta de Google que es propietaria o tiene acceso a la propiedad de Search Console
- Verifica que la propiedad esté configurada y verificada en [Google Search Console](https://search.google.com/search-console)
- Si administras múltiples propiedades, verifica que hayas seleccionado la correcta durante la configuración

### Los datos parecen incompletos

Google Search Console no reporta consultas con volumen muy bajo por razones de privacidad. Si una consulta generó solo uno o dos clics, puede no aparecer en los datos. Este es un comportamiento normal del lado de Google.

---

## Relacionado

<Cards>
  <Card title="Fuentes de Tráfico" href="/docs/es/features/traffic-sources">
    Ve los datos de palabras clave en los tableros de desglose de tu dashboard
  </Card>
  <Card title="Clasificación de Canales" href="/docs/es/concepts/channels">
    Cómo se clasifica y rastrea el tráfico de búsqueda orgánica
  </Card>
</Cards>

---

# MercadoPago

La integración con MercadoPago rastrea los pagos de clientes latinoamericanos. Si tu negocio acepta pagos a través de MercadoPago, Atribu registra cada transacción y atribuye los ingresos a las campañas publicitarias que trajeron a esos clientes.

---

## Qué obtienes

- Cada pago de MercadoPago registrado como una conversión `payment_received`
- Ingresos atribuidos a campañas publicitarias específicas para un ROAS preciso
- Soporte multi-moneda -- los pagos en MXN, BRL, ARS, COP y otras monedas se normalizan a tu moneda de reporte
- Coincidencia de identidad del cliente mediante direcciones de correo electrónico

---

## Conectar MercadoPago

<Steps>
<Step>

## Abrir Integraciones

Ve a **Configuración > Integraciones** en tu espacio de trabajo de Atribu.

</Step>
<Step>

## Iniciar la conexión

Haz clic en **Conectar** junto a la tarjeta de MercadoPago. Serás redirigido a MercadoPago.

</Step>
<Step>

## Autorizar en MercadoPago

Inicia sesión con tu cuenta de MercadoPago y autoriza a Atribu para recibir notificaciones de pago.

</Step>
<Step>

## Verificar la conexión

De vuelta en Atribu, la tarjeta de MercadoPago debería mostrar **Conectado**. Los pagos comenzarán a fluir automáticamente a través de webhooks.

</Step>
</Steps>

---

## Cómo funciona

La integración con MercadoPago funciona de la misma manera que Stripe:

1. **MercadoPago envía un webhook** a Atribu cuando se completa un pago
2. **Atribu crea una conversión** -- se registra un evento `payment_received` con el monto y la moneda del pago
3. **El grafo de identidad vincula el pago** -- Atribu hace coincidir el correo del pagador con cualquier visita previa en tu sitio
4. **Se calcula la atribución** -- Atribu rastrea hasta el clic original en el anuncio y asigna crédito de ingresos

---

## Qué datos recibe Atribu

| Dato | Descripción |
|------|-------------|
| **Monto del pago** | El monto cobrado, en la moneda original |
| **Moneda** | MXN, BRL, ARS, COP, CLP, PEN, UYU, USD, etc. |
| **Correo del pagador** | Usado para la coincidencia de identidad |

---

## Normalización de moneda

<Callout type="info" title="Conversión automática de moneda">
Los pagos de MercadoPago frecuentemente llegan en monedas locales (pesos mexicanos, reales brasileños, pesos argentinos, etc.). Atribu normaliza automáticamente estos montos a la moneda de reporte de tu perfil para que todos tus números de ingresos, ROAS y dashboards se muestren en una sola moneda consistente.
</Callout>

---

## Ingresos y ROAS

Al igual que Stripe, los pagos de MercadoPago se clasifican como ingresos **cash** (efectivo). Este es el único tipo de ingreso utilizado para los cálculos de ROAS. Tu ROAS refleja dinero real recibido de los clientes, no proyecciones ni estimaciones.

---

## Solución de problemas

### Los pagos no aparecen

- Verifica que el estado de la conexión muestre **Conectado** en **Configuración > Integraciones**
- Los webhooks de MercadoPago pueden tardar desde unos segundos hasta un minuto en llegar
- Si el problema persiste, desconecta y reconecta la integración

### Los pagos aparecen como no atribuidos

La atribución requiere que el correo del pagador coincida con un visitante conocido. Si el cliente pagó sin haber visitado tu sitio rastreado ni haber enviado su correo a través de un formulario, el pago se registrará pero no podrá ser atribuido a una campaña publicitaria.

### La moneda se ve incorrecta en el dashboard

Asegúrate de que la moneda de reporte de tu perfil esté configurada correctamente en **Configuración > Perfil**. Atribu convierte todos los pagos entrantes a esta moneda usando las tasas de cambio actuales.

---

## Relacionado

<Cards>
  <Card title="Tipos de Ingresos" href="/docs/es/concepts/revenue-types">
    Por qué solo los pagos en efectivo cuentan para el ROAS
  </Card>
  <Card title="Resolución de Identidad" href="/docs/es/concepts/identity-resolution">
    Cómo los pagos se vinculan a los clics en anuncios mediante el grafo de identidad
  </Card>
  <Card title="Instalar el Tracker" href="/docs/es/getting-started/install-tracker">
    Necesario para conectar los clics en anuncios con las visitas al sitio web
  </Card>
</Cards>

---

# Meta (Facebook e Instagram)

La integración con Meta conecta tus cuentas de anuncios de Facebook e Instagram a Atribu. Sincroniza toda tu jerarquía de campañas con datos de rendimiento diarios y habilita el flujo de datos bidireccional a través de la API de Conversiones de Meta (CAPI).

---

## Qué obtienes

- Estructura completa de campañas: campañas, conjuntos de anuncios y anuncios individuales
- Gasto diario, impresiones, clics, alcance y frecuencia para cada anuncio
- URLs de video creativos para previsualizar anuncios directamente en Atribu
- Clasificación automática de tráfico -- las visitas desde anuncios de Meta se etiquetan como **Paid Social**
- Exportación de conversiones vía CAPI para que Meta pueda optimizar la entrega de tus anuncios

---

## Conectar Meta

<Steps>
<Step>

## Abrir Integraciones

Ve a **Configuración > Integraciones** en tu espacio de trabajo de Atribu.

</Step>
<Step>

## Iniciar la conexión

Haz clic en **Conectar** junto a la tarjeta de Meta (Facebook e Instagram). Serás redirigido a Facebook.

</Step>
<Step>

## Autorizar en Facebook

Inicia sesión con la cuenta de Facebook que administra tus cuentas de anuncios. Otorga a Atribu los permisos solicitados:

- **Ads Read** -- permite a Atribu obtener datos de campañas y métricas de rendimiento
- **Ads Management** -- permite a Atribu enviar datos de conversión de vuelta a Meta (CAPI)
- **Pages Read Engagement** -- permite a Atribu cargar los creativos de video desde tu Página

<Callout type="warn" title="Otorga todos los permisos">
Si omites un permiso, algunas funciones no funcionarán. Las previsualizaciones de video requieren Pages Read Engagement, y CAPI requiere Ads Management.
</Callout>

</Step>
<Step>

## Seleccionar cuentas de anuncios

Elige qué cuentas de anuncios deseas sincronizar. Atribu comenzará a importar datos de inmediato.

</Step>
</Steps>

---

## Qué se sincroniza

| Dato | Descripción |
|------|-------------|
| **Campañas** | Nombre de campaña, estado, objetivo, presupuesto |
| **Conjuntos de anuncios** | Segmentación, ubicación, horario, estrategia de puja |
| **Anuncios** | Creativos, copy, IDs de video, URLs de miniatura |
| **Gasto diario** | Monto de gasto, impresiones, clics, alcance, frecuencia por día |
| **Fuentes de video** | URLs de video reproducibles para previsualizar creativos en Atribu |

Las métricas de rendimiento como CPM, CPC y CTR se calculan en tiempo real a partir de los datos crudos (gasto, impresiones, clics) en lugar de almacenarse como valores estáticos. Esto significa que siempre reflejan los números más actualizados, incluso después de la normalización de moneda.

---

## Frecuencia de sincronización

Después de conectar, Atribu ejecuta una sincronización inicial para importar tus datos históricos. Después de eso, los datos se sincronizan periódicamente durante el día. También puedes activar una sincronización manual desde **Configuración > Integraciones** en cualquier momento.

Las nuevas campañas y anuncios generalmente aparecen dentro de unas pocas horas después de ser creados en Meta Ads Manager.

---

## API de Conversiones de Meta (CAPI)

La API de Conversiones de Meta envía tus datos de conversión (leads, compras, citas) de vuelta a Meta. Esto ayuda al algoritmo de Meta a entender qué clics en anuncios generan resultados reales de negocio, para que pueda optimizar la entrega de tus anuncios y encontrar más personas similares a tus mejores clientes.

En términos simples: Atribu le dice a Meta "esta persona hizo clic en tu anuncio y luego se convirtió en un cliente que pagó", para que Meta pueda mostrar tus anuncios a personas similares.

<Callout type="info" title="Por qué CAPI es importante">
A medida que las restricciones de privacidad del navegador crecen (bloqueo de cookies, límites de seguimiento en iOS), los datos de conversión del lado del servidor a través de CAPI se vuelven cada vez más importantes para una optimización precisa de anuncios. Sin ello, Meta podría no saber que muchas de tus conversiones provienen de sus anuncios.
</Callout>

---

## Seguimiento de Click ID

Meta agrega automáticamente un parámetro `fbclid` a la URL cuando alguien hace clic en tu anuncio. Por ejemplo:

```txt title="URL de ejemplo con fbclid"
https://tusitio.com/landing-page?fbclid=AbC123xYz...
```

Atribu detecta este click ID y lo usa para:

1. Clasificar la visita como **Paid Social** en tu desglose de tráfico
2. Vincular al visitante con la campaña, conjunto de anuncios y anuncio específico de Meta en el que hizo clic
3. Rastrear si ese visitante luego convierte (llena un formulario, realiza un pago, etc.)

No necesitas configurar parámetros UTM manualmente -- el manejo de `fbclid` es automático.

---

## Re-autorización

<Callout type="warn" title="Puede ser necesaria una re-autorización">
Si Atribu agrega nuevas funciones que requieren permisos adicionales de Facebook, es posible que necesites re-autorizar la conexión. Ve a **Configuración > Integraciones** y haz clic en **Re-autorizar** junto a Meta. Esto mostrará nuevamente el diálogo de permisos de Facebook para que puedas otorgar los nuevos permisos.
</Callout>

---

## Solución de problemas

### Las previsualizaciones de video no cargan

Los videos de anuncios son propiedad de tu Página de Facebook, no de tu cuenta personal. Atribu necesita el permiso **Pages Read Engagement** para acceder a ellos. Si omitiste este permiso durante la configuración:

1. Ve a **Configuración > Integraciones**
2. Haz clic en **Re-autorizar** junto a Meta
3. Asegúrate de que todos los permisos estén otorgados

### No veo todas mis cuentas de anuncios

Tu cuenta de usuario de Facebook necesita acceso a las cuentas de anuncios a través de Business Manager. Verifica que:

- Seas administrador o anunciante en la cuenta de anuncios
- La cuenta de anuncios sea parte de un Business Manager al que tengas acceso
- Hayas seleccionado las cuentas de anuncios correctas durante el paso de conexión

### Los datos parecen desactualizados

Meta limita las solicitudes a la API, por lo que puede haber un retraso de algunas horas para obtener los datos más recientes. Puedes activar una sincronización manual desde **Configuración > Integraciones** para actualizar de inmediato.

---

## Relacionado

<Cards>
  <Card title="Instalar el Tracker" href="/docs/es/getting-started/install-tracker">
    Necesario para conectar los clics en anuncios con las visitas al sitio web
  </Card>
  <Card title="Parámetros UTM" href="/docs/es/tracking/utm-parameters">
    Etiqueta las URLs de tus anuncios de Meta para una atribución detallada
  </Card>
  <Card title="Rendimiento de Anuncios" href="/docs/es/features/ads-performance">
    Ve tus campañas de Meta ordenadas por ROAS y otras métricas
  </Card>
  <Card title="Conversion Sync" href="/docs/es/features/conversion-sync">
    Envia conversiones a Meta CAPI para mejor optimizacion de anuncios
  </Card>
</Cards>

---

# Shopify

La integración con Shopify conecta tu tienda en línea a Atribu. Rastrea pedidos y datos de clientes para que puedas ver qué campañas publicitarias generan más ventas.

---

## Qué obtienes

- Pedidos registrados como eventos de conversión con datos de ingresos
- Información del cliente (correo, nombre) vinculada al grafo de identidad de Atribu
- Atribución de ingresos de e-commerce a campañas publicitarias específicas
- **Tracking completo en la tienda** mediante la extensión de tema de Atribu (engagement, scroll, video, formularios, web vitals)
- **Tracking de checkout** mediante el Shopify Web Pixel (agregar al carrito, checkout iniciado, compra)
- Webhooks para `orders/paid`, `orders/create` y `checkouts/create`

---

## Cómo funciona el sistema dual

Atribu usa dos extensiones complementarias en tu tienda Shopify:

| Extensión | Dónde se ejecuta | Qué captura |
|---|---|---|
| **Extensión de tema** (Atribu Tracker) | Páginas de la tienda (producto, colección, inicio, blog) | Engagement, scroll, video, formularios, identidad, web vitals, UTMs, click IDs |
| **Web Pixel** | Checkout (sandbox) | `add_to_cart`, `checkout_started`, `purchase` con valor del pedido |

La extensión de tema te da analíticas de comportamiento completas en tu tienda. El web pixel maneja el checkout, donde Shopify no permite ejecutar scripts externos directamente.

Ambos comparten el mismo `anonymous_id` y `session_id` en localStorage, así que el comportamiento de navegación del visitante se vincula con su eventual compra.

<Callout type="info" title="¿Por qué dos extensiones?">
El checkout de Shopify se ejecuta en un entorno aislado (sandbox) que bloquea scripts externos. La Web Pixel API es la única forma de rastrear eventos ahí. Para todo lo demás (páginas de la tienda), el tracker completo de Atribu provee datos mucho más ricos que el pixel solo.
</Callout>

---

## Conectar Shopify

<Steps>
<Step>

## Abrir Integraciones

Ve a **Configuración > Integraciones** en tu espacio de trabajo de Atribu.

</Step>
<Step>

## Ingresar el dominio de tu tienda

Escribe el dominio de tu tienda Shopify (ej., `mi-tienda` o `mi-tienda.myshopify.com`) en la tarjeta de Shopify.

</Step>
<Step>

## Autorizar la aplicación

Haz clic en **Conectar**. Serás redirigido a Shopify para instalar y autorizar la app de Atribu. Atribu solicita:

- **Leer pedidos** y **Leer todos los pedidos** -- para rastrear compras e ingresos
- **Leer clientes** -- para vincular compradores con clics en anuncios
- **Escribir pixels** -- para activar automáticamente el pixel de checkout
- **Leer eventos de clientes** -- para leer datos del pixel

</Step>
<Step>

## Activar tracking en la tienda

Después de conectar, haz clic en el botón **Activar tracking en tienda**. Esto abre el editor de temas de Shopify directamente en el toggle del Atribu Tracker.

1. Activa **Atribu Tracker**
2. Tu clave de tracking está pre-configurada
3. Haz clic en **Guardar**

<Callout type="warn" title="No omitas este paso">
Sin la extensión de tema, solo recibes eventos de checkout del web pixel. Te pierdes todos los datos de engagement (scroll, video, formularios, web vitals) que potencian la atribución ponderada por engagement.
</Callout>

</Step>
<Step>

## Verificar

De vuelta en Atribu, la tarjeta de Shopify debería mostrar **Conectado**. El web pixel se activa automáticamente durante la conexión.

Verifica en **Configuración > Eventos de clientes** en tu panel de Shopify que el pixel de Atribu muestre **Conectado**.

</Step>
</Steps>

---

## Qué se sincroniza

| Dato | Fuente | Descripción |
|---|---|---|
| **Vistas de página** | Extensión de tema | Cada visita a una página con UTMs y click IDs |
| **Engagement** | Extensión de tema | Scroll, tiempo en página, video, calidad de clics, web vitals |
| **Agregar al carrito** | Web Pixel | Producto agregado al carrito con valor |
| **Checkout iniciado** | Web Pixel | Checkout iniciado con subtotal |
| **Compra** | Web Pixel + Webhook | Pedido completado con valor total, order ID, checkout token |
| **Detalles del pedido** | Webhook | Monto, moneda, correo/nombre del cliente, productos |
| **Identidad del cliente** | Extensión de tema + Webhook | Correo de formularios (vía `identify()`) y de pedidos |

---

## Cómo funciona la atribución

1. Un cliente hace clic en tu anuncio y llega a tu tienda Shopify (con parámetros UTM o click IDs)
2. El **tracker de Atribu** en el tema registra la visita, el engagement y captura los datos de atribución
3. Si el cliente completa un formulario (suscripción, contacto), `identify()` vincula su visita anónima con su correo
4. El cliente realiza una compra -- el **web pixel** dispara `purchase` y el **webhook** entrega el pedido con el correo del cliente
5. Atribu vincula el correo del cliente con la visita anterior a la tienda
6. Los ingresos se atribuyen a la campaña publicitaria que generó el clic original

### Atribución ponderada por engagement

Cuando la extensión de tema está habilitada, Atribu calcula un `engagement_quality_score` (0-100) para cada sesión en la tienda. Este puntaje influye en el modelo de atribución **ponderado por engagement**, dando más crédito a los clics en anuncios que generaron visitas de alta calidad.

---

## Configuración de la extensión de tema

El app embed de Atribu Tracker soporta estas configuraciones en el editor de temas:

| Configuración | Predeterminado | Descripción |
|---|---|---|
| Clave de Tracking | *(requerido)* | Tu clave `trk_` de Atribu Configuración > Tracking |
| Endpoint de Tracking | `https://www.atribu.app/api/tracking/collect` | Cambiar solo si usas dominio personalizado |
| URL del Script | `https://www.atribu.app/atribu-tracker.js` | Cambiar solo si lo alojas tú mismo |
| Interceptar Meta Pixel | ON | Replicar eventos `fbq()` server-side para mejor calidad de match en Meta CAPI |
| Timeout de Sesión | 30 minutos | Minutos de inactividad antes de iniciar nueva sesión |
| Modo de Sesión | Inactividad o cambio de fuente | También inicia nueva sesión cuando el visitante llega desde otra campaña |
| Calidad de Clics | ON | Rastrear rage clicks y dead clicks |
| Web Vitals | ON | Capturar métricas de rendimiento LCP, CLS, INP |
| Visibilidad CTA | ON | Rastrear cuando botones de acción entran en el viewport |
| Video Tracking | ON | Rastrear reproducción de video nativo, Wistia y Video.js |

---

## Solución de problemas

### Los pedidos no se sincronizan

- Verifica que la conexión muestre **Conectado** en **Configuración > Integraciones**
- Asegúrate de que la app de Atribu siga instalada en **Configuración > Apps y canales de venta**
- Verifica los webhooks en **Configuración > Notificaciones > Webhooks** en Shopify
- Si cambiaste el dominio de tu tienda, reconecta desde Atribu

### El web pixel muestra "Desconectado"

El web pixel se activa automáticamente al conectar Shopify desde Atribu. Si muestra desconectado:

- Reconecta desde Atribu (Configuración > Integraciones > Shopify > Reconectar)
- Verifica en **Configuración > Eventos de clientes** en Shopify

### No hay datos de engagement en la tienda

- Asegúrate de que el app embed **Atribu Tracker** esté activado en el editor de temas
- Verifica que la clave de tracking sea correcta
- Revisa la consola del navegador en una página de la tienda

### Los ingresos no se atribuyen a campañas

Para que la atribución funcione, el cliente debe ser identificable. Esto requiere:

- El tracker de Atribu capturando el clic en el anuncio (UTMs/click IDs) en la visita a la tienda
- Que el correo del cliente coincida con un visitante previamente identificado

Si ninguna condición se cumple, el pedido se registrará pero se mostrará como no atribuido.

### Deduplicación

El web pixel y los webhooks pueden capturar la misma compra. Atribu deduplica por `order_id` y `checkout_token` -- nunca verás ingresos contados dos veces.

---

## Relacionado

<Cards>
  <Card title="Tipos de Ingresos" href="/docs/es/concepts/revenue-types">
    Cómo los valores de pedido difieren de los pagos en efectivo
  </Card>
  <Card title="Resolución de Identidad" href="/docs/es/concepts/identity-resolution">
    Cómo los correos de Shopify se vinculan a clics en anuncios
  </Card>
  <Card title="Modelos de Atribución" href="/docs/es/concepts/attribution-models">
    Cómo el engagement en la tienda afecta la distribución de crédito
  </Card>
</Cards>

---

# Stripe

La integración con Stripe registra cada pago que recibe tu negocio como un evento de conversión. Atribu atribuye esos ingresos a la campaña, conjunto de anuncios y anuncio específico que originalmente trajo al cliente a tu sitio.

---

## Qué obtienes

- Cada pago de Stripe registrado como una conversión `payment_received`
- Ingresos atribuidos a campañas publicitarias específicas para un ROAS preciso
- Coincidencia de identidad del cliente -- Atribu vincula los pagos con clics anteriores en anuncios
- Payment Links con seguimiento de atribución integrado

---

## Conectar Stripe

<Steps>
<Step>

## Abrir Integraciones

Ve a **Configuración > Integraciones** en tu espacio de trabajo de Atribu.

</Step>
<Step>

## Iniciar la conexión

Haz clic en **Conectar** junto a la tarjeta de Stripe. Serás redirigido a Stripe.

</Step>
<Step>

## Autorizar en Stripe

Inicia sesión con tu cuenta de Stripe y autoriza a Atribu. Esto configura un webhook seguro para que Stripe notifique a Atribu cada vez que ocurre un pago.

</Step>
<Step>

## Verificar la conexión

De vuelta en Atribu, la tarjeta de Stripe debería mostrar **Conectado**. Los pagos comenzarán a fluir automáticamente.

</Step>
</Steps>

---

## Cómo funciona

Cuando un cliente realiza un pago a través de Stripe, ocurre lo siguiente:

1. **Stripe envía un webhook** a Atribu con los detalles del pago (monto, moneda, correo del cliente, datos de facturación)
2. **Atribu crea una conversión** -- se registra un evento `payment_received` con el monto exacto
3. **El grafo de identidad vincula el pago** -- Atribu hace coincidir el correo del cliente del pago con cualquier visita previa en tu sitio
4. **Se calcula la atribución** -- Atribu rastrea hasta el clic original en el anuncio y asigna crédito de ingresos a la campaña

Atribu procesa tres tipos de eventos de Stripe para asegurar que no se pierda ningún pago:

| Evento | Propósito |
|--------|-----------|
| `charge.succeeded` | Evento principal de pago -- la mayoría de los cargos llegan por esta vía |
| `checkout.session.completed` | Datos más completos de Stripe Checkout y Payment Links |
| `payment_intent.succeeded` | Respaldo para casos donde el cargo no está en el payload |

<Callout type="info" title="Cómo funciona la atribución de pagos">
Este es un flujo típico: un cliente hace clic en tu anuncio de Meta, visita tu landing page y llena un formulario de contacto. Dos semanas después, paga vía Stripe. Atribu hace coincidir el correo del pago de Stripe con el correo del formulario enviado, luego rastrea a ese visitante hasta el clic original en el anuncio de Meta. Los ingresos se atribuyen a ese anuncio específico.
</Callout>

---

## Qué datos recibe Atribu

| Dato | Descripción |
|------|-------------|
| **Monto del pago** | El monto cobrado, en la moneda original |
| **Moneda** | USD, EUR, MXN, etc. -- Atribu normaliza a tu moneda de reporte |
| **Correo del cliente** | Del correo de recibo o datos de facturación |
| **Nombre del cliente** | De los datos de facturación, si se proporcionan |
| **Teléfono del cliente** | De los datos de facturación, si se proporcionan |
| **Stripe Customer ID** | Usado para la coincidencia de identidad entre múltiples pagos |

---

## Ingresos y ROAS

<Callout type="warn" title="Los pagos de Stripe son el estándar de oro para el ROAS">
Los pagos de Stripe se clasifican como ingresos **cash** (efectivo). Este es el **único** tipo de ingreso utilizado para los cálculos de ROAS (Retorno sobre el Gasto Publicitario). Los valores de pipeline de CRMs como GoHighLevel se rastrean por separado y nunca se mezclan en el ROAS. Esto asegura que tu ROAS refleje dinero real recibido, no proyecciones.
</Callout>

---

## Payment Links

Atribu puede generar Stripe Payment Links con seguimiento de atribución integrado. Estos enlaces incluyen metadatos que vinculan el pago directamente a un cliente y campaña específicos, incluso antes de que el cliente complete el checkout.

Esto es útil para:

- Enviar enlaces de pago personalizados a leads
- Rastrear qué campañas generan más pagos directos
- Atribuir ingresos cuando el cliente paga sin visitar tu sitio web primero

---

## Extensión para el dashboard de Stripe

Atribu tiene una App de Stripe que se ejecuta directamente dentro de tu dashboard de Stripe. Te muestra qué campañas publicitarias atrajeron a cada cliente y sus ingresos atribuidos -- sin salir de Stripe. Consulta con tu administrador de cuenta sobre cómo habilitarla.

---

## Solución de problemas

### Los pagos no aparecen

- Verifica que el estado de la conexión muestre **Conectado** en **Configuración > Integraciones**
- Los webhooks de Stripe pueden tardar unos segundos en llegar. Actualiza la página después de un minuto.
- Si el problema persiste, desconecta y reconecta la integración de Stripe

### Un pago aparece como no atribuido

La atribución requiere que el cliente sea identificado antes de pagar. Esto generalmente ocurre cuando:

- El cliente llenó un formulario en tu sitio (correo o teléfono capturado)
- El correo del cliente en Stripe coincide con un correo que ya está en Atribu

Si un cliente paga sin haber visitado tu sitio rastreado ni haber enviado su correo previamente, el pago se registrará pero no podrá ser atribuido a una campaña publicitaria.

---

## Relacionado

<Cards>
  <Card title="Tipos de Ingresos" href="/docs/es/concepts/revenue-types">
    Por qué solo los pagos en efectivo cuentan para el ROAS
  </Card>
  <Card title="Resolución de Identidad" href="/docs/es/concepts/identity-resolution">
    Cómo los pagos se vinculan a los clics en anuncios mediante el grafo de identidad
  </Card>
  <Card title="Payment Links" href="/docs/es/features/payment-links">
    Crea Stripe Payment Links con seguimiento de atribución integrado
  </Card>
  <Card title="Conversion Sync" href="/docs/es/features/conversion-sync">
    Envia eventos de pago a plataformas publicitarias para optimizacion de ingresos
  </Card>
</Cards>

---

Todas las solicitudes MCP requieren un Bearer token en el header `Authorization`.

```bash title="Header de autorización"
Authorization: Bearer atb_user_tu_token_aqui
```

Los tokens MCP tienen **alcance de usuario** -- un solo token da acceso a todos los workspaces a los que perteneces. El workspace y perfil se especifican en cada llamada a herramienta, no se almacenan en el token.

## Crear un token

Crea tokens desde **Developer > MCP Tokens** en el dashboard de Atribu.

<Callout type="warn" title="Se muestra una vez">
Los tokens se muestran exactamente una vez al crearse. Guárdalos de forma segura -- no puedes recuperarlos después.
</Callout>

## Scopes

Cada token tiene scopes granulares que controlan qué datos y acciones están disponibles.

| Scope | Acceso |
|-------|--------|
| `mcp:read` | Datos de atribución, campañas, creativos, recorridos (PII enmascarado) |
| `mcp:read_pii` | Desenmascara email, teléfono y nombre completo cuando `include_sensitive=true` |
| `mcp:write` | Operaciones de write-back (exportación de conversiones a Meta CAPI) |

**Scope por defecto** al crear un token: `mcp:read`.

<Callout type="error" title="Scope de escritura">
`mcp:write` permite enviar datos de conversión a plataformas externas. Solo otórgalo cuando necesites específicamente el write-back de Meta CAPI. El administrador del workspace también debe habilitar write-back en la configuración.
</Callout>

## Límites de uso

MCP usa **limitación de uso ponderada**. Cada herramienta tiene un costo en unidades y tu token tiene un límite de ráfaga por minuto.

**Límite por minuto:** 120 unidades (por defecto).

| Herramienta | Costo |
|-------------|-------|
| `list_workspaces`, `list_profiles` | 0 unidades (gratis) |
| `get_performance_summary`, `compare_periods`, `top_campaigns`, `top_creatives` | 1 unidad |
| `explain_campaign`, `explain_customer_journey`, `creative_fatigue_check`, `find_anomalies`, `whatsapp_attribution_summary` | 2 unidades |
| `compare_attribution_models` | 5 unidades |
| `send_meta_conversions` | 10 unidades |

Cuando se excede el límite, la herramienta devuelve un error `rate_limited` con `retry_after` (segundos) y `retryable: true`.

## Medición de uso

Tu suscripción incluye una asignación mensual de unidades. Las unidades se debitan en cada llamada exitosa a herramienta.

| Plan | Unidades por período |
|------|---------------------|
| Trial | 2,000 / 14 días |
| Starter | 20,000 / mes |
| Growth | 200,000 / mes |
| Agency | 2,000,000 / mes |
| Enterprise | Ilimitado |

Consulta tu uso actual desde la pestaña **Developer > MCP Tokens** en el dashboard.

## Gestión de tokens

### Rotación

Rota tokens sin tiempo de inactividad:

<Steps>
<Step>
Haz clic en **Rotate** junto al token en el dashboard
</Step>
<Step>
Se genera un nuevo token y se muestra una vez
</Step>
<Step>
Actualiza la configuración de tu herramienta de IA con el nuevo token
</Step>
<Step>
El token anterior se revoca automáticamente
</Step>
</Steps>

### Revocación

Haz clic en **Revoke** para invalidar un token inmediatamente. Cualquier herramienta de IA que use ese token perderá acceso al instante.

## Sesiones concurrentes

Los tokens no tienen estado del lado del servidor. Múltiples herramientas de IA o sesiones de IDE pueden usar el mismo token simultáneamente sin interferir entre sí. Cada llamada a herramienta especifica su propio scope de workspace y perfil.

## Relacionado

<Cards>
  <Card title="Inicio rápido" href="/docs/mcp/quickstart">
    Conéctate en menos de 2 minutos
  </Card>
  <Card title="Referencia de errores" href="/docs/mcp/errors">
    Códigos de error y solución de problemas
  </Card>
  <Card title="Privacidad y PII" href="/docs/mcp/privacy">
    Cómo funcionan el enmascaramiento de PII y la configuración del workspace
  </Card>
</Cards>

---

Cuando una llamada a herramienta MCP falla, la respuesta incluye `isError: true` y un objeto de error estructurado. Tu herramienta de IA típicamente explicará el error y sugerirá una solución.

## Formato de respuesta de error

```json title="Respuesta de error"
{
  "error": {
    "code": "rate_limited",
    "message": "Per-minute rate limit exceeded. Wait 12 seconds.",
    "retryable": true,
    "retry_after": 12,
    "request_id": "01968a3b-..."
  }
}
```

Algunos errores incluyen campos adicionales:

- `action_url` -- un enlace a la página del dashboard de Atribu donde puedes solucionar el problema
- `workspaces` -- una lista de workspaces disponibles (para `workspace_required`)
- `profiles` -- una lista de perfiles disponibles (para `profile_required`)
- `support_hint` -- una sugerencia para contactar soporte con el `request_id`

---

## Códigos de error

### Errores de autenticación

| Código | Reintentable | Causa | Solución |
|--------|-------------|-------|----------|
| `auth_required` | No | Header Authorization faltante o malformado | Agrega el header `Authorization: Bearer atb_user_...` |
| `token_expired` | No | El token fue revocado o expiró | Crea un nuevo token desde **Developer > MCP Tokens** |

---

### Errores de scope

| Código | Reintentable | Causa | Solución |
|--------|-------------|-------|----------|
| `workspace_required` | No | El usuario tiene múltiples workspaces y ninguno fue especificado | Pasa `workspace_id` -- el error incluye una lista de tus workspaces |
| `profile_required` | No | El workspace tiene múltiples perfiles y ninguno fue especificado | Pasa `profile_id` -- el error incluye una lista de perfiles |
| `insufficient_scope` | No | El token no tiene el scope requerido para esta herramienta | Crea un nuevo token con el scope necesario |

<Callout type="info" title="Inferencia de recurso único">
Si tienes exactamente un workspace o un perfil, se selecciona automáticamente. Estos errores solo ocurren cuando hay múltiples opciones.
</Callout>

---

### Errores de límite de uso

| Código | Reintentable | Causa | Solución |
|--------|-------------|-------|----------|
| `rate_limited` | Sí | Límite de unidades por minuto o por período excedido | Espera `retry_after` segundos, luego reintenta |

El campo `retry_after` indica cuántos segundos esperar. Las herramientas de IA que soportan reintento lo manejarán automáticamente.

---

### Errores de conector

| Código | Reintentable | Causa | Solución |
|--------|-------------|-------|----------|
| `connector_expired` | No | El token OAuth de una integración requerida expiró | Re-autoriza en el `action_url` de la respuesta de error |
| `connector_required` | No | Una integración requerida no está conectada | Conéctala en el `action_url` de la respuesta de error |

Estos errores incluyen un `action_url` que enlaza directamente a la página de integraciones en tu dashboard de Atribu.

---

### Errores de write-back

| Código | Reintentable | Causa | Solución |
|--------|-------------|-------|----------|
| `writeback_disabled` | No | El administrador del workspace no ha habilitado MCP write-back | Pide a un administrador que lo habilite en **Settings > Privacy & MCP** |
| `idempotency_conflict` | No | Un confirm con esta clave de idempotencia ya fue procesado | El resultado anterior se devuelve -- no se necesita acción |
| `circuit_open` | Sí | 3+ fallas consecutivas en los últimos 30 minutos | Espera al enfriamiento, luego investiga las fallas subyacentes |

---

### Errores de entrada

| Código | Reintentable | Causa | Solución |
|--------|-------------|-------|----------|
| `invalid_input` | No | Parámetros inválidos o faltantes | Verifica los tipos de parámetros y campos requeridos |
| `data_unavailable` | No | No existen datos para la ventana o entidad especificada | Amplía el rango de fechas o verifica que la entidad existe |

---

### Errores de servidor

| Código | Reintentable | Causa | Solución |
|--------|-------------|-------|----------|
| `internal_error` | Sí | Error inesperado del servidor | Reintenta una vez. Si persiste, contacta soporte con el `request_id` |

---

## Solución de problemas

### "Recibo workspace_required pero solo tengo un workspace"

Esto puede pasar si tu token fue creado antes de que un workspace fuera eliminado. Intenta llamar `list_workspaces` para ver qué workspaces puede acceder tu token, y pasa el `workspace_id` explícitamente.

### "Los datos parecen obsoletos o vacíos"

Revisa el campo `meta.data_as_of` en las respuestas exitosas. Si tiene más de unas horas, tu integración puede necesitar re-sincronización. Ve a **Settings > Integrations** y revisa el estado de sincronización.

### "No puedo ver los emails de clientes"

El PII se enmascara por defecto. Necesitas las tres cosas: scope `mcp:read_pii` en el token, `include_sensitive: true` en la llamada, y modo PII del workspace en `full_default`. Ver [Privacidad y PII](/docs/mcp/privacy).

### "send_meta_conversions devuelve insufficient_scope"

Esta herramienta requiere scope `mcp:write` en el token, write-back del workspace habilitado y rol de administrador del workspace. Verifica las tres condiciones.

## Relacionado

<Cards>
  <Card title="Autenticación" href="/docs/mcp/authentication">
    Scopes de tokens y límites de uso
  </Card>
  <Card title="Herramientas disponibles" href="/docs/mcp/tools">
    Las 13 herramientas con parámetros
  </Card>
  <Card title="Inicio rápido" href="/docs/mcp/quickstart">
    Conéctate en menos de 2 minutos
  </Card>
</Cards>

---

# Servidor MCP

Atribu ofrece un servidor [Model Context Protocol](https://modelcontextprotocol.io) (MCP) alojado que da a herramientas de IA acceso directo a tus datos de atribución. Haz preguntas en lenguaje natural y obtén respuestas basadas en rendimiento real de campañas, recorridos de clientes y datos de ingresos.

```mermaid
flowchart LR
    A["Claude Code / Cursor"] -->|protocolo MCP| B["mcp.atribu.app"]
    B -->|consultas| C["Datos Atribu"]
    C --> D["Campañas"]
    C --> E["Recorridos"]
    C --> F["Ingresos"]
    B -->|respuesta estructurada| A
```

---

## Qué puedes hacer

- **"¿Cuál es mi ROAS este mes?"** -- obtén KPIs principales con gasto, ingresos y cobertura de atribución
- **"¿Qué campañas debería pausar?"** -- detecta fatiga creativa e identifica bajo rendimiento
- **"Muéstrame el recorrido de este cliente"** -- traza cada punto de contacto desde el primer clic hasta el pago
- **"Compara la semana pasada con la anterior"** -- análisis período contra período con porcentajes de cambio
- **"¿Hay alguna anomalía?"** -- detección automática de anomalías con z-score en gasto, ingresos y tráfico
- **"Envía estas conversiones a Meta"** -- exporta conversiones atribuidas a Meta CAPI para optimización

---

## Cómo funciona

El servidor MCP de Atribu está **alojado centralmente** en `https://mcp.atribu.app/mcp`. Tu herramienta de IA se conecta usando un token de acceso personal -- no requiere instalación local ni configuración de servidor.

El servidor expone **13 herramientas** que devuelven datos estructurados y etiquetados semánticamente. Todo el razonamiento y narrativa ocurre del lado de la herramienta de IA -- Atribu proporciona los datos, tu IA proporciona el análisis.

<Cards>
<Card title="Inicio rápido" href="/docs/mcp/quickstart">
  Conéctate en menos de 2 minutos
</Card>
<Card title="Autenticación" href="/docs/mcp/authentication">
  Scopes de tokens, límites de uso y medición
</Card>
<Card title="Herramientas disponibles" href="/docs/mcp/tools">
  Las 13 herramientas con parámetros y ejemplos
</Card>
<Card title="Write-Back (Meta CAPI)" href="/docs/mcp/write-back">
  Envía conversiones a Meta con controles de seguridad
</Card>
</Cards>

---

## Herramientas de IA compatibles

El servidor MCP funciona con cualquier herramienta que soporte el transporte Streamable HTTP:

| Herramienta | Transporte | Estado |
|-------------|-----------|--------|
| Claude Code (CLI) | Streamable HTTP | Soportado |
| Claude Desktop | Streamable HTTP | Soportado |
| Cursor | Streamable HTTP | Soportado |
| Windsurf | Streamable HTTP | Soportado |
| VS Code (Copilot) | Streamable HTTP | Soportado |
| Cualquier cliente MCP | Streamable HTTP | Soportado |

---

## Decisiones clave de diseño

**Sin IA en el servidor.** El servidor MCP devuelve datos estructurados con etiquetas semánticas, unidades y metadatos. Tu herramienta de IA interpreta los datos y genera la narrativa. Esto significa cero costo de inferencia LLM del lado de Atribu y siempre obtienes las últimas capacidades del modelo de tu herramienta de IA.

**Scope explícito por llamada.** Cada llamada a herramienta especifica qué workspace y perfil consultar. No hay estado de "workspace activo" en el servidor, así que sesiones concurrentes de IA usando el mismo token son seguras.

**PII enmascarado por defecto.** Emails, teléfonos y nombres de clientes se enmascaran a menos que solicites explícitamente desenmascarar con el scope correcto del token y la configuración del workspace.

**Medición ponderada.** Las herramientas cuestan diferentes cantidades de unidades según su complejidad. Listar workspaces es gratis; enviar conversiones a Meta cuesta 10 unidades. Tu plan incluye una asignación mensual de unidades.

---

## Relacionado

<Cards>
  <Card title="API REST" href="/docs/api/quickstart">
    Acceso programático vía endpoints REST tradicionales
  </Card>
  <Card title="Modelos de atribución" href="/docs/concepts/attribution-models">
    Cómo se asigna el crédito entre puntos de contacto
  </Card>
  <Card title="Tipos de ingresos" href="/docs/concepts/revenue-types">
    Efectivo vs pipeline vs ingresos brutos
  </Card>
</Cards>

---

El servidor MCP enmascara información de identificación personal (PII) por defecto. Esta página explica cómo funciona el enmascaramiento y cómo configurar el acceso a datos desenmascarados.

## Comportamiento por defecto

Cuando una herramienta devuelve datos de clientes (ej. `explain_customer_journey`), los campos de PII se enmascaran:

| Campo | Valor enmascarado | Valor desenmascarado |
|-------|-------------------|---------------------|
| Email | `j***@e****.com` | `jane@example.com` |
| Teléfono | `+1 2** *** 5678` | `+1 234 567 5678` |
| Nombre | Solo primer nombre (`Jane`) | Nombre completo (`Jane Smith`) |

Esto se aplica automáticamente -- no requiere configuración.

---

## Solicitar datos desenmascarados

Para ver PII completo, tres condiciones deben cumplirse simultáneamente:

<Steps>
<Step>

### Scope del token

Tu token MCP debe incluir el scope `mcp:read_pii`. Crea o rota un token con este scope desde **Developer > MCP Tokens**.

</Step>
<Step>

### Solicitud explícita

Pasa `include_sensitive: true` en la llamada a la herramienta. La herramienta de IA hará esto cuando pidas explícitamente datos desenmascarados (ej. "muéstrame los emails reales").

</Step>
<Step>

### Configuración del workspace

Un administrador del workspace debe configurar el modo PII del workspace en `full_default` en **Settings > Privacy & MCP**. El valor por defecto es `masked_default`.

</Step>
</Steps>

Si alguna condición no se cumple, la respuesta se enmascara y `meta.pii_level_applied` se establece en `"masked"`. No se devuelve error -- la herramienta hace fallback gracioso al enmascaramiento.

---

## Controles de administrador del workspace

Los administradores del workspace pueden configurar dos ajustes relacionados con MCP desde **Settings > Privacy & MCP**:

| Configuración | Defecto | Descripción |
|---------------|---------|-------------|
| Modo PII | `masked_default` | Controla si tokens con `mcp:read_pii` pueden ver PII completo |
| Write-back | Deshabilitado | Controla si tokens con `mcp:write` pueden enviar datos a Meta CAPI |

Estas configuraciones aplican a todos los tokens MCP usados para acceder al workspace, independientemente de qué usuario creó el token.

---

## Qué se registra

Las invocaciones de herramientas MCP registran **solo metadatos**:

- Nombre de herramienta, duración, código de estado, unidades debitadas
- Workspace ID, profile ID, request ID
- Conteo de registros, nivel de PII aplicado

Argumentos crudos de herramientas, datos de respuesta y PII **nunca se registran**. El `request_id` en cada respuesta puede usarse para consultas de soporte sin exponer datos sensibles.

---

## Flujo de datos

```mermaid
flowchart TD
    A["Herramienta IA llama explain_customer_journey"] --> B{"¿include_sensitive?"}
    B -->|No| C["Devuelve PII enmascarado"]
    B -->|Sí| D{"¿Token tiene mcp:read_pii?"}
    D -->|No| C
    D -->|Sí| E{"¿Workspace pii_mode = full_default?"}
    E -->|No| C
    E -->|Sí| F["Devuelve PII completo"]
```

## Relacionado

<Cards>
  <Card title="Autenticación" href="/docs/mcp/authentication">
    Scopes de tokens y límites de uso
  </Card>
  <Card title="Write-Back" href="/docs/mcp/write-back">
    Flujo de seguridad de write-back a Meta CAPI
  </Card>
  <Card title="Equipo y workspace" href="/docs/settings/team">
    Gestiona roles y permisos del workspace
  </Card>
</Cards>

---

<Steps>
<Step>

## Crea un token MCP

1. Inicia sesión en [Atribu](https://www.atribu.app)
2. Ve a **Developer** en la barra lateral
3. Cambia a la pestaña **MCP Tokens**
4. Haz clic en **New token**
5. Selecciona al menos el scope **Read analytics**
6. Copia el token

<Callout type="warn" title="Guarda tu token">
El token comienza con `atb_user_` y se muestra solo una vez. Guárdalo en un lugar seguro.
</Callout>

</Step>
<Step>

## Conecta tu herramienta de IA

<Tabs items={["Claude Code", "Cursor", "Claude Desktop", "Otro"]}>
<Tab value="Claude Code">
```bash title="Terminal"
claude mcp add atribu --transport http \
  https://mcp.atribu.app/mcp \
  --header "Authorization: Bearer atb_user_TU_TOKEN"
```
</Tab>
<Tab value="Cursor">

Agrega a tu configuración MCP de Cursor (`.cursor/mcp.json`):

```json title=".cursor/mcp.json"
{
  "mcpServers": {
    "atribu": {
      "url": "https://mcp.atribu.app/mcp",
      "headers": {
        "Authorization": "Bearer atb_user_TU_TOKEN"
      }
    }
  }
}
```
</Tab>
<Tab value="Claude Desktop">

Agrega a tu configuración de Claude Desktop (`claude_desktop_config.json`):

```json title="claude_desktop_config.json"
{
  "mcpServers": {
    "atribu": {
      "url": "https://mcp.atribu.app/mcp",
      "headers": {
        "Authorization": "Bearer atb_user_TU_TOKEN"
      }
    }
  }
}
```
</Tab>
<Tab value="Otro">

Cualquier cliente MCP que soporte el transporte Streamable HTTP funciona. Configúralo con:

- **URL:** `https://mcp.atribu.app/mcp`
- **Transporte:** Streamable HTTP
- **Header de auth:** `Authorization: Bearer atb_user_TU_TOKEN`

</Tab>
</Tabs>

</Step>
<Step>

## Pruébalo

Abre tu herramienta de IA y haz una pregunta sobre el rendimiento de tus anuncios:

```
¿Cuál es mi ROAS este mes? ¿Qué campañas rinden mejor?
```

La herramienta de IA llamará automáticamente las herramientas de Atribu correctas y devolverá una respuesta narrativa basada en tus datos reales.

<Callout type="info" title="Resolución de workspace">
Si perteneces a múltiples workspaces, la herramienta de IA te pedirá que especifiques cuál. Si tienes un solo workspace con un solo perfil, se selecciona automáticamente.
</Callout>

</Step>
</Steps>

## Verifica la conexión

Puedes verificar que el servidor MCP es accesible:

```bash title="Health check"
curl https://mcp.atribu.app/health/live
```

```json title="Respuesta"
{ "status": "ok" }
```

## Conversación de ejemplo

Así se ve una interacción típica en Claude Code:

```
Tú: ¿Qué pasó con el rendimiento de mis anuncios la semana pasada vs la anterior?

Claude: Voy a comparar los dos períodos.

[llama compare_periods con la semana pasada vs la anterior]

El rendimiento de tus anuncios mejoró la semana pasada:
- Gasto: $2,450 → $2,680 (+9.4%)
- Ingresos: $8,200 → $11,400 (+39.0%)
- ROAS: 3.35 → 4.25 (+26.9%)
- Conversiones: 42 → 58 (+38.1%)

El salto en ingresos se debe principalmente a "Summer Sale - Retargeting"
que generó $4,200 en ingresos atribuidos con un ROAS de 6.8x.

Datos al: 2026-04-14T18:30:00Z | Modelo: last_touch | Moneda: USD
```

## Próximos pasos

<Cards>
  <Card title="Autenticación" href="/docs/mcp/authentication">
    Scopes de tokens, límites de uso y medición
  </Card>
  <Card title="Herramientas disponibles" href="/docs/mcp/tools">
    Las 13 herramientas con parámetros y ejemplos
  </Card>
  <Card title="Privacidad y PII" href="/docs/mcp/privacy">
    Cómo funciona el enmascaramiento de PII y cómo configurarlo
  </Card>
</Cards>

---

Atribu incluye un [skill de Claude Code](https://code.claude.com/docs/en/skills) opinionado que enseña a Claude cómo razonar sobre tus datos de atribución. Una vez instalado, el skill se carga automáticamente cuando preguntas sobre ROAS, campañas, creativos o recorridos de clientes — Claude sigue las reglas del skill y llama a las herramientas MCP correctas en el orden correcto.

## Qué hace el skill

El servidor MCP devuelve datos crudos; el skill aporta el criterio:

- **Cash-ROAS por defecto** — el skill enseña a Claude que `revenue_type='cash'` es lo único que cuenta como ROAS. Los deals de pipeline de GHL son "valor de deal", no ingresos.
- **Heurísticas de orden de herramientas** — para cada pregunta común ("¿qué anuncios debería pausar?", "¿por qué cae el ROAS?", "¿quién está convirtiendo?"), el skill especifica qué herramientas MCP llamar y en qué orden.
- **Guía de modelos** — cuándo usar `last_touch` vs `engagement_weighted` vs `compare_attribution_models` según volumen de conversiones y ciclo de venta.
- **Seguridad de write-back** — flujo obligatorio preview → confirmación del usuario → confirm con idempotency-key para `send_meta_conversions`.
- **Explicaciones de PII** — cuando pides datos desenmascarados y tu token no puede proporcionarlos, Claude explica exactamente qué cambiar en lugar de devolver silenciosamente valores enmascarados.

---

## Instalación

El skill se distribuye como un plugin de Claude Code desde el repositorio público [AtribuCore/atribu-attribution-skill](https://github.com/AtribuCore/atribu-attribution-skill).

<Steps>
<Step>

### Agrega el marketplace e instala el plugin

En cualquier sesión de Claude Code, ejecuta:

```
/plugin marketplace add AtribuCore/atribu-attribution-skill
/plugin install atribu-attribution@atribu-attribution
```

El primer comando registra el marketplace; el segundo instala el plugin `atribu-attribution` (que incluye el skill).

</Step>
<Step>

### Reinicia Claude Code

Una sesión nueva garantiza que el skill esté cargado. Puedes verificar con:

```
¿Qué skills están disponibles?
```

Deberías ver `atribu-attribution` en la lista.

</Step>
<Step>

### Configura el servidor MCP

Si aún no lo has hecho, agrega el servidor MCP de Atribu ([inicio rápido](/docs/mcp/quickstart)):

```bash title="Terminal"
claude mcp add atribu --transport http \
  https://mcp.atribu.app/mcp \
  --header "Authorization: Bearer atb_user_TU_TOKEN"
```

</Step>
</Steps>

### Actualizaciones

Cuando el skill se actualice upstream, refresca con:

```
/plugin update atribu-attribution
```

### Otros clientes MCP

Cursor, Claude Desktop, Windsurf y otras herramientas compatibles con MCP usan el mismo servidor pero no tienen el sistema de plugins de Claude Code. Copia el contenido de [`SKILL.md`](https://github.com/AtribuCore/atribu-attribution-skill/blob/main/plugins/atribu-attribution/skills/atribu-attribution/SKILL.md) en tu system prompt o reglas de proyecto para comportamiento equivalente.

---

## Estructura del skill

Dentro del repositorio público, el plugin se organiza así:

```
atribu-attribution-skill/
├── .claude-plugin/marketplace.json     # Manifiesto del marketplace
├── plugins/
│   └── atribu-attribution/
│       ├── .claude-plugin/plugin.json  # Manifiesto del plugin
│       └── skills/atribu-attribution/
│           ├── SKILL.md                # Instrucciones principales
│           ├── references/
│           │   ├── tool-ordering.md    # Heurísticas detalladas
│           │   └── error-playbook.md   # Manejo de códigos de error
│           └── evals/evals.json        # Casos de prueba
├── README.md
└── LICENSE                             # MIT
```

El **`SKILL.md` principal** se mantiene enfocado — la referencia rápida. Cuando Claude necesita más detalle (manejo de errores o selección de herramientas), lee los archivos en `references/` bajo demanda. Esto sigue la recomendación de Anthropic de mantener `SKILL.md` ligero y cargar material extenso de forma perezosa.

---

## Evals

El skill incluye `evals/evals.json` — casos de prueba con comportamiento esperado. Cada eval es un objeto `{ prompt, expected_output, assertions }` que documenta lo que Claude debe hacer cuando el skill está cargado.

Ejemplos de aserciones:

- `"¿Qué anuncios debería pausar?"` → debe llamar `creative_fatigue_check` primero, luego `top_creatives`
- `"Muéstrame los emails reales"` → debe explicar el requisito de tres condiciones para PII, no devolver silenciosamente datos enmascarados
- `"Envía conversiones a Meta"` → debe llamar `preview` primero, pedir confirmación del usuario, generar una idempotency key, reportar el `audit_id`

Los evals no se ejecutan automáticamente por Claude Code — son casos de prueba de referencia para cualquiera que modifique el skill. Úsalos como lista de verificación de regresión antes de cambiar SKILL.md.

---

## Contribuir

El skill es open source bajo MIT. Si encuentras que da mala guía, [abre un issue](https://github.com/AtribuCore/atribu-attribution-skill/issues) o envía un PR a [AtribuCore/atribu-attribution-skill](https://github.com/AtribuCore/atribu-attribution-skill). Las versiones publicadas tienen tags para que los usuarios finales puedan fijarse a una si lo prefieren.

## Relacionado

<Cards>
  <Card title="Inicio rápido MCP" href="/docs/mcp/quickstart">
    Configura la conexión del servidor MCP
  </Card>
  <Card title="Herramientas disponibles" href="/docs/mcp/tools">
    Las 21 herramientas MCP que el skill orquesta
  </Card>
  <Card title="Write-Back" href="/docs/mcp/write-back">
    El flujo de preview/dry-run/confirm para send_meta_conversions
  </Card>
</Cards>

---

El servidor MCP de Atribu expone 13 herramientas. Cada herramienta devuelve una respuesta estructurada con un objeto `data` y un objeto `meta` que contiene metadatos de la solicitud, frescura de datos y contexto de atribución.

## Parámetros compartidos

La mayoría de herramientas aceptan estos parámetros comunes:

| Parámetro | Tipo | Requerido | Descripción |
|-----------|------|-----------|-------------|
| `workspace_id` | UUID | No | Qué workspace consultar. Se infiere si tienes exactamente uno. |
| `profile_id` | UUID | No | Qué perfil consultar. Se infiere si el workspace tiene exactamente uno. |
| `window_start` | `YYYY-MM-DD` | Sí | Inicio del rango de fechas |
| `window_end` | `YYYY-MM-DD` | Sí | Fin del rango de fechas |
| `model` | string | No | Modelo de atribución. Por defecto: `last_touch` |

**Modelos de atribución disponibles:** `last_touch`, `first_touch`, `split_50_50`, `linear`, `position_based`, `time_decay`, `last_non_direct`, `custom_weighted`, `engagement_weighted`

---

## Herramientas de descubrimiento

### list_workspaces

Lista todos los workspaces a los que tienes acceso. Usa los valores `id` devueltos como `workspace_id` en llamadas posteriores.

| Parámetro | Tipo | Requerido |
|-----------|------|-----------|
| — | — | — |

**Costo:** 0 unidades

**Devuelve:** Array de objetos `{ id, name }`.

---

### list_profiles

Lista todos los perfiles dentro de un workspace. Usa los valores `id` devueltos como `profile_id` en llamadas posteriores.

| Parámetro | Tipo | Requerido | Descripción |
|-----------|------|-----------|-------------|
| `workspace_id` | UUID | No | Se infiere si tienes un workspace |

**Costo:** 0 unidades

**Devuelve:** Array de objetos `{ id, name, domain }`.

---

## Herramientas de rendimiento

### get_performance_summary

KPIs principales para una ventana de fechas más el período previo equivalente. Es la herramienta de "dashboard en una llamada".

| Parámetro | Tipo | Requerido | Defecto |
|-----------|------|-----------|---------|
| `window_start` | fecha | Sí | — |
| `window_end` | fecha | Sí | — |
| `model` | string | No | `last_touch` |

**Costo:** 1 unidad

**Devuelve:**

```json title="Respuesta de ejemplo (data)"
{
  "current": {
    "spend": 4250.00,
    "revenue": 12800.00,
    "roas": 3.01,
    "conversions": 145,
    "visitors": 8420,
    "pageviews": 24100,
    "bounce_rate": 42.5,
    "cash_collected": 12800.00
  },
  "previous": {
    "spend": 3800.00,
    "revenue": 9200.00,
    "roas": 2.42,
    "conversions": 112,
    "visitors": 7200,
    "pageviews": 20800,
    "bounce_rate": 45.1,
    "cash_collected": 9200.00
  }
}
```

---

### compare_periods

Compara dos ventanas de fechas arbitrarias lado a lado. Útil para comparaciones año contra año, pre/post lanzamiento de campaña o comparaciones estacionales.

| Parámetro | Tipo | Requerido | Defecto |
|-----------|------|-----------|---------|
| `window_start` | fecha | Sí | — |
| `window_end` | fecha | Sí | — |
| `prev_window_start` | fecha | Sí | — |
| `prev_window_end` | fecha | Sí | — |
| `model` | string | No | `last_touch` |

**Costo:** 1 unidad

**Devuelve:** Misma estructura que `get_performance_summary` con objetos `current` y `previous` representando las dos ventanas.

---

## Herramientas de campañas

### top_campaigns

Top campañas ordenadas por ingresos atribuidos dentro de una ventana de fechas.

| Parámetro | Tipo | Requerido | Defecto |
|-----------|------|-----------|---------|
| `window_start` | fecha | Sí | — |
| `window_end` | fecha | Sí | — |
| `model` | string | No | `last_touch` |
| `limit` | número | No | `10` (máx 50) |

**Costo:** 1 unidad

**Devuelve:** Array de campañas con `name`, `platform_id`, `spend`, `revenue`, `roas`, `conversions`, `clicks`, `impressions`.

<Callout type="info" title="platform_id">
Cada campaña incluye un `platform_id` que puedes pasar a `explain_campaign` para un análisis profundo.
</Callout>

---

### top_creatives

Top anuncios individuales ordenados por ROAS, con detalles creativos (URLs de miniaturas, titulares, texto).

| Parámetro | Tipo | Requerido | Defecto |
|-----------|------|-----------|---------|
| `window_start` | fecha | Sí | — |
| `window_end` | fecha | Sí | — |
| `model` | string | No | `last_touch` |
| `limit` | número | No | `10` (máx 50) |

**Costo:** 1 unidad

**Requiere:** Conexión de Meta Ads

**Devuelve:** Array de anuncios con `name`, `spend`, `revenue`, `roas`, `ctr`, `thumbnail_url`, `headline`, `body`.

---

### explain_campaign

Análisis profundo de una sola campaña: métricas principales, tendencia diaria y anuncios constituyentes.

| Parámetro | Tipo | Requerido | Defecto |
|-----------|------|-----------|---------|
| `campaign_id` | string | Sí | — |
| `window_start` | fecha | Sí | — |
| `window_end` | fecha | Sí | — |
| `model` | string | No | `last_touch` |

**Costo:** 2 unidades

El `campaign_id` puede ser un UUID (ID interno), un `platform_id` de Meta o un nombre de campaña. La herramienta lo resuelve automáticamente.

**Devuelve:**

```json title="Respuesta de ejemplo (data)"
{
  "summary": {
    "name": "Summer Sale - Retargeting",
    "platform_id": "23851234567890",
    "spend": 1200.00,
    "revenue": 5400.00,
    "roas": 4.50,
    "conversions": 28,
    "clicks": 1840,
    "impressions": 42000
  },
  "daily_trend": [
    { "date": "2026-04-01", "spend": 180, "revenue": 720, "clicks": 260 }
  ],
  "ads": [
    { "name": "Video - Testimonial", "spend": 600, "revenue": 3200, "roas": 5.33 }
  ]
}
```

---

## Herramientas de clientes

### explain_customer_journey

Línea temporal completa de eventos para un solo cliente: clics en anuncios, vistas de página, envíos de formularios, conversiones y pagos.

| Parámetro | Tipo | Requerido | Defecto |
|-----------|------|-----------|---------|
| `customer_profile_id` | UUID | Sí | — |
| `window_start` | fecha | Sí | — |
| `window_end` | fecha | Sí | — |
| `include_sensitive` | boolean | No | `false` |
| `offset` | número | No | `0` |
| `limit` | número | No | `50` (máx 100) |

**Costo:** 2 unidades

**Devuelve:** Información del cliente (nombre, email/teléfono enmascarado) más un array de eventos ordenados cronológicamente.

<Callout type="info" title="Enmascaramiento de PII">
Por defecto, el email y teléfono se enmascaran (ej. `j***@e****.com`). Para ver valores desenmascarados, pasa `include_sensitive: true` -- requiere scope `mcp:read_pii` y modo PII del workspace en `full_default`. Ver [Privacidad y PII](/docs/mcp/privacy).
</Callout>

---

## Herramientas de análisis

### compare_attribution_models

Ejecuta la misma ventana de fechas a través de múltiples modelos de atribución para ver cómo cambia la distribución de crédito.

| Parámetro | Tipo | Requerido | Defecto |
|-----------|------|-----------|---------|
| `window_start` | fecha | Sí | — |
| `window_end` | fecha | Sí | — |
| `models` | string[] | No | `["last_touch", "first_touch", "linear", "position_based", "time_decay"]` |

**Costo:** 5 unidades

**Devuelve:** Un conjunto de resultados por modelo, cada uno con las mismas métricas a nivel de campaña.

---

### creative_fatigue_check

Detecta anuncios que muestran señales de fatiga: CTR en declive, CPM en aumento o saturación de audiencia comparado con la ventana anterior de igual duración.

| Parámetro | Tipo | Requerido | Defecto |
|-----------|------|-----------|---------|
| `window_start` | fecha | Sí | — |
| `window_end` | fecha | Sí | — |
| `model` | string | No | `last_touch` |

**Costo:** 2 unidades

**Requiere:** Conexión de Meta Ads

**Devuelve:** Array de anuncios con métricas del período actual vs anterior e indicadores de fatiga.

---

### find_anomalies

Identifica picos o caídas inusuales diarias en gasto, ingresos o tráfico usando análisis de z-score (mediana + MAD).

| Parámetro | Tipo | Requerido | Defecto |
|-----------|------|-----------|---------|
| `window_start` | fecha | Sí | — |
| `window_end` | fecha | Sí | — |
| `metrics` | string[] | No | `["spend", "cash_revenue", "visitors"]` |
| `threshold_sigma` | número | No | `2.0` |
| `model` | string | No | `last_touch` |

**Costo:** 2 unidades

**Devuelve:** Array de días anómalos con nombre de métrica, valor observado, rango esperado y magnitud del z-score.

---

### whatsapp_attribution_summary

Ingresos y conversiones atribuidos a puntos de contacto de WhatsApp (anuncios Click-to-WhatsApp o tráfico con origen WhatsApp).

| Parámetro | Tipo | Requerido | Defecto |
|-----------|------|-----------|---------|
| `window_start` | fecha | Sí | — |
| `window_end` | fecha | Sí | — |

**Costo:** 2 unidades

**Requiere:** Integración de WhatsApp

**Devuelve:** Conteo de conversaciones, conteo de conversiones, ingresos atribuidos a puntos de contacto de WhatsApp.

---

## Herramienta de write-back

### send_meta_conversions

Envía conversiones atribuidas a la API de Conversiones de Meta (CAPI) para optimización de entrega de anuncios. Esta herramienta tiene un flujo de seguridad de tres pasos: preview, dry-run, confirm.

Consulta la guía dedicada de [Write-Back](/docs/mcp/write-back) para el flujo completo, controles de seguridad y ejemplos.

| Parámetro | Tipo | Requerido | Defecto |
|-----------|------|-----------|---------|
| `mode` | `preview` \| `dry_run` \| `confirm` | Sí | — |
| `window_start` | fecha | Sí | — |
| `window_end` | fecha | Sí | — |
| `event_types` | string[] | Sí | — |
| `pixel_id` | string | Para dry_run/confirm | — |
| `idempotency_key` | string | Para confirm | — |
| `test_event_code` | string | No | — |
| `max_events` | número | No | `500` (máx 500) |

**Costo:** 10 unidades

**Requiere:** Scope `mcp:write`, conexión de Meta Ads, write-back del workspace habilitado, rol de administrador del workspace.

---

## Envelope de respuesta

Cada herramienta devuelve un envelope consistente:

```json title="Estructura de respuesta"
{
  "data": { },
  "meta": {
    "request_id": "01968a3b-...",
    "workspace_id": "...",
    "profile_id": "...",
    "window_start": "2026-04-01",
    "window_end": "2026-04-14",
    "attribution_model": "last_touch",
    "currency": "USD",
    "data_as_of": "2026-04-14T18:30:00Z",
    "freshness_by_provider": {
      "meta": "2026-04-14T18:30:00Z",
      "ghl": "2026-04-14T17:00:00Z"
    },
    "data_freshness_warning": null,
    "record_count": 12,
    "pii_level_applied": "masked"
  }
}
```

| Campo meta | Descripción |
|------------|-------------|
| `request_id` | ID único para depuración y soporte |
| `data_as_of` | Timestamp de sincronización más antiguo entre los proveedores requeridos |
| `freshness_by_provider` | Tiempo de última sincronización por proveedor |
| `data_freshness_warning` | Advertencia legible si los datos están obsoletos (>6 horas) |
| `pii_level_applied` | `masked` o `full` -- qué nivel de PII se usó realmente |

## Relacionado

<Cards>
  <Card title="Write-Back (Meta CAPI)" href="/docs/mcp/write-back">
    El flujo completo de preview/dry-run/confirm para send_meta_conversions
  </Card>
  <Card title="Referencia de errores" href="/docs/mcp/errors">
    Códigos de error y solución de problemas
  </Card>
  <Card title="Modelos de atribución" href="/docs/concepts/attribution-models">
    Cómo cada modelo distribuye el crédito
  </Card>
</Cards>

---

La herramienta `send_meta_conversions` te permite enviar conversiones atribuidas de Atribu a la API de Conversiones de Meta (CAPI). Esto mejora la optimización de entrega de anuncios de Meta alimentando datos reales de conversión al algoritmo.

<Callout type="warn" title="Acción irreversible">
Las conversiones confirmadas se envían a Meta y no pueden ser retiradas. La herramienta aplica un flujo de seguridad de tres pasos para prevenir envíos accidentales.
</Callout>

## Prerrequisitos

Antes de usar write-back, las cuatro condiciones deben cumplirse:

1. **Scope del token** -- tu token MCP debe incluir `mcp:write`
2. **Configuración del workspace** -- un administrador del workspace debe habilitar **MCP write-back** en la configuración
3. **Rol de usuario** -- debes ser **propietario** o **administrador** del workspace
4. **Conexión Meta** -- debe haber una conexión de Meta Ads activa

Si alguna condición no se cumple, la herramienta devuelve un error tipado explicando qué falta y cómo solucionarlo.

---

## El flujo de tres pasos

### Paso 1: Preview

Construye eventos de conversión localmente e inspecciónalos sin llamar a Meta.

```
Envía mis conversiones de payment_received de los últimos 7 días a Meta en modo preview
```

La herramienta devuelve:
- Conteo total de eventos
- Eventos de muestra (primeros 3) con campos mapeados
- Desglose de calidad de coincidencia (excelente / buena / débil) basado en datos de usuario disponibles

Ningún dato sale de Atribu durante el preview.

---

### Paso 2: Dry-run

Envía eventos a Meta con un código de evento de prueba. Meta valida el formato del payload y la calidad de coincidencia sin registrar conversiones reales.

```
Haz un dry-run de esas conversiones con pixel ID 1234567890
```

La herramienta devuelve:
- Conteo de `events_received` de Meta
- `fbtrace_id` para depuración en Meta Events Manager
- Cualquier advertencia de validación de Meta

<Callout type="info" title="Eventos de prueba">
Los eventos de dry-run aparecen en Meta Events Manager bajo la pestaña **Test Events**. No afectan la entrega de anuncios ni los reportes.
</Callout>

---

### Paso 3: Confirm

Envía eventos a Meta de forma real. Requiere una clave de idempotencia para prevenir envíos duplicados.

```
Confirma el envío de esas conversiones. Usa clave de idempotencia "abril-semana2-pagos"
```

La herramienta de IA debe generar una clave de idempotencia única (típicamente un UUID o string descriptivo) e incluirla en la llamada de confirm. Si la misma clave se usa dos veces, la herramienta devuelve el resultado anterior en lugar de enviar de nuevo.

---

## Mapeo de tipos de evento

Los tipos de outcome de Atribu se mapean a eventos estándar de Meta:

| Evento Atribu | Evento Meta |
|---------------|-------------|
| `payment_received` | `Purchase` |
| `order_placed` | `Purchase` |
| `closed_won` | `Purchase` |
| `appointment_booked` | `Schedule` |
| `lead_created` | `Lead` |
| `checkout_started` | `InitiateCheckout` |
| `add_to_cart` | `AddToCart` |
| `add_payment_info` | `AddPaymentInfo` |
| `view_content` | `ViewContent` |
| `search` | `Search` |

---

## Controles de seguridad

### Idempotencia

Cada llamada `confirm` requiere un `idempotency_key`. Si un confirm con la misma clave ya fue procesado para este perfil, la herramienta devuelve el resultado anterior. Esto previene envíos dobles accidentales incluso si la herramienta de IA reintenta.

### Circuit breaker

Si 3 o más operaciones `confirm` fallan para el mismo perfil en 30 minutos, la herramienta entra en estado circuit-open y rechaza nuevas llamadas de confirm. Espera al enfriamiento o investiga las fallas.

### Rastro de auditoría

Cada operación (preview, dry-run, confirm) crea un registro de auditoría inmutable con:
- Hash del payload, conteo de eventos, fechas de ventana
- IDs de envío externos (en caso de éxito)
- Estado del resultado y detalles de error
- Request ID para trazabilidad

Los registros de auditoría son visibles para administradores del workspace en el dashboard.

---

## Calidad de coincidencia

El paso de preview estima qué tan bien Meta puede hacer coincidir tus eventos con clics en anuncios:

| Calidad | Criterios |
|---------|-----------|
| **Excelente** | Tiene cookie FBP + email + teléfono + IP + user agent |
| **Buena** | Tiene email o teléfono + IP o user agent |
| **Débil** | Faltan la mayoría de identificadores -- Meta puede no hacer coincidir este evento |

Mayor calidad de coincidencia significa que Meta puede atribuir mejor la conversión al clic correcto del anuncio, mejorando la entrega futura de anuncios.

## Relacionado

<Cards>
  <Card title="Integración Meta" href="/docs/integrations/meta">
    Conecta tus cuentas de anuncios de Meta
  </Card>
  <Card title="Herramientas disponibles" href="/docs/mcp/tools">
    Las 13 herramientas MCP
  </Card>
  <Card title="Referencia de errores" href="/docs/mcp/errors">
    Códigos de error incluyendo errores específicos de write-back
  </Card>
</Cards>

---

Las ventanas de atribución definen el tiempo máximo entre un punto de contacto de marketing (como un clic en un anuncio) y una conversión (como un pago) para que el punto de contacto reciba crédito.

## Ventanas predeterminadas

| Ventana | Valor predeterminado | Rango | Qué controla |
|---------|---------------------|-------|--------------|
| **Ventana de clic** | 30 días | 1-365 días | Por cuánto tiempo después de un clic en un anuncio se puede atribuir una conversión |
| **Ventana de view-through** | 24 horas | 1-168 horas | Por cuánto tiempo después de ver un anuncio (sin hacer clic) se puede atribuir una conversión |
| **Ventana de first-touch** | 90 días | 1-365 días | Período máximo de retrospección para el modelo de atribución first-touch |

## Cómo funcionan las ventanas

Cuando un cliente convierte (ej., realiza un pago), Atribu mira hacia atrás en el tiempo buscando puntos de contacto:

- Si el cliente hizo clic en un anuncio **dentro de la ventana de clic** (30 días por defecto), el punto de contacto recibe crédito
- Si el cliente vio un anuncio **dentro de la ventana de view-through** (24 horas por defecto) pero no hizo clic, la impresión puede recibir crédito
- Para la atribución first-touch, Atribu mira tan atrás como la **ventana de first-touch** (90 días por defecto) para encontrar el punto de contacto de descubrimiento original

<Callout type="info" title="Ejemplo">
Tu ventana de clic es de 30 días. Un cliente hace clic en tu anuncio de Facebook el 1 de marzo, navega por tu sitio, se va, y vuelve el 25 de marzo para comprar. La compra SÍ se atribuye al anuncio de Facebook (25 días < ventana de 30 días). Si compra el 5 de abril, NO se atribuye (35 días > ventana de 30 días).
</Callout>

## Configurar las ventanas

<Steps>
<Step>
Ve a **Configuración > General** (o Configuración > Tracking)
</Step>
<Step>
Encuentra la sección **Ventanas de Atribución**
</Step>
<Step>
Ajusta los valores para tu negocio:
- **Ciclo de venta corto** (e-commerce): ventana de clic de 7-14 días
- **Ciclo de venta medio** (SaaS, servicios): ventana de clic de 30 días (predeterminado)
- **Ciclo de venta largo** (B2B, alto valor): ventana de clic de 60-90 días
</Step>
<Step>
Haz clic en Guardar. Los cambios se aplican a los cálculos de atribución futuros.
</Step>
</Steps>

<Callout type="warn" title="Cambiar las ventanas afecta el ROAS">
Ventanas más amplias = más conversiones atribuidas = ROAS aparente más alto. Ventanas más estrechas = menos atribuciones = ROAS más conservador. Elige basándote en tu ciclo de venta real, no en el número que quieres ver.
</Callout>

## Mejores prácticas

- **Adapta tu ciclo de venta**: Si la mayoría de los clientes compran dentro de 2 semanas, una ventana de 14 días es más precisa que 90 días
- **Sé consistente**: Cambiar las ventanas frecuentemente hace que las comparaciones período a período no sean confiables
- **Usa view-through con cautela**: Una ventana de view-through de 24 horas significa que alguien que vio (pero no hizo clic en) tu anuncio ayer recibe crédito si compra hoy. Esto puede inflar la atribución.

## Relacionado

<Cards>
  <Card title="Modelos de Atribución" href="/docs/es/concepts/attribution-models">
    Cómo se distribuye el crédito entre los puntos de contacto dentro de la ventana
  </Card>
  <Card title="Rendimiento de Anuncios" href="/docs/es/features/ads-performance">
    Ve cómo los cambios en las ventanas afectan el ROAS de las campañas
  </Card>
</Cards>

---

Los objetivos de conversión le indican a Atribu qué eventos importan para tu negocio. Cuando un evento rastreado coincide con un objetivo, se convierte en una **conversión** que se atribuye a tus campañas publicitarias.

## Objetivos predeterminados

Cuando creas un nuevo perfil, Atribu configura automáticamente estos objetivos:

| Objetivo | Tipo de Evento | Tipo de Ingreso | Descripción |
|----------|---------------|-----------------|-------------|
| Pago Recibido | `payment_received` | Cash | Pago de Stripe o MercadoPago completado |
| Lead Creado | `lead_created` | Pipeline | Nuevo lead en GHL |
| Cita Agendada | `appointment_booked` | Pipeline | Reserva de calendario completada |
| Cerrado Ganado | `closed_won` | Pipeline | Negocio marcado como ganado en GHL |
| Pedido Realizado | `order_placed` | Gross | Pedido de Shopify o e-commerce |
| Respuesta de Contacto | `contact_reply` | Pipeline | El cliente respondió |

## Tipos de ingresos

Cada objetivo tiene un **tipo de ingreso** que determina cómo se cuenta su valor:

<Callout type="warn" title="Los tipos de ingresos importan para el ROAS">
Solo los ingresos **Cash** se usan para los cálculos de ROAS. Los valores Pipeline y Gross se rastrean por separado.
</Callout>

| Tipo de Ingreso | Qué significa | ¿Se usa para ROAS? |
|-----------------|--------------|---------------------|
| **Cash** | Dinero real recibido (Stripe, MercadoPago) | Sí |
| **Pipeline** | Valor proyectado del negocio desde CRM (GHL) | No |
| **Gross** | Valor del pedido antes de reembolsos/cancelaciones | No |

## Crear objetivos personalizados

<Steps>
<Step>

Ve a la pestaña **Configuración > Resultados**

</Step>
<Step>

Haz clic en **Agregar Objetivo**

</Step>
<Step>

Configura el objetivo:
- **Nombre**: Nombre para mostrar (ej., "Prueba Gratuita Iniciada")
- **Eventos fuente**: Qué tipos de evento activan este objetivo (ej., `trial_started`)
- **Tipo de ingreso**: Cash, Pipeline o Gross
- **Elegible para atribución**: Si este objetivo debe incluirse en los modelos de atribución

</Step>
<Step>

Haz clic en Guardar. Las nuevas conversiones que coincidan con este objetivo se crearán automáticamente.

</Step>
</Steps>

## Mapeo de etapas del pipeline de GHL

Si usas GoHighLevel, Atribu mapea las etapas de tu pipeline a tipos de conversión:

- Etapas "New Lead" → `lead_created`
- Etapas "Appointment" → `appointment_booked`
- Etapas "Won" / "Closed" → `closed_won`

Puedes personalizar este mapeo en **Configuración > Resultados**. Atribu usa coincidencia de palabras clave para sugerir mapeos iniciales, pero puedes modificarlos.

<Callout type="info" title="Activación automática">
Cuando llega un nuevo evento (de Stripe, GHL o el tracker) que coincide con los tipos de evento fuente de un objetivo, Atribu crea automáticamente una conversión y ejecuta la atribución. No se necesita acción manual.
</Callout>

## Relacionado

<Cards>
  <Card title="Tipos de Ingresos" href="/docs/es/concepts/revenue-types">
    Cómo difieren los tipos de ingresos cash, pipeline y gross
  </Card>
  <Card title="Eventos Personalizados" href="/docs/es/tracking/custom-events">
    Rastrea acciones personalizadas y úsalas como objetivos de conversión
  </Card>
  <Card title="Modelos de Atribución" href="/docs/es/concepts/attribution-models">
    Cómo se distribuye el crédito entre los puntos de contacto para cada conversión
  </Card>
</Cards>

---

Atribu usa una organización de dos niveles: los **Espacios de Trabajo** contienen **Perfiles**. Cada espacio de trabajo puede tener múltiples miembros del equipo con diferentes roles.

## Espacios de Trabajo vs Perfiles

| Nivel | Qué es | Ejemplo |
|-------|--------|---------|
| **Espacio de Trabajo** | Tu organización o agencia | "Agencia de Marketing Acme" |
| **Perfil** | Un negocio/cliente específico siendo rastreado | "Cliente A - Tienda E-commerce" |

Un espacio de trabajo puede tener múltiples perfiles -- perfecto para agencias que administran múltiples clientes.

## Roles del equipo

| Rol | Dashboard | Configuración | Integraciones | Claves API | Miembros |
|-----|-----------|---------------|---------------|------------|----------|
| **Admin** | Ver | Editar | Administrar | Crear/Revocar | Invitar/Eliminar |
| **Editor** | Ver | Editar | Ver | Crear | Ver |
| **Viewer** | Ver | Ver | Ver | No | Ver |

## Invitar miembros del equipo

<Steps>
<Step>
Ve a **Configuración del Espacio de Trabajo** (haz clic en el nombre del espacio de trabajo en la barra lateral)
</Step>
<Step>
Haz clic en la pestaña **Miembros**
</Step>
<Step>
Ingresa la dirección de correo electrónico y selecciona un rol
</Step>
<Step>
Haz clic en **Invitar**. Recibirán un correo para unirse.
</Step>
</Steps>

<Callout type="info" title="Acceso a nivel de perfil">
También puedes administrar el acceso por perfil. Ve a **Configuración > Miembros** del perfil para controlar quién puede ver los datos de cada cliente.
</Callout>

## Registros de auditoría

Los administradores del espacio de trabajo pueden ver un registro de actividad de todas las acciones realizadas por los miembros del equipo:

- Ve a **Configuración del Espacio de Trabajo > Registros de Auditoría**
- Mira quién hizo qué y cuándo (cambios de conexión, actualizaciones de configuración, creación de claves)
- Se conservan los últimos 50 eventos

## Configuración del espacio de trabajo

En **Configuración del Espacio de Trabajo > General**:

- **Nombre del espacio de trabajo**: El nombre de tu organización
- **Zona horaria**: Zona horaria predeterminada para nuevos perfiles

## Relacionado

<Cards>
  <Card title="Autenticación de API" href="/docs/es/api/authentication">
    Crea y administra claves API para acceso programático
  </Card>
  <Card title="Reportes" href="/docs/es/features/reports">
    Genera reportes compartibles para miembros del equipo y clientes
  </Card>
</Cards>

---

# Captura Automatica

Atribu captura automaticamente envios de formularios, reservas completadas y redirecciones de pago -- sin codigo adicional necesario. Estos eventos capturados automaticamente son la base de la atribucion, porque conectan visitantes anonimos con personas reales.

---

## Captura automatica de formularios

Cuando un visitante envia un formulario en tu sitio web, Atribu lo detecta y hace tres cosas:

<Steps>
<Step>

### Extrae informacion de contacto

El tracker escanea los campos del formulario y busca:

- **Email** -- campos con `type="email"`, o un atributo `name`/`autocomplete` que contenga `email`
- **Telefono** -- campos con `type="tel"`, o un atributo `name` que contenga `phone` o `telephone`
- **Nombre** -- campos nombrados `first_name`, `firstName`, `given_name`, `fname`, o con `autocomplete="given-name"`
- **Apellido** -- campos nombrados `last_name`, `lastName`, `family_name`, `lname`, `surname`, o con `autocomplete="family-name"`
- **Nombre completo** -- campos nombrados `name` o con `autocomplete="name"` (se divide automaticamente en nombre y apellido)

Los campos ocultos e invisibles se ignoran.

</Step>
<Step>

### Identifica al visitante

Si se encuentra un email o numero de telefono, el tracker automaticamente llama a `identify()`. Este es el paso critico que vincula al visitante anonimo (que hizo clic en un anuncio) con su identidad real (la persona que lleno el formulario).

</Step>
<Step>

### Dispara un evento `lead_submitted`

Se registra un evento de seguimiento con el ID del formulario y la ruta de la pagina, para que puedas ver los envios de formularios en tu dashboard y usarlos como objetivos de conversion.

</Step>
</Steps>

<Callout type="info" title="Por esto funciona la atribucion">
La captura automatica de formularios es donde ocurre la magia. Antes del formulario, Atribu solo sabe "un visitante anonimo hizo clic en un anuncio de Facebook y vio 3 paginas". Despues del formulario, Atribu sabe "Jane en jane@example.com hizo clic en un anuncio de Facebook". Cuando Jane mas tarde hace un pago a traves de Stripe, Atribu lo conecta con ese clic original en el anuncio.
</Callout>

<Callout type="warn" title="Campos de formulario personalizados o inusuales">
Atribu detecta campos de email y telefono verificando los atributos `type`, `name` y `autocomplete` en elementos `<input>`, `<select>` y `<textarea>`. Si tu formulario usa nombres de campo no estandar (como `data-email` o un web component personalizado), el tracker puede no detectarlos. En ese caso, llama a `identify()` manualmente. Consulta [Identificar Usuarios](/docs/es/tracking/identify-users) para mas detalles.
</Callout>

---

## Captura de widgets de reservas

Atribu detecta cuando un visitante completa una reserva a traves de widgets de agendamiento integrados. Las siguientes plataformas son compatibles:

| Plataforma | Metodo de deteccion |
|------------|---------------------|
| **GoHighLevel** | Escucha el postMessage `msgsndr-booking-complete` del widget de GHL |
| **Calendly** | Escucha el postMessage `calendly.event_scheduled` |
| **Cal.com** | Escucha el postMessage `bookingSuccessful` |
| **Basado en hash** | Detecta cambios de hash en la URL como `#booking`, `#booked`, `#scheduled`, `#confirmed` o `#thank-you` |

Cuando se detecta una reserva:

1. La informacion de contacto se extrae cuando esta disponible (email, nombre, telefono de los datos del mensaje del widget)
2. Se llama a `identify()` automaticamente si la informacion de contacto esta presente
3. Se dispara un evento `appointment_booked`

<Callout type="info" title="Una reserva por sesion">
Para evitar duplicados, el evento de reserva se dispara solo una vez por sesion. Si un visitante de alguna manera activa el mensaje de reserva completada multiples veces, solo se registra la primera.
</Callout>

### Patrones de reserva personalizados

Si usas un widget de reservas que no es compatible nativamente, puedes registrar patrones de deteccion personalizados:

```js title="Patron de reserva personalizado"
window.ATRIBU_BOOKING_PATTERNS = [
  { match: "booking-confirmed", widget: "my_scheduler" }
];
```

El tracker buscara la cadena `match` en cualquier dato de `postMessage` y disparara el evento `appointment_booked` cuando lo encuentre.

---

## Captura de checkout de Stripe

Cuando usas Stripe Payment Links, los clientes son redirigidos de vuelta a tu sitio web despues de completar el pago. La URL de redireccion contiene un parametro `session_id` como:

```text title="URL de redireccion de Stripe"
https://yoursite.com/thank-you?session_id=cs_live_a1b2c3...
```

Atribu detecta este parametro y:

1. Dispara un evento `stripe_checkout_completed` con el ID de la sesion de checkout de Stripe
2. Limpia el parametro `session_id` de la URL (usando `history.replaceState`) para que el evento no se dispare de nuevo si el visitante actualiza la pagina

Esto permite a Atribu conectar el pago con la sesion del navegador, lo que lo vincula al clic original en el anuncio.

<Callout type="info" title="Parametros de URL compatibles">
El tracker verifica `session_id`, `checkout_session` y `checkout_session_id` en la URL. Solo se procesan los valores que comienzan con `cs_` (el prefijo de sesion de checkout de Stripe).
</Callout>

---

## Siguientes pasos

<Cards>
  <Card title="Identificar Usuarios" href="/docs/es/tracking/identify-users">
    Identificacion manual para formularios personalizados y flujos de inicio de sesion
  </Card>
  <Card title="Eventos Personalizados" href="/docs/es/tracking/custom-events">
    Rastrea cualquier accion como un evento personalizado
  </Card>
  <Card title="Resolucion de Identidad" href="/docs/es/concepts/identity-resolution">
    Como los visitantes anonimos se convierten en clientes conocidos a traves de canales
  </Card>
  <Card title="Payment Links" href="/docs/es/features/payment-links">
    Crea Stripe Payment Links con seguimiento de atribucion integrado
  </Card>
</Cards>

---

# Dominio Personalizado

Usa tu propio dominio para el seguimiento y mejora la precision de los datos evitando bloqueadores de anuncios.

---

## Por que usar un dominio personalizado

Los bloqueadores de anuncios y las funciones de privacidad del navegador a veces bloquean scripts de seguimiento de terceros. Cuando tu seguimiento se ejecuta en un dominio de terceros, los datos de algunos visitantes pueden perderse.

Al configurar un subdominio personalizado (por ejemplo, `t.tudominio.com`), el tracker se convierte en un **script de primera parte** -- se ejecuta en tu propio dominio, igual que el resto de tu sitio web. La mayoria de los bloqueadores de anuncios no bloquean solicitudes de primera parte, por lo que capturas datos mas precisos.

---

## Configuracion

<Steps>
<Step>

### Abre la configuracion de seguimiento

Ve a **Configuracion > Seguimiento** en tu dashboard de Atribu.

</Step>
<Step>

### Ingresa tu subdominio personalizado

Elige un subdominio en tu dominio, por ejemplo:

```text title="Ejemplos de subdominios"
t.tudominio.com
track.tudominio.com
data.tudominio.com
```

Cualquier subdominio funciona. Mantenlo corto -- `t` es una opcion popular.

</Step>
<Step>

### Agrega el registro DNS

Agrega un registro **CNAME** en tu proveedor de DNS apuntando tu subdominio al servidor de seguimiento de Atribu:

| Tipo | Nombre | Destino |
|------|--------|---------|
| CNAME | `t` | `track.atribu.app` |

Los pasos exactos dependen de tu proveedor de DNS (Cloudflare, GoDaddy, Namecheap, etc.). Busca "Agregar registro DNS" o "Gestionar DNS" en el panel de tu proveedor.

</Step>
<Step>

### Verifica la conexion

Haz clic en **Verificar** en Atribu. El sistema comprueba que tu registro DNS esta correctamente configurado y apunta al destino correcto.

Una vez verificado, tu fragmento de seguimiento usa automaticamente el dominio personalizado para toda la recopilacion de datos.

</Step>
</Steps>

---

## Domain Connect (configuracion automatica)

Si tu proveedor de DNS soporta el protocolo Domain Connect, Atribu puede configurar el registro DNS por ti automaticamente.

<Steps>
<Step>

### Ingresa tu subdominio

Escribe tu subdominio deseado en la configuracion de seguimiento.

</Step>
<Step>

### Haz clic en "Configuracion Automatica"

Atribu verifica si tu proveedor de DNS soporta Domain Connect. Si es asi, veras una opcion de "Configuracion Automatica".

</Step>
<Step>

### Autoriza el cambio de DNS

Seras redirigido a tu proveedor de DNS para aprobar el registro CNAME. Una vez aprobado, el registro se crea automaticamente y la verificacion ocurre de inmediato.

</Step>
</Steps>

<Callout type="info" title="Soporte de Domain Connect">
Domain Connect es compatible con muchos proveedores de DNS importantes incluyendo GoDaddy, 1&1 IONOS y otros. Si tu proveedor no lo soporta, usa la configuracion manual de CNAME descrita anteriormente.
</Callout>

---

## Solucion de problemas

### "DNS no verificado" despues de agregar el registro

Los cambios de DNS pueden tardar en propagarse. En la mayoria de los casos toma de 1 a 5 minutos, pero puede tardar hasta **48 horas** dependiendo de tu proveedor de DNS y la configuracion de TTL. Espera unos minutos y haz clic en **Verificar** de nuevo.

### Usando Cloudflare

Si usas Cloudflare, tienes dos opciones:

- **Solo DNS (nube gris)** -- El CNAME se resuelve normalmente. La verificacion funciona como se espera.
- **Proxied (nube naranja)** -- Cloudflare aplana el CNAME a un registro A/AAAA. Atribu soporta esto y verifica comprobando que las direcciones IP resueltas coincidan.

Ambas opciones funcionan. No se necesita ninguna configuracion especial.

### Eliminar un dominio personalizado

Ve a **Configuracion > Seguimiento**, encuentra tu dominio personalizado y haz clic en **Eliminar**. Tu fragmento de seguimiento volvera a usar el dominio predeterminado de Atribu.

---

## Siguientes pasos

<Cards>
  <Card title="Como Funciona el Seguimiento" href="/docs/es/tracking/how-tracking-works">
    Entiende visitantes, sesiones y cookies
  </Card>
  <Card title="Parametros UTM" href="/docs/es/tracking/utm-parameters">
    Etiqueta tus fuentes de trafico para atribucion precisa
  </Card>
  <Card title="Calidad de Datos" href="/docs/es/features/data-quality">
    Monitorea la cobertura del seguimiento y corrige brechas de atribucion
  </Card>
</Cards>

---

# Eventos Personalizados

Mas alla de las vistas de pagina automaticas y capturas de formularios, puedes rastrear cualquier accion en tu sitio web como un evento personalizado. Usa eventos personalizados para medir clics en botones, reproducciones de video, vistas de la pagina de precios o cualquier otra cosa que sea importante para tu negocio.

---

## Seguimiento basico de eventos

Llama a `window.atribuTracker.track()` con un nombre de evento y propiedades opcionales:

```js title="Rastrear un evento personalizado"
window.atribuTracker.track("button_clicked", {
  payload: {
    button_name: "pricing_cta",
    page: "/pricing",
  },
});
```

El nombre del evento es el primer argumento. El segundo argumento es un objeto con una clave `payload` que contiene las propiedades que quieras adjuntar al evento.

---

## Seguimiento de ingresos

Para eventos que involucran dinero (compras, upgrades, upsells), usa `trackRevenue()` para incluir el monto y la moneda:

```js title="Rastrear una compra con ingresos"
window.atribuTracker.trackRevenue("purchase", 99.99, "USD", {
  product: "Pro Plan",
  billing_cycle: "monthly",
});
```

| Parametro | Tipo | Descripcion |
|-----------|------|-------------|
| `eventName` | string | Nombre del evento |
| `amount` | number | Monto de ingresos |
| `currency` | string | Codigo de moneda ISO (USD, EUR, MXN, etc.) |
| `data` | object | Propiedades adicionales opcionales |

---

## Seguimiento de vistas de pagina

Las vistas de pagina se rastrean automaticamente en cada carga de pagina. Para aplicaciones de pagina unica (SPAs) donde la pagina cambia sin una recarga completa, puedes disparar vistas de pagina manualmente:

```js title="Vista de pagina manual para navegacion SPA"
window.atribuTracker.page();
```

<Callout type="info" title="Auto-deteccion de SPA">
Atribu detecta automaticamente la navegacion por `pushState` y `replaceState` en la mayoria de las SPAs (React, Next.js, Vue, etc.) y dispara vistas de pagina sin llamadas manuales. Solo usa vistas de pagina manuales si la auto-deteccion no esta funcionando para tu configuracion.
</Callout>

---

## Mejores practicas para nombres de eventos

Buenos nombres de eventos hacen que tus reportes sean mas faciles de leer y tus objetivos de conversion mas faciles de configurar.

**Usa `snake_case`**

```js title="Buenos nombres de eventos"
window.atribuTracker.track("video_played", { payload: { video_id: "abc" } });
window.atribuTracker.track("pricing_viewed", { payload: { plan: "pro" } });
window.atribuTracker.track("checkout_started", { payload: { cart_value: 149 } });
```

**Se especifico**

| En lugar de | Usa |
|-------------|-----|
| `click` | `pricing_cta_clicked` |
| `submit` | `contact_form_submitted` |
| `view` | `case_study_viewed` |
| `purchase` | `subscription_purchased` |

**Se consistente**

Elige una convencion de nombres y usala en todos lados. Si tu primer evento es `form_submitted`, no cambies a `formSubmit` o `FormSubmitted` despues.

---

## Usar eventos personalizados como objetivos de conversion

Cualquier evento personalizado puede convertirse en un objetivo de conversion en Atribu. Esto significa que el motor de atribucion rastreara cuales anuncios y campanas impulsan esa accion especifica.

<Steps>
<Step>

### Rastrea el evento en tu sitio

```js title="Rastrear un evento de conversion personalizado"
window.atribuTracker.track("demo_requested", {
  payload: {
    plan_interest: "enterprise",
  },
});
```

</Step>
<Step>

### Crea una definicion de conversion en Atribu

Ve a **Configuracion > Conversiones** y crea una nueva definicion de conversion. Establece el **Nombre del evento fuente** como `demo_requested`. Atribu comenzara a atribuir este evento a tus campanas.

</Step>
</Steps>

<Callout type="info" title="Los eventos automaticos tambien funcionan">
Tambien puedes crear definiciones de conversion para eventos capturados automaticamente como `lead_submitted` y `appointment_booked`. Cualquier evento que fluya a traves del tracker puede convertirse en un objetivo de atribucion.
</Callout>

---

## Metodos de seguimiento adicionales

### Establecer un ID de usuario

Si tu aplicacion tiene su propio sistema de IDs de usuario, puedes vincularlo al visitante de Atribu:

```js title="Establecer un ID de usuario personalizado"
window.atribuTracker.setUserId("user_12345");
```

### Observar impresiones de elementos

Rastrea cuando un elemento especifico se vuelve visible en pantalla (util para medir impresiones de anuncios o visibilidad de contenido):

```js title="Rastrear visibilidad de elementos"
var cleanup = window.atribuTracker.observeImpression("#hero-banner", {
  payload: { banner_variant: "spring_sale" },
});

// Llama a cleanup() para dejar de observar
```

El evento se dispara una vez cuando el elemento es al menos 50% visible. Puedes personalizar el umbral:

```js title="Umbral de visibilidad personalizado"
window.atribuTracker.observeImpression("#cta-section", {}, {
  threshold: 0.75, // Disparar cuando sea 75% visible
});
```

---

## Siguientes pasos

<Cards>
  <Card title="Identificar Usuarios" href="/docs/es/tracking/identify-users">
    Conecta visitantes anonimos con personas reales
  </Card>
  <Card title="Parametros UTM" href="/docs/es/tracking/utm-parameters">
    Etiqueta tus fuentes de trafico para atribucion precisa
  </Card>
  <Card title="Objetivos de Conversion" href="/docs/es/settings/goals">
    Convierte eventos personalizados en objetivos de conversion para atribucion
  </Card>
</Cards>

---

Instrucciones detalladas de configuracion para cada framework. El paquete `@atribu/tracker` funciona con cualquier framework de JavaScript gracias a su diseno seguro para SSR.

## React

Crea un componente proveedor que inicialice el tracker una sola vez:

```tsx title="src/components/AtribuProvider.tsx"
import { useEffect } from "react";
import { init } from "@atribu/tracker";

export function AtribuProvider({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    init({
      trackingKey: "trk_live_your_key",
      // Opcional: personalizar comportamiento
      sessionTimeoutMinutes: 30,
      enableWebVitals: true,
    });
  }, []);

  return <>{children}</>;
}
```

```tsx title="src/main.tsx"
import { AtribuProvider } from "./components/AtribuProvider";

function App() {
  return (
    <AtribuProvider>
      <YourApp />
    </AtribuProvider>
  );
}
```

### Rastrear eventos en componentes

```tsx title="src/components/PricingButton.tsx"
import { track } from "@atribu/tracker";

export function PricingButton() {
  return (
    <button onClick={() => track("pricing_clicked", { plan: "pro" })}>
      Ver Precios
    </button>
  );
}
```

### Identificar usuarios despues del envio de formulario

```tsx title="src/components/ContactForm.tsx"
import { identify } from "@atribu/tracker";

export function ContactForm() {
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const data = new FormData(e.currentTarget);

    identify({
      email: data.get("email") as string,
      firstName: data.get("name") as string,
    });

    // Enviar formulario...
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="Nombre" />
      <input name="email" type="email" placeholder="Email" />
      <button type="submit">Enviar</button>
    </form>
  );
}
```

---

## Next.js (App Router)

<Callout type="warn" title="Se requiere componente de cliente">
El tracker usa APIs del navegador (`window`, `localStorage`). Debe inicializarse en un Client Component con la directiva `"use client"`.
</Callout>

```tsx title="src/components/AtribuProvider.tsx"
"use client";

import { useEffect } from "react";
import { init } from "@atribu/tracker";

export function AtribuProvider({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    init({ trackingKey: "trk_live_your_key" });
  }, []);

  return <>{children}</>;
}
```

```tsx title="src/app/layout.tsx"
import { AtribuProvider } from "@/components/AtribuProvider";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <AtribuProvider>{children}</AtribuProvider>
      </body>
    </html>
  );
}
```

<Callout type="info" title="Seguro para SSR">
Si importas accidentalmente `@atribu/tracker` en un Server Component, `init()` devuelve un cliente no-op silencioso. Sin errores, sin fallos: simplemente no hace nada hasta que se ejecuta en el navegador.
</Callout>

---

## Vue 3

```vue title="src/App.vue"
<script setup lang="ts">
import { onMounted } from "vue";
import { init } from "@atribu/tracker";

onMounted(() => {
  init({ trackingKey: "trk_live_your_key" });
});
</script>

<template>
  <RouterView />
</template>
```

### Rastrear eventos

```vue title="src/components/PricingCard.vue"
<script setup lang="ts">
import { track } from "@atribu/tracker";

function handleClick() {
  track("pricing_clicked", { plan: "pro" });
}
</script>

<template>
  <button @click="handleClick">Ver Precios</button>
</template>
```

---

## Svelte

```svelte title="src/App.svelte"
<script>
  import { onMount } from "svelte";
  import { init } from "@atribu/tracker";

  onMount(() => {
    init({ trackingKey: "trk_live_your_key" });
  });
</script>

<slot />
```

---

## JavaScript Vanilla

```html title="index.html"
<script type="module">
  import { init, track, identify } from "@atribu/tracker";

  init({ trackingKey: "trk_live_your_key" });

  // Rastrear un evento personalizado
  document.querySelector("#cta").addEventListener("click", () => {
    track("cta_clicked");
  });

  // Identificar despues de enviar formulario
  document.querySelector("form").addEventListener("submit", (e) => {
    const email = e.target.querySelector('[name="email"]').value;
    identify({ email });
  });
</script>
```

---

## Navegacion SPA

El tracker detecta automaticamente la navegacion de Single Page Apps mediante:
- Intercepcion de `history.pushState` y `history.replaceState`
- Listener del evento `popstate`
- Listener del evento `hashchange`

Cada navegacion dispara un nuevo evento `page_view` y reinicia el rastreo de engagement (profundidad de scroll, tiempo en pagina). No se necesita configuracion adicional.

---

## Entornos SSR

En renderizado del lado del servidor (Next.js, Nuxt, SvelteKit SSR), el tracker maneja la ausencia de `window` de forma elegante:

1. `init()` detecta `typeof window === "undefined"`
2. Devuelve un **cliente no-op** donde todos los metodos (`track`, `identify`, etc.) son funciones vacias
3. No se lanzan errores, no hay fallos de `window is not defined`
4. Cuando el codigo se ejecuta posteriormente en el navegador, `init()` se activa normalmente

Esto significa que puedes importar y llamar funciones del tracker de forma segura en cualquier lugar, servidor o cliente.

## Relacionado

<Cards>
  <Card title="Paquete npm" href="/docs/es/tracking/npm-package">
    Referencia completa de la API y opciones de configuracion
  </Card>
  <Card title="Instalar Tracking" href="/docs/es/getting-started/install-tracker">
    Instalacion con script tag (alternativa a npm)
  </Card>
</Cards>

---

# Como Funciona el Seguimiento

Cuando alguien visita tu sitio web, el tracker de Atribu registra lo que hace -- cuales paginas ve, de donde vino y como interactua con tu sitio. Estos datos alimentan tu dashboard, reportes de atribucion y analisis de ingresos.

```mermaid
flowchart LR
    A["Browser"] -->|page view| B["Atribu Tracker\n(JavaScript)"]
    B -->|events| C["/api/tracking/collect"]
    C --> D["Enrichment Pipeline"]
    D --> E["events_enriched"]
    E --> F["Sessions & Views"]
    F --> G["Dashboard"]
```

---

## Dos IDs, dos conceptos

Atribu usa dos identificadores para entender tu trafico. Ambos se generan automaticamente -- no requieren configuracion.

### ID de Visitante (anonymous_id)

Piensa en esto como una huella digital para cada dispositivo. La misma persona visitando tu sitio desde el mismo navegador y dispositivo siempre sera reconocida como el mismo visitante. Este ID se almacena en el navegador por **1 ano**, asi que los visitantes recurrentes se cuentan con precision.

- Almacenado en `localStorage` como `atribu_anon_id`
- Tambien se escribe como una cookie de primera parte (`atribu_visitor_id`) para que el codigo del lado del servidor pueda leerlo
- Uno por combinacion de dispositivo/navegador -- si alguien visita desde su telefono y su laptop, eso cuenta como dos visitantes

### ID de Sesion

Una sesion representa una sola **visita**. Comienza cuando alguien llega a tu sitio y termina despues de **30 minutos de inactividad**. Si regresan una hora despues, eso es una nueva sesion (una nueva visita), pero el mismo visitante.

- Almacenado en `localStorage` como `atribu_session_id`
- Tambien se escribe como una cookie (`atribu_session_id`) con una expiracion de 30 minutos
- Una nueva sesion tambien comienza cuando la fuente de marketing cambia (por ejemplo, un visitante hace clic en un anuncio diferente)

<Callout type="info" title="Visitantes vs. sesiones">
Tu dashboard muestra ambas metricas. **Visitantes** te dice cuantas personas unicas llegaron a tu sitio. **Sesiones** te dice cuantas visitas totales ocurrieron. Un visitante puede tener muchas sesiones.
</Callout>

---

## Que se captura en cada vista de pagina

Cada vez que un visitante carga una pagina, Atribu registra:

| Dato | Ejemplo |
|------|---------|
| **URL de la pagina** | `/pricing`, `/blog/how-to-run-ads` |
| **Referrer** | `google.com`, `facebook.com`, directo |
| **Parametros UTM** | `utm_source=facebook`, `utm_medium=paid` |
| **IDs de clic** | `fbclid`, `gclid`, `msclkid`, `ttclid` |
| **Tipo de dispositivo** | Escritorio, movil, tablet |
| **Navegador** | Chrome, Safari, Firefox |
| **Pais** | Basado en la direccion IP |

---

## Como funcionan las sesiones

<Steps>
<Step>

### El visitante llega

Cuando alguien aterriza en tu sitio, el tracker verifica si existe una sesion activa. Si la ultima actividad fue hace mas de 30 minutos (o no existe una sesion), comienza una nueva sesion.

</Step>
<Step>

### Se rastrea la actividad

Cada vista de pagina, envio de formulario y evento personalizado actualiza la marca de tiempo de "ultima vez visto" de la sesion. Mientras el visitante siga interactuando dentro de ventanas de 30 minutos, todo cuenta como una sola sesion.

</Step>
<Step>

### La sesion termina

Despues de 30 minutos de inactividad (sin vistas de pagina, sin clics, sin eventos), la sesion expira. Si el visitante regresa despues de eso, comienza una nueva sesion.

</Step>
</Steps>

### Configurar el tiempo de expiracion de sesion

El tiempo de expiracion predeterminado es de 30 minutos, lo cual funciona bien para la mayoria de los sitios web. Si necesitas un valor diferente, puedes configurarlo en tu fragmento de seguimiento:

```js title="Tiempo de expiracion de sesion personalizado (15 minutos)"
window.ATRIBU_SESSION_TIMEOUT_MINUTES = 15;
```

Rango valido: 1 a 120 minutos.

### Deteccion de cambio de fuente

Por defecto, una nueva sesion comienza solo despues de inactividad. Tambien puedes iniciar una nueva sesion cuando la fuente de marketing cambia -- por ejemplo, si un visitante hace clic en un anuncio de Google y luego hace clic en un anuncio de Facebook 10 minutos despues:

```js title="Habilitar division de sesion por cambio de fuente"
window.ATRIBU_SESSION_MODE = "inactivity_or_source_change";
```

---

## Cookies explicadas

Atribu escribe dos cookies de primera parte en tu dominio:

| Cookie | Duracion | Proposito |
|--------|----------|-----------|
| `atribu_visitor_id` | 1 ano | Almacena el ID anonimo del visitante para que el codigo del lado del servidor (como flujos de checkout) pueda leerlo y pasarlo a proveedores de pago |
| `atribu_session_id` | 30 minutos | Almacena el ID de la sesion actual por la misma razon |

Estas cookies son de **primera parte** (configuradas en tu propio dominio), **SameSite=Lax**, y contienen solo identificadores aleatorios -- sin datos personales.

<Callout type="info" title="Privacidad por defecto">
Atribu no recopila ninguna informacion personal hasta que un visitante se identifica voluntariamente (al llenar un formulario, agendar una cita, etc.). Hasta ese punto, los visitantes son completamente anonimos -- Atribu solo sabe que "alguien en este dispositivo" vio ciertas paginas. Aprende como funciona esta transicion de anonimo a conocido en [Resolucion de Identidad](/docs/es/concepts/identity-resolution).
</Callout>

---

## Siguientes pasos

<Cards>
  <Card title="Captura Automatica" href="/docs/es/tracking/auto-capture">
    Como Atribu detecta automaticamente envios de formularios, reservas y pagos
  </Card>
  <Card title="Identificar Usuarios" href="/docs/es/tracking/identify-users">
    Conecta visitantes anonimos con personas reales para atribucion completa
  </Card>
  <Card title="Parametros UTM" href="/docs/es/tracking/utm-parameters">
    Etiqueta tus fuentes de trafico para una clasificacion precisa de canales
  </Card>
  <Card title="Dominio Personalizado" href="/docs/es/tracking/custom-domain">
    Evita bloqueadores de anuncios enrutando el seguimiento a traves de tu propio dominio
  </Card>
</Cards>

---

# Identificar Usuarios

La funcion `identify()` es la parte mas importante del seguimiento de Atribu. Conecta a un visitante anonimo del sitio web con una persona real -- y esa conexion es lo que hace posible la atribucion.

---

## Por que importa la identificacion

Sin identificacion, Atribu ve dos mundos desconectados:

- **Lado del sitio web**: Un visitante anonimo hizo clic en un anuncio de Facebook, vio 3 paginas y se fue
- **Lado del negocio**: Jane Smith pago $500 a traves de Stripe

La llamada a `identify()` conecta esta brecha. Una vez que un visitante es identificado (generalmente a traves de un formulario), Atribu puede trazar todo el recorrido desde el clic en el anuncio hasta el pago.

---

## Como funciona

<Steps>
<Step>

### El visitante hace clic en un anuncio

Un cliente potencial hace clic en tu anuncio de Facebook y aterriza en tu sitio web. Atribu le asigna un `anonymous_id` -- un identificador aleatorio vinculado a su dispositivo.

</Step>
<Step>

### El visitante llena un formulario

El visitante llena un formulario de contacto con su email. El tracker automaticamente llama a `identify({ email: "jane@example.com" })`.

</Step>
<Step>

### Atribu crea un perfil de cliente

Detras de escena, Atribu crea un perfil de cliente que vincula el `anonymous_id` con la direccion de email. El visitante ya no es anonimo -- es Jane Smith.

</Step>
<Step>

### Un pago llega despues

Cuando Stripe envia un evento de pago para `jane@example.com`, Atribu busca el perfil del cliente, encuentra el `anonymous_id` vinculado y lo rastrea hasta el clic original en el anuncio. Se logra la atribucion completa.

</Step>
</Steps>

---

## Identificacion automatica

En la mayoria de los casos, no necesitas llamar a `identify()` tu mismo. La [captura automatica](/docs/es/tracking/auto-capture) de Atribu lo maneja por ti:

- **Envios de formularios** -- el email y telefono se extraen de los campos del formulario e `identify()` se llama antes de que se dispare el evento `lead_submitted`
- **Widgets de reservas** -- las reservas completadas de GoHighLevel, Calendly y Cal.com extraen la informacion de contacto y llaman a `identify()` automaticamente

Si tu sitio usa formularios HTML estandar o widgets de reservas compatibles, la identificacion sucede sin ningun codigo adicional.

---

## Identificacion manual

Para formularios personalizados, flujos de inicio de sesion u otros escenarios donde la captura automatica no aplica, llama a `identify()` directamente.

<Tabs items={["Despues de un formulario personalizado", "Despues de login/registro", "Con numero de telefono"]}>
<Tab value="Despues de un formulario personalizado">
```js title="Identificar despues de un formulario personalizado"
document.querySelector("#my-form").addEventListener("submit", function () {
  var email = document.querySelector("#email-field").value;
  var name = document.querySelector("#name-field").value;

  window.atribuTracker.identify({
    email: email,
    firstName: name.split(" ")[0],
    lastName: name.split(" ").slice(1).join(" "),
  });
});
```
</Tab>
<Tab value="Despues de login/registro">
```js title="Identificar despues del inicio de sesion"
async function onLoginSuccess(user) {
  window.atribuTracker.identify({
    email: user.email,
    firstName: user.firstName,
    lastName: user.lastName,
    userId: user.id,
  });
}
```
</Tab>
<Tab value="Con numero de telefono">
```js title="Identificar con numero de telefono"
window.atribuTracker.identify({
  phone: "+1-555-123-4567",
  firstName: "Jane",
});
```
</Tab>
</Tabs>

### Campos aceptados

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `email` | string | Direccion de email (maxima prioridad para coincidencia) |
| `phone` | string | Numero de telefono (segunda prioridad) |
| `firstName` | string | Nombre |
| `lastName` | string | Apellido |
| `userId` | string | Tu ID de usuario interno |

---

## Prioridad de identidad

Al vincular visitantes con perfiles de cliente, Atribu usa este orden de prioridad:

1. **Email** -- el identificador mas fuerte. Si dos eventos comparten el mismo email, son la misma persona.
2. **Numero de telefono** -- se usa cuando el email no esta disponible.
3. **IDs externos** -- IDs especificos del proveedor como IDs de cliente de Stripe o IDs de contacto de GHL.

Atribu normaliza todos los identificadores antes de la coincidencia (convirtiendo emails a minusculas, eliminando formato de telefono), asi que `Jane@Example.com` y `jane@example.com` se tratan como la misma persona.

---

## Se puede llamar multiples veces de forma segura

Llamar a `identify()` mas de una vez es seguro y se recomienda. Si un visitante llena un formulario con su email y mas tarde proporciona su numero de telefono, llamar a `identify()` de nuevo agrega el numero de telefono al perfil existente sin sobrescribir el email.

```js title="Enriquecimiento progresivo"
// Primer formulario: solo email
window.atribuTracker.identify({ email: "jane@example.com" });

// Despues: numero de telefono capturado de un formulario diferente
window.atribuTracker.identify({ phone: "+1-555-123-4567" });

// Ambos identificadores ahora estan vinculados al mismo perfil de cliente
```

<Callout type="error" title="Critico: cada punto de conversion necesita identificacion">
Sin `identify()`, los eventos del lado del servidor como pagos (Stripe, MercadoPago) y eventos de CRM (GoHighLevel) no pueden ser atribuidos a clics en anuncios. Asegurate de que cada formulario, flujo de checkout o punto de conversion en tu sitio llame a `identify()` -- ya sea a traves de captura automatica o manualmente.
</Callout>

---

## Siguientes pasos

<Cards>
  <Card title="Captura Automatica" href="/docs/es/tracking/auto-capture">
    Ve cuales formularios y widgets se capturan automaticamente
  </Card>
  <Card title="Eventos Personalizados" href="/docs/es/tracking/custom-events">
    Rastrea acciones personalizadas mas alla de envios de formularios
  </Card>
  <Card title="Resolucion de Identidad" href="/docs/es/concepts/identity-resolution">
    Como el grafo de identidad conecta visitantes, emails y pagos
  </Card>
  <Card title="Calidad de Datos" href="/docs/es/features/data-quality">
    Monitorea la cobertura de identificacion y corrige datos faltantes
  </Card>
</Cards>

---

El paquete npm `@atribu/tracker` es el SDK oficial de TypeScript para Atribu. Incluye el runtime completo del tracker con configuracion tipada, compatible con cualquier framework y seguridad automatica para SSR.

## Instalacion

```bash title="Instalar"
npm install @atribu/tracker
```

## Inicio rapido

<Tabs items={["React", "Next.js", "Vue 3", "Vanilla JS"]}>
<Tab value="React">
```tsx title="src/App.tsx"
import { useEffect } from "react";
import { init } from "@atribu/tracker";

function App() {
  useEffect(() => {
    init({ trackingKey: "trk_live_your_key" });
  }, []);

  return <div>Tu aplicacion</div>;
}
```
</Tab>
<Tab value="Next.js">
```tsx title="src/components/AtribuProvider.tsx"
"use client";

import { useEffect } from "react";
import { init } from "@atribu/tracker";

export function AtribuProvider({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    init({ trackingKey: "trk_live_your_key" });
  }, []);

  return <>{children}</>;
}
```

Luego envuelve tu layout raiz:
```tsx title="src/app/layout.tsx"
import { AtribuProvider } from "@/components/AtribuProvider";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <AtribuProvider>{children}</AtribuProvider>
      </body>
    </html>
  );
}
```
</Tab>
<Tab value="Vue 3">
```vue title="src/App.vue"
<script setup>
import { onMounted } from "vue";
import { init } from "@atribu/tracker";

onMounted(() => {
  init({ trackingKey: "trk_live_your_key" });
});
</script>
```
</Tab>
<Tab value="Vanilla JS">
```html title="index.html"
<script type="module">
  import { init } from "@atribu/tracker";
  init({ trackingKey: "trk_live_your_key" });
</script>
```
</Tab>
</Tabs>

## Configuracion

Todas las opciones se pasan a `init()`:

| Opcion | Tipo | Valor por defecto | Descripcion |
|--------|------|-------------------|-------------|
| `trackingKey` | `string` | **requerido** | Tu clave de ingesta desde Configuracion > Tracking |
| `apiHost` | `string` | `window.location.origin` | Origen para el endpoint de recoleccion |
| `trackingEndpoint` | `string` | `{apiHost}/api/tracking/collect` | URL completa para sobreescribir el endpoint de tracking |
| `interceptMetaFbq` | `boolean` | `true` | Interceptar llamadas `fbq()` del Meta Pixel para deduplicacion del lado del servidor |
| `metaBridgePageview` | `boolean` | `false` | Tambien replicar eventos PageView de Meta |
| `sessionTimeoutMinutes` | `number` | `30` | Timeout de inactividad de sesion (1-120 min) |
| `sessionMode` | `string` | `"inactivity_only"` | `"inactivity_only"` o `"inactivity_or_source_change"` |
| `heartbeatIntervalSeconds` | `number \| false` | `60` | Intervalo de heartbeat de engagement (checkpoint de recuperación). `0` o `false` para desactivar |
| `enableClickQuality` | `boolean` | `true` | Rastrear rage clicks y dead clicks |
| `enableWebVitals` | `boolean` | `true` | Capturar Core Web Vitals (LCP, FCP, CLS, INP, TTFB) |
| `enableCtaTracking` | `boolean` | `true` | Auto-detectar y rastrear visibilidad de botones CTA |
| `enableVideoTracking` | `boolean` | `true` | Auto-detectar elementos `<video>` y rastrear hitos |
| `ignoredPages` | `string[]` | — | Patrones de URL a excluir del tracking (ej., `["/admin/*"]`) |
| `customProperties` | `object \| function` | — | Propiedades estaticas o funcion que se combinan con cada evento |
| `transformRequest` | `function` | — | Middleware para modificar o suprimir eventos antes de enviarlos |

### Propiedades personalizadas

```ts title="Propiedades estaticas"
init({
  trackingKey: "trk_live_...",
  customProperties: {
    environment: "production",
    appVersion: "2.1.0",
  },
});
```

```ts title="Propiedades dinamicas (funcion)"
init({
  trackingKey: "trk_live_...",
  customProperties: (ctx) => ({
    currentPath: ctx.path,
    isLoggedIn: !!localStorage.getItem("token"),
  }),
});
```

### Transform request (middleware)

```ts title="Filtrar eventos"
init({
  trackingKey: "trk_live_...",
  transformRequest: (event) => {
    // Suprimir eventos de paginas de administracion
    if (event.path?.startsWith("/admin")) return null;
    // Agregar header personalizado
    event.payload.buildId = "abc123";
    return event;
  },
});
```

## Rastreo de eventos

```ts title="Evento basico"
import { track } from "@atribu/tracker";

track("button_clicked", { buttonName: "pricing_cta" });
```

```ts title="Evento de ingreso"
import { trackRevenue } from "@atribu/tracker";

trackRevenue("purchase", 99.99, "USD", { plan: "Pro" });
```

```ts title="Evento auto-descriptivo (con esquema)"
import { trackSelfDescribing } from "@atribu/tracker";

trackSelfDescribing({
  eventSchema: "com.atribu.checkout",
  schemaVersion: 1,
  payload: { step: "payment", method: "credit_card" },
});
```

## Identificacion de usuarios

```ts title="Identificar despues del envio de formulario"
import { identify } from "@atribu/tracker";

identify({
  email: "jane@example.com",
  firstName: "Jane",
  lastName: "Smith",
  phone: "+1-555-0123",
});
```

<Callout type="error" title="Critico para la atribucion">
Sin `identify()`, los visitantes anonimos no pueden vincularse con pagos o eventos de CRM. Llamalo en cada envio de formulario, inicio de sesion o registro. Consulta [Identificacion de usuarios](/docs/es/tracking/identify-users) para mas detalles.
</Callout>

## Gestion de consentimiento

```ts title="Establecer consentimiento despues del banner de cookies"
import { setConsent } from "@atribu/tracker";

// Despues de que el usuario acepta las cookies de analitica
setConsent({ analytics: true, marketing: false });
```

El estado de consentimiento se persiste en `localStorage` y se adjunta a cada evento posterior.

## Impresiones

```ts title="Rastrear cuando un elemento se hace visible"
import { observeImpression } from "@atribu/tracker";

// Selector CSS
const cleanup = observeImpression("#hero-banner", { section: "hero" });

// Elemento DOM
const el = document.querySelector(".pricing-card");
const cleanup2 = observeImpression(el, { plan: "pro" }, { threshold: 0.8 });

// Limpiar al desmontar
cleanup();
cleanup2();
```

## Ciclo de vida

```ts title="Enviar y reiniciar"
import { flush, reset } from "@atribu/tracker";

// Forzar el envio de todos los eventos en cola (antes de navegar)
flush();

// Limpiar todo el estado almacenado (al cerrar sesion)
reset();
```

## Eventos capturados automaticamente

El paquete captura automaticamente estos eventos sin necesidad de configuracion:

| Categoria | Eventos |
|-----------|---------|
| **Navegacion** | Vistas de pagina, navegacion SPA (pushState, popstate, hashchange) |
| **Sesiones** | Inicio/fin de sesion, timeout configurable |
| **Engagement** | Profundidad de scroll, tiempo en pagina, heartbeat |
| **Enlaces** | Clics en enlaces externos, descargas de archivos (PDF, ZIP, etc.) |
| **Formularios** | Envios de formulario con extraccion de email/telefono |
| **Reservas** | Completados de widgets GHL, Calendly, Cal.com |
| **Meta Pixel** | Intercepcion de `fbq()` para deduplicacion del lado del servidor |
| **Stripe** | Deteccion de checkout completado (`?session_id=cs_*`) |
| **Formularios GHL** | Intercepcion de `fetch()` para formularios basados en div |
| **Calidad de clics** | Rage clicks (3+ rapidos), dead clicks (opcional) |
| **Web Vitals** | LCP, FCP, CLS, INP, TTFB (opcional) |
| **Visibilidad CTA** | Rastreo de visibilidad de botones/enlaces de accion (opcional) |
| **Video** | Reproduccion/pausa/hitos de `<video>` (opcional) |
| **Errores** | Errores de JavaScript no capturados |
| **Deteccion de bots** | Filtra bots, etiqueta agentes de IA (ChatGPT, Claude, etc.) |

## Script tag vs npm

| | Paquete npm | Script Tag |
|---|---|---|
| **Instalacion** | `npm install @atribu/tracker` | `<script src="..."></script>` |
| **TypeScript** | Seguridad de tipos completa | Sin tipos |
| **Frameworks** | React, Next.js, Vue, Svelte | Solo HTML vanilla |
| **SSR** | Cliente no-op integrado | Solo del lado del cliente |
| **Configuracion** | Objeto `init()` tipado | Variables de window |
| **Modulo** | ESM + CommonJS | IIFE global |
| **Funcionalidad** | Identica | Identica |

## Soporte de TypeScript

Todos los tipos se exportan:

```ts title="Importar tipos"
import type {
  AtribuConfig,
  AtribuClient,
  TrackOptions,
  IdentifyInput,
  ConsentPayload,
  TrackingEvent,
} from "@atribu/tracker";
```

## Seguridad SSR

Cuando `window` no esta definido (Node.js, componentes de servidor de Next.js, SSR), `init()` devuelve un **cliente no-op silencioso**. Todos los metodos (`track`, `identify`, etc.) son seguros de llamar: simplemente no hacen nada del lado del servidor y se activan una vez que el codigo se ejecuta en el navegador.

```ts title="Seguro en cualquier entorno"
import { init, track } from "@atribu/tracker";

// Esto funciona en SSR — devuelve cliente no-op, sin errores
const client = init({ trackingKey: "trk_live_..." });

// Esto es seguro del lado del servidor — se ignora silenciosamente
track("page_loaded");
```

## Relacionado

<Cards>
  <Card title="Instalar Tracking" href="/docs/es/getting-started/install-tracker">
    Instalacion con script tag para configuraciones sin npm
  </Card>
  <Card title="Guias de Frameworks" href="/docs/es/tracking/framework-guides">
    Configuracion detallada para React, Next.js, Vue y Svelte
  </Card>
  <Card title="Eventos Personalizados" href="/docs/es/tracking/custom-events">
    Rastrear acciones personalizadas y eventos de ingreso
  </Card>
</Cards>

---

# Parametros UTM

Los parametros UTM le dicen a Atribu de donde viene tu trafico. Son pequenas etiquetas que se agregan al final de tus URLs para identificar la fuente de trafico, el medio y la campana.

---

## Que son los parametros UTM?

Cuando compartes un enlace en un anuncio, email o publicacion social, puedes agregar parametros UTM a la URL para que Atribu sepa exactamente de donde vino el visitante. Aqui hay un ejemplo:

```text title="URL con parametros UTM"
https://yoursite.com/landing-page?utm_source=facebook&utm_medium=paid&utm_campaign=spring_sale&utm_content=video_ad_1
```

Todo lo que esta despues del `?` es un conjunto de pares clave-valor que Atribu lee y almacena con la sesion del visitante. Estos datos alimentan tu desglose de Fuentes de Trafico y la atribucion a nivel de campana.

---

## Parametros UTM que Atribu lee

| Parametro | Que le dice a Atribu | Valores de ejemplo |
|-----------|----------------------|-------------------|
| `utm_source` | De donde viene el trafico | `facebook`, `google`, `newsletter`, `linkedin` |
| `utm_medium` | Como llegaron | `paid`, `cpc`, `email`, `organic`, `social` |
| `utm_campaign` | Cual campana los envio | `spring_sale`, `black_friday`, `webinar_march` |
| `utm_content` | Cual anuncio o variacion de contenido | `video_ad_1`, `banner_blue`, `cta_bottom` |
| `utm_term` | Cual palabra clave (para anuncios de busqueda) | `marketing_tools`, `crm_software` |

<Callout type="info" title="Solo utm_source y utm_medium son obligatorios">
Como minimo, siempre incluye `utm_source` y `utm_medium`. Los demas parametros son opcionales pero altamente recomendados para reportes detallados.
</Callout>

---

## IDs de clic (automaticos, sin configuracion necesaria)

Las plataformas de anuncios automaticamente agregan un ID de clic a tus URLs cuando alguien hace clic en un anuncio. Estos son separados de los parametros UTM -- no necesitas configurarlos.

| ID de clic | Plataforma | Que hace |
|------------|-----------|----------|
| `fbclid` | Meta (Facebook / Instagram) | Agregado automaticamente por Meta a cada clic en un anuncio |
| `gclid` | Google Ads | Agregado automaticamente por Google a cada clic en un anuncio |
| `msclkid` | Microsoft / Bing Ads | Agregado automaticamente por Microsoft a cada clic en un anuncio |
| `ttclid` | TikTok Ads | Agregado automaticamente por TikTok a cada clic en un anuncio |

Atribu detecta estos IDs de clic y los usa para dos cosas:

1. **Clasificacion de canal** -- Una visita con `fbclid` se clasifica como Social Pagado, incluso si faltan los parametros UTM
2. **Atribucion a nivel de anuncio** -- Los IDs de clic ayudan a rastrear un clic especifico hasta el anuncio exacto que lo genero

<Callout type="info" title="Los IDs de clic tienen prioridad">
Si una visita tiene tanto un ID de clic como parametros UTM, el ID de clic tiene prioridad para la clasificacion del canal. Esto previene clasificaciones erroneas cuando las etiquetas UTM estan ausentes o son incorrectas. Por ejemplo, una visita con `gclid` siempre se clasifica como **Busqueda Pagada**, independientemente de lo que diga `utm_medium`.
</Callout>

---

## Como los parametros UTM alimentan los canales

Atribu clasifica automaticamente cada visita en un canal de marketing basado en UTMs, referrer e IDs de clic:

| Lo que Atribu ve | Se clasifica como |
|------------------|-------------------|
| `utm_medium=paid` o `utm_medium=cpc` con `utm_source=facebook` | **Social Pagado** |
| `utm_medium=paid` o `utm_medium=cpc` con `utm_source=google` | **Busqueda Pagada** |
| `fbclid` o `ttclid` en la URL (sin referrer social) | **Social Pagado** |
| `gclid` o `msclkid` en la URL | **Busqueda Pagada** |
| `utm_medium=email` | **Email** |
| `utm_medium=affiliate` | **Afiliados** |
| `utm_medium=display` | **Display** |
| Referrer de google.com, bing.com (sin indicadores de pago) | **Busqueda Organica** |
| Referrer de facebook.com, instagram.com (sin indicadores de pago) | **Social Organico** |
| Otro referrer | **Referencia** |
| Sin referrer, sin UTMs, sin IDs de clic | **Directo** |

---

## Mejores practicas

### Siempre combina utm_source y utm_medium

Estos dos parametros trabajan juntos. `utm_source` identifica la plataforma, `utm_medium` identifica el tipo de trafico:

```text title="Bueno: source + medium juntos"
?utm_source=facebook&utm_medium=paid
?utm_source=google&utm_medium=cpc
?utm_source=mailchimp&utm_medium=email
```

### Usa nombres consistentes en minusculas

Elige una convencion y mantenla en todas tus campanas:

```text title="Nombres consistentes"
utm_source=facebook    (no Facebook, fb, o FB)
utm_medium=paid        (no Paid, cpc, o PPC)
utm_campaign=spring_sale  (no Spring Sale o SpringSale)
```

Atribu trata `Facebook` y `facebook` como fuentes diferentes, asi que el uso inconsistente de mayusculas lleva a reportes fragmentados.

### No uses UTMs en enlaces internos

Los parametros UTM solo deben usarse en enlaces de fuentes **externas** que apuntan a tu sitio. Nunca agregues UTMs a enlaces dentro de tu propio sitio web (como menus de navegacion o banners internos) -- esto sobrescribe la fuente de trafico original y rompe la atribucion.

```text title="Enlace externo (correcto)"
Enlace de newsletter por email: https://yoursite.com/sale?utm_source=newsletter&utm_medium=email

Enlace interno (nunca hagas esto):
https://yoursite.com/pricing?utm_source=homepage&utm_medium=banner
```

### Incluye utm_content para variaciones de anuncios

Cuando ejecutas multiples variaciones de anuncios en la misma campana, usa `utm_content` para distinguirlas:

```text title="Distinguiendo variaciones de anuncios"
?utm_source=facebook&utm_medium=paid&utm_campaign=spring_sale&utm_content=video_ad
?utm_source=facebook&utm_medium=paid&utm_campaign=spring_sale&utm_content=carousel_ad
?utm_source=facebook&utm_medium=paid&utm_campaign=spring_sale&utm_content=static_image
```

Esto te permite ver cual variacion creativa genera mas conversiones.

---

## Meta Ads y parametros UTM

Cuando conectas tu cuenta de Meta Ads a Atribu, los datos de campanas y anuncios se sincronizan automaticamente. Meta tambien agrega `fbclid` a cada URL de clic en anuncio. Para mejores resultados, agrega parametros UTM a las URLs de tus anuncios de Meta:

```text title="Parametros de URL recomendados para anuncios de Meta"
?utm_source=facebook&utm_medium=paid&utm_campaign={{campaign.name}}&utm_content={{ad.name}}
```

Los parametros de URL dinamicos de Meta (`{{campaign.name}}`, `{{ad.name}}`) se reemplazan automaticamente con los nombres reales de la campana y el anuncio cuando alguien hace clic.

---

## Siguientes pasos

<Cards>
  <Card title="Clasificacion de Canales" href="/docs/es/concepts/channels">
    Referencia completa de como se clasifican los canales
  </Card>
  <Card title="Como Funciona el Seguimiento" href="/docs/es/tracking/how-tracking-works">
    Entiende visitantes, sesiones y cookies
  </Card>
  <Card title="Fuentes de Trafico" href="/docs/es/features/traffic-sources">
    Ve como los datos UTM aparecen en los tableros de desglose de tu dashboard
  </Card>
</Cards>

---