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

# Tracking and cookies

> What the headless SDK tracks for your test results, what you track yourself, and which cookies it sets.

Your results need storefront events and checkout events. The SDK sends the first kind, and ABConvert collects the second in Shopify checkout.

**Runs on:** the browser. `ABConvertProvider` tracks on its own; `track`, `trackExposure` and `setConsent` come from `useABConvert()`, or from the client on a stack without React.

## Track events

These events feed your results:

| Events | Who sends them |
| - | - |
| Assignment: the visitor was put in a test group | The SDK, from the browser, once the page has loaded. A known crawler, such as Googlebot, gets your original store and is never assigned. A prefetch is assigned but never counted. |
| Exposure: the visitor experienced a test | The SDK, or ABConvert in checkout. See [Exposure](#exposure). |
| Page views, product views, add to cart, custom events | The SDK, from your storefront's pages |
| [Engagement](/analytics/engagement-metrics): click-throughs, scrolls and time on page | The SDK, from every page view it tracks |
| Checkout steps and orders | ABConvert, from Shopify checkout |

ABConvert's web pixel does not run on a custom storefront's pages. That is why the SDK sends storefront events itself.

A custom storefront has no Shopify templates, so scope a test's engagement by page, not by template.

### Exposure

An exposure is the moment a test could first affect the visitor. It is what the **Visitors who experienced the test** view in your results counts, and [what counts differs by test type](/analytics/overview#what-counts-as-experiencing-the-test).

Reading data never records an exposure. An exposure is recorded in one of these ways:

| Test type | An exposure is recorded when |
| - | - |
| Price | Your code calls `trackExposure` after it shows a test price. See [Record a price exposure](#record-a-price-exposure). |
| Offer | Your code calls `trackExposure` after it shows the offer. See [Record an offer exposure](#record-an-offer-exposure). |
| URL redirect | Control opens the page the test redirects from, or a variant lands on its destination. The SDK records it. |
| Shipping, checkout | ABConvert records it in Shopify checkout, as on a theme. |

#### Record a price exposure

Call `trackExposure` with the test ID from the `Price` object, in the browser, once the price is on screen. The SDK records one exposure per test per visit, so calling it from every price on a collection page is fine.

```tsx Browser (component) theme={null}
import { useEffect } from 'react';
import { useABConvert } from '@abconvert/headless/react';

export function ProductPrice({ productVariant }) {
  const { getPriceByVariantId, formatPrice, trackExposure } = useABConvert();
  const price = getPriceByVariantId(productVariant.id);

  // The visitor has seen the test price
  useEffect(() => {
    if (price) trackExposure(price.experimentId);
  }, [price]);

  const amount = price ? price.amount : Number(productVariant.price.amount);
  return <span>{formatPrice(amount, productVariant.price.currencyCode)}</span>;
}
```

Without this call, a visitor who saw a test price is counted under **Assigned visitors** but not under **Visitors who experienced the test**.

#### Record an offer exposure

Call `trackExposure` with the offer's `experimentId` once your offer banner or message is on screen. Checkout applies the discount either way, but only this call counts the visitor under **Visitors who experienced the test**.

```tsx Browser (component) theme={null}
const { getOffers, trackExposure } = useABConvert();
const [offer] = getOffers();

useEffect(() => {
  if (offer) trackExposure(offer.experimentId);
}, [offer]);
```

### Page views and add to cart

On Hydrogen, the provider from [the Hydrogen guide](/headless-sdk/hydrogen#step-3-load-it-in-the-root-loader-and-pass-it-to-the-browser) listens to Hydrogen's own analytics, so these need no code. On every other stack, nothing publishes them: call `track` with these event names.

| Event | Call it when | Payload |
| - | - | - |
| `page_viewed` | A page loads, and on every client-side route change | None |
| `product_viewed` | A product page shows its product variant | `productId`, `variantId` |
| `product_added_to_cart` | A line is added to the cart | `variantId`, `quantity` |

```ts Browser (component) theme={null}
const { track } = useABConvert();

track('page_viewed');
track('product_viewed', { productId: '8901234567890', variantId: '47522361606401' });
track('product_added_to_cart', { variantId: '47522361606401', quantity: 1 });
```

`page_viewed` also keeps the visitor's session alive: a session ends 30 minutes after the last event it records. Product views and add to cart give a test its per-product results, and the results of a product group attached to it. The SDK sends an event only for a visitor who is in at least one running test, and never for a forced or preview visit. To see events, launch a test and open your storefront as a new visitor, without a preview link.

### Custom events

Call `track` with the name you gave the event in ABConvert. This works the same on every stack. The SDK sends custom events for every visitor, in a test or not. A name that matches no custom event in ABConvert is dropped, with a warning in the browser console.

```ts Browser (component) theme={null}
track('product_builder_completed');
```

### Checkout steps and orders

ABConvert tracks these in Shopify checkout, where Shopify loads ABConvert's web pixel for any storefront. The pixel reads the visitor ID and test groups from the checkout's attributes, which you wrote through the [cart](/headless-sdk/cart), and the order carries the same attributes. You add nothing beyond connecting the cart.

<Tip>
  Put checkout on a subdomain of your storefront domain, such as `checkout.brand.com` under `brand.com`. Shopify [recommends it](https://shopify.dev/docs/storefronts/headless/hydrogen/analytics/consent) so that the visitor's consent carries into checkout.
</Tip>

## Cookies and storage

You do not manage cookies or browser storage for ABConvert. The SDK sets first-party cookies on your storefront's domain for the visitor ID, the current visit, and the decisions it has made. It reads them on every request, so a visitor keeps the same test group on the server, in the browser, and on return visits, whatever they answer to your cookie banner.

For your cookie policy, these are the cookies:

| Cookie | Holds | Lasts |
| - | - | - |
| `abconvert-visitor` | The visitor ID | One year |
| `abconvert-session` | The visit | 30 minutes after the last page, at most until midnight UTC |
| `abconvert-state` | The visitor's test groups, and a forced or previewed test group for the visit | One year |
| `abconvert-pending` | Test groups the server assigned, until the browser reports them | One day |
| `abconvert-key` | The visitor's test groups for a CDN that caches by them. Only with [`cdnKeyedByCookie`](/headless-sdk/nextjs-caching#cache-tested-pages-at-your-cdn) on Next.js. | One year |

The SDK also keeps a flag per test in `sessionStorage`, so an exposure is recorded once per visit.

You do three things:

* Pass `request` when you create the client on the server, and add `getSetCookieHeaders()` to the response. Your stack guide's setup step does this; on Next.js, the middleware does.
* Do not cache a response that sets an ABConvert cookie.
* If you ask visitors for cookie consent, [tell the SDK the visitor's choice](#consent).

## Consent

Until a visitor allows analytics, the SDK tracks nothing. What it would send waits until they allow, and is dropped if they decline.

The SDK sets its cookies whatever the visitor answers, so they see the same test group on every page and at checkout. We trade a cookie set before the answer for a visit that stays consistent: without it, a visitor who has not answered could see one price on a product page and another in the cart. Check that this fits your cookie policy.

On Hydrogen, the client and provider from the Hydrogen guide read the visitor's choice from Shopify's [Customer Privacy API](https://shopify.dev/docs/api/customer-privacy), and you pass nothing. If another stack loads that API, set `expectCustomerPrivacy: true` in `createABConvert`, and `expectCustomerPrivacy` on `ABConvertProvider` or in the options of [`hydrateABConvert`](/headless-sdk/reference#methods), to get the same behavior.

Otherwise, pass the choice when you create the client:

```ts Server theme={null}
const abconvert = await createABConvert({
  shop: 'your-store.myshopify.com',
  storefrontKey: 'abc_pk_4f9c2e7a1b3d5f6e8a0c2b4d6f8e0a1c',
  request,
  consent: { analytics: true },
});
```

When the visitor answers your cookie banner, update it in the browser:

```ts Browser (component) theme={null}
const { setConsent } = useABConvert();

setConsent({ analytics: true });
```

If you pass no `consent`, the SDK reads Shopify's `_tracking_consent` cookie when the visitor has one, and otherwise treats analytics as allowed.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.