# Tracking

> Install the Lucra Pixel and credit creators for sales on your site.

Source: https://www.onlucra.com/docs/tracking

The Lucra Pixel records what visitors do on your site and credits each sale or sign-up to the creator who sent them. Lucra uses it to report what creators drive, and to pay them for it.

Start by installing the pixel. Then decide which events count as [conversions](#conversions).

## Install

On Shopify? Skip to [Shopify](#shopify); there's no code to add.

Create a pixel in **Settings → Lucra Pixel** with the domains it runs on. Paste its snippet before `</head>` on every page:

```html
<script>window.lucra=window.lucra||[];lucra.push(["init",{siteId:"pxl_0Q2m8VxR4sTnYb7KcLd3Ew"}]);</script>
<script async src="https://a.onlucra.com/p.js"></script>
```

It records a `page_view` on every page, including navigations in a single-page app. The pixel shows **Verified** once the first event arrives.

### Recommended: load it from your own domain

Proxy `/lucra/*` on your site to `https://a.onlucra.com/*`, then use the first-party snippet from the same page:

```html
<script>window.lucra=window.lucra||[];lucra.push(["init",{siteId:"pxl_0Q2m8VxR4sTnYb7KcLd3Ew"}]);</script>
<script async src="/lucra/p.js"></script>
```

This does two things. The pixel keeps visitors' attribution in a first-party `_lucra` cookie on your domain, which Safari keeps past its 7-day limit on script storage. And it loads like your own script, so blockers that filter third-party scripts leave it alone.

Next.js, in `next.config.js`:

```js
module.exports = {
  async rewrites() {
    return [{ source: "/lucra/:path*", destination: "https://a.onlucra.com/:path*" }]
  },
}
```

Vercel, in `vercel.json`:

```json
{
  "rewrites": [{ "source": "/lucra/:path*", "destination": "https://a.onlucra.com/:path*" }]
}
```

Cloudflare, a Worker on the route `example.com/lucra/*`:

```js
export default {
  fetch(request) {
    const url = new URL(request.url)
    return fetch(`https://a.onlucra.com${url.pathname.slice(6)}${url.search}`, request)
  },
}
```

nginx:

```text
location /lucra/ {
  proxy_pass https://a.onlucra.com/;
  proxy_ssl_server_name on;
}
```

## Shopify

In **Settings → Lucra Pixel**, choose **Install on Shopify**. This installs the Lucra Shopify app (or asks your store to approve tracking if it's already connected). It adds the pixel to your store, records page views, product views, carts and checkouts, and confirms every paid order from Shopify's servers. It runs only where the visitor allows marketing and analytics tracking.

## Track events

The pixel records page views on its own. Call `lucra.track` for everything else, like a purchase on your order page:

```js
lucra.track("purchase", {
  value: 4999, // cents
  currency: "USD",
  orderId: "1042",
  discountCode: "SPRING10",
  contents: [{ id: "jacket-01", quantity: 1, price: 4999 }],
  user: { email: "jo@example.com" },
})
```

Call it once the script has loaded. Code that may run first can queue the call instead: `lucra.push(["track", "purchase", { … }])`.

Use a standard event name where one fits:

| Event            | When                  |
| ---------------- | --------------------- |
| `page_view`      | Sent for you          |
| `view_content`   | Viewed a product      |
| `search`         | Searched              |
| `add_to_cart`    | Added to cart         |
| `begin_checkout` | Started checkout      |
| `purchase`       | Bought                |
| `lead`           | Became a lead         |
| `sign_up`        | Created an account    |
| `start_trial`    | Started a trial       |
| `subscribe`      | Subscribed            |
| `contact`        | Contacted you         |

Name your own events in snake_case, like `booked_demo`. Every field is optional:

| Field                               | What                                                                         |
| ----------------------------------- | ---------------------------------------------------------------------------- |
| `value`, `currency`                 | In cents, with an ISO 4217 code. Send both or neither.                       |
| `orderId`                           | Your order's ID.                                                             |
| `eventId`                           | Your own ID, to match a [server copy](#server-events). Generated if left out. |
| `discountCode`                      | The order's discount code, recorded with the event.                          |
| `productId`, `variantId`, `quantity` | One product.                                                                |
| `contents`                          | Up to 100 items: `{ id, quantity, price }`, price in cents.                  |
| `custom`                            | Up to 25 of your own fields: strings, numbers or booleans.                   |
| `user`                              | `email`, `phone`, `firstName`, `lastName`, `city`, `state`, `zip`, `country`, `externalId`. |

## Identify

Once you know who the visitor is, send their details with every later event:

```js
lucra.identify({ email: "jo@example.com", externalId: "user_123" })
```

Details are hashed on arrival; see [Data & privacy](#data--privacy).

## Consent

Consent is implied: the pixel records with consent unless you or the visitor say otherwise, so most US sites need nothing. Two defaults follow the law for you:

- Visitors in the EU, EEA, UK and Switzerland start at `unknown` (opt-in is required there) until you call `lucra.consent("granted")`.
- A browser sending Global Privacy Control starts at `denied` (an opt-out under California's CCPA).

With a consent banner, pass its answer and update it when the visitor changes it; what you pass always wins:

```js
lucra.push(["init", { siteId: "pxl_0Q2m8VxR4sTnYb7KcLd3Ew", consent: "unknown" }])

lucra.consent("granted") // or "denied"
```

Until consent is `granted`, events carry nothing that identifies the visitor: no visitor ID, personal details, click IDs or IP, and stored attribution is cleared. They still count, with their place and device.

## Server events

Send conversions from your server with an API key, so ad blockers and closed tabs can't lose them:

```bash
curl https://api.onlucra.com/v1/pixels/pxl_0Q2m8VxR4sTnYb7KcLd3Ew/events \
  -H "Authorization: Bearer $LUCRA_API_KEY" \
  -H "Lucra-Version: 2026-10-01" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "purchase",
    "eventId": "order-1042",
    "orderId": "1042",
    "value": 4999,
    "currency": "USD",
    "pageUrl": "https://shop.example.com/thanks",
    "lucraId": "sub_14XA4WGiSOmaVI3B4QLm4Fs",
    "user": { "email": "jo@example.com" }
  }'
```

To count a conversion once when both fire, pass the same `eventId` to `lucra.track` in the browser and to your server. The server copy confirms the browser event and fills in what it lacked (value, order, customer details), and only server-confirmed events pay creators by default.

A server event credits a creator only if it carries what the visitor came with: `lucraId` from the landing URL, and `campaign` from its `lucra_campaign`. All fields are in the [API reference](https://www.onlucra.com/docs/api-attribution#server-events).

## Creator links

Links on Lucra ads carry `lucra_id`, the video the visitor came from, which credits its creator, and `lucra_campaign`, the paid campaign. The pixel also reads UTM tags and ad click IDs (`gclid`, `fbclid`, `ttclid` and others), and remembers the latest creator link for the visitor.

## Conversions

A conversion is the goal you count: a `purchase`, a `sign_up`, or your own event, narrowed by page (`pageUrlContains`) or a minimum value. Create them in **Settings → Lucra Pixel** or with `POST /v1/conversions`.

An event is credited to a creator when the visitor converts within the conversion's attribution window (30 days by default, up to 90) of landing from their video. Events with no landing time, like a server event, are credited too.

### Pay per conversion

Programs and retainers can pay creators for conversions credited to them:

```json
{ "conversion": "conv_1fK7pQ2wXy9LmN4bVc8RtZa", "amount": 500 }
```

That's $5 per conversion; `{ "conversion": "conv_…", "percent": 10 }` pays 10% of each conversion's value instead (an affiliate commission). A retainer can pay only on conversions, like $5 per sign-up each week; on a program, a conversion rule pays alongside its usual rule. Lucra pays them like other results, under the `cap`. See [pay](https://www.onlucra.com/docs/api-payments#pay).

- Only server-confirmed events pay: [server events](#server-events) and Shopify orders. Set `payBrowserEvents: true` on a conversion to pay on browser-only events too, but anyone with your pixel ID can send those.
- A `percent` counts only events valued in USD for now.
- A credited conversion counts toward every program and retainer of that creator that pays on it, except one from a paid campaign that [credits creators with Lucra](https://www.onlucra.com/docs/api-ads#how-creators-are-credited): that campaign pays it.

## Reports

Each conversion shows where it happens: by creator, country, region, city, in-app browser (TikTok, Instagram…), device, source (ad or direct) and day, with counts and value. In the API, that's `GET /v1/conversions/:id/report`.

## Data quality

The pixel's page shows, of the last 7 days' events, how many had consent, came from your server, carried customer details or ad click IDs, were credited to a creator, and have a location. More of each means better matching and fewer missed conversions.

## Data & privacy

- Personal details (email, phone, name, address, `externalId`) are normalized and SHA-256 hashed on arrival, never stored raw. Already-hashed values are kept as sent.
- The raw IP address is kept 13 months, then cleared.
- Location (country, region, city), network, device and in-app browser are recorded for every event, with or without consent, since they don't identify anyone.
- Visitors in a pixel's excluded countries aren't recorded at all.
- With consent `denied` or `unknown`, nothing that identifies the visitor is sent.

## Forward to ad platforms

Lucra never sends your events to ad platforms. To forward them, for example to the Meta Conversions API, listen for the `conversion.recorded` [webhook](https://www.onlucra.com/docs/webhooks) and fetch its `pixel_event`. The hashes are normalized the way Meta, TikTok and Google expect.

```ts
const event = await fetch(`https://api.onlucra.com/v1/pixel-events/${data.pixel_event}`, {
  headers: { Authorization: `Bearer ${process.env.LUCRA_API_KEY}`, "Lucra-Version": "2026-10-01" },
}).then((response) => response.json())

if (event.consent === "granted") {
  await fetch(`https://graph.facebook.com/v21.0/${META_PIXEL_ID}/events?access_token=${META_TOKEN}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      data: [
        {
          event_name: "Purchase", // purchase → Purchase, sign_up → CompleteRegistration, lead → Lead…
          event_time: Math.floor(Date.parse(event.occurredAt) / 1000),
          event_id: event.eventId, // matches Meta's browser pixel when it sent the same ID
          action_source: event.actionSource,
          event_source_url: event.pageUrl,
          user_data: {
            em: event.user.email,
            ph: event.user.phone,
            fn: event.user.firstName,
            ln: event.user.lastName,
            ct: event.user.city,
            st: event.user.state,
            zp: event.user.zip,
            country: event.user.country,
            external_id: event.user.externalId,
            fbc: event.clickIds.fbc,
            fbp: event.clickIds.fbp,
            client_ip_address: event.ipAddress,
            client_user_agent: event.userAgent,
          },
          custom_data: { value: event.value / 100, currency: event.currency, order_id: event.orderId },
        },
      ],
    }),
  })
}
```

Every platform's conversion API takes the same pieces; only the names change. Send each one the click ID and cookie it set:

| Platform | Event name for `purchase` / `sign_up` | From `clickIds` | User data |
| --- | --- | --- | --- |
| Meta Conversions API | `Purchase` / `CompleteRegistration` | `fbc`, `fbp` | `user.*`, `ipAddress`, `userAgent` |
| TikTok Events API | `CompletePayment` / `CompleteRegistration` | `ttclid`, `ttp` | `user.email`, `user.phone`, `user.externalId`, `ipAddress`, `userAgent` |
| Snapchat Conversions API | `PURCHASE` / `SIGN_UP` | `sccid` (as `sc_click_id`), `scid` (as `sc_cookie1`) | `user.email`, `user.phone`, `ipAddress`, `userAgent` |
| Google Ads (offline conversions) | your conversion action | `gclid`, `gbraid`, `wbraid` | `user.email`, `user.phone` (enhanced conversions) |

The pixel picks these up from ad links (`fbclid`, `gclid`, `ttclid`, `ScCid`) and the platforms' own cookies (`_fbp`, `_fbc`, `_ttp`, `_scid`), so they're there whenever the visitor came from an ad. Only forward events whose `consent` is `granted`.
