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

# Integrate Affixo tracking

> Three code snippets, three different pages. This guide tells you exactly what to add, where, and why — including copy-paste examples for the most common platforms.

<Note>
  **Using an LLM to implement this?** Every section in this guide is written to be unambiguous for automated implementation. Each snippet is labelled with the exact page or event trigger it belongs to. You can paste this entire page into your AI assistant and ask it to find the right places in your codebase.
</Note>

## How Affixo tracking works

Affixo tracks three events in an affiliate's journey:

| Event               | What it means                           | Where it fires                                | Commission?                       |
| ------------------- | --------------------------------------- | --------------------------------------------- | --------------------------------- |
| **Click**           | A visitor arrived via an affiliate link | Automatic — no code needed beyond the snippet | No                                |
| **Lead** (`signup`) | That visitor signed up / opted in       | Your thank-you / signup confirmation page     | No — tracks the referral          |
| **Sale** (`sale`)   | That visitor paid                       | Your payment / order confirmation page        | **Yes** — triggers the commission |

All three share a single attribution chain. The snippet records the click on Affixo's side and remembers who the visitor is; `track('signup')` and `track('sale')` carry that visitor forward, and Affixo matches the event back to the right affiliate from their click history automatically.

***

## Step 1 — Add the snippet to every page

**Where:** The `<head>` of **every page** on your site (layout file, base template, `_document.js`, etc.).

**What it does:** Detects when a visitor arrives via an affiliate link (`?ref=`, `?via=`, `?fpr=`, and others), records the click and remembers the visitor in their browser, and fires a page-view hit so you can see which domains are active.

<Note>
  The snippet does **not** set a cookie on your domain — it can't, because it's
  served from `go.affixo.dev`. Visitor identity is kept in your visitors'
  browser storage instead, and read back with `Affixo.getVisitorId()`. Practical
  consequence: **Safari and every iOS browser clear that storage after about 7
  days of no return visit**, so a click-based referral older than a week may
  fall back to weaker matching (device fingerprint, then IP) on those browsers.
  Coupon, manual-code, subscription-renewal and email-match attribution are
  unaffected — see [Attribution](/attribution).
</Note>

```html theme={null}
<!-- Affixo tracking — paste into <head> on every page -->
<script async src="https://go.affixo.dev/sa.js?w=YOUR_PUBLIC_KEY"></script>
```

Replace `YOUR_PUBLIC_KEY` with the key shown in **Settings → Tracking**. It looks like `pk_live_xxxxxxxx`.

<Tip>
  The `?w=` key is what identifies your workspace. Without it the snippet fires hits to the wrong account (or none at all). Always copy it from your dashboard — don't type it manually.
</Tip>

### Framework examples

<CodeGroup>
  ```html HTML / static site theme={null}
  <!-- In your <head> -->
  <script async src="https://go.affixo.dev/sa.js?w=YOUR_PUBLIC_KEY"></script>
  ```

  ```jsx Next.js (App Router) theme={null}
  // app/layout.tsx
  import Script from 'next/script'

  export default function RootLayout({ children }) {
    return (
      <html>
        <head>
          <Script
            src="https://go.affixo.dev/sa.js?w=YOUR_PUBLIC_KEY"
            strategy="afterInteractive"
          />
        </head>
        <body>{children}</body>
      </html>
    )
  }
  ```

  ```jsx Next.js (Pages Router) theme={null}
  // pages/_document.tsx
  import { Html, Head, Main, NextScript } from 'next/document'

  export default function Document() {
    return (
      <Html>
        <Head>
          <script async src="https://go.affixo.dev/sa.js?w=YOUR_PUBLIC_KEY" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
  ```

  ```jsx React (index.html) theme={null}
  <!-- public/index.html -->
  <head>
    <script async src="https://go.affixo.dev/sa.js?w=YOUR_PUBLIC_KEY"></script>
  </head>
  ```

  ```html WordPress theme={null}
  <!-- In your theme's header.php, before </head> -->
  <script async src="https://go.affixo.dev/sa.js?w=YOUR_PUBLIC_KEY"></script>
  ```

  ```html Webflow theme={null}
  <!-- Site Settings → Custom Code → Head Code -->
  <script async src="https://go.affixo.dev/sa.js?w=YOUR_PUBLIC_KEY"></script>
  ```

  ```html Shopify theme={null}
  <!-- theme.liquid, inside <head> -->
  <script async src="https://go.affixo.dev/sa.js?w=YOUR_PUBLIC_KEY"></script>
  ```
</CodeGroup>

**Verify it works:** Go to **Settings → Tracking**, scroll to the live test, enter your site URL, and click **Run test**. A confirmed hit means step 1 is complete.

***

## Step 2 — Track signups (leads)

**Where:** Your **signup confirmation** or **thank-you page** — the page a user lands on *after* they successfully sign up or opt in. Do **not** put this on the form page itself; it must fire only after the account is created.

**What it does:** Records the referred visitor as a **lead**. This tells Affixo "this affiliate sent us a signup." No commission is earned at this stage — leads are a count metric that proves attribution is working before money changes hands.

```html theme={null}
<!-- Paste on your signup / opt-in thank-you page only -->
<!-- Fire AFTER the account is created, not on the form page -->
<script>
  Affixo.track('signup')
</script>
```

<Warning>
  `Affixo.track()` is only available after the main snippet (Step 1) has loaded. If your thank-you page doesn't include the `<head>` snippet, add it there too before calling `track()`.
</Warning>

### With user identity (recommended)

Pass the user's email or ID so Affixo can deduplicate signups and link this lead to a future purchase:

```html theme={null}
<script>
  Affixo.track('signup', {
    email: 'user@example.com',   // the signed-up user's email
    uid:   'usr_123'             // your internal user ID
  })
</script>
```

### Framework examples

<CodeGroup>
  ```jsx Next.js — thank-you page theme={null}
  // app/signup/success/page.tsx (or wherever you redirect after signup)
  'use client'
  import { useEffect } from 'react'

  export default function SignupSuccess({ userEmail }) {
    useEffect(() => {
      window.Affixo?.track('signup', { email: userEmail })
    }, [userEmail])

    return <div>Thanks for signing up!</div>
  }
  ```

  ```js Node.js / Express (server-side render) theme={null}
  // Render your thank-you page and inline the call
  res.send(`
    <html>
      <head>
        <script async src="https://go.affixo.dev/sa.js?w=YOUR_PUBLIC_KEY"></script>
      </head>
      <body>
        <script>
          document.addEventListener('DOMContentLoaded', function () {
            window.Affixo?.track('signup', { email: '${user.email}', uid: '${user.id}' })
          })
        </script>
        <h1>Thanks for signing up!</h1>
      </body>
    </html>
  `)
  ```

  ```html Webflow (form success state) theme={null}
  <!-- Add to the success page's custom code, or use an embed block -->
  <script>
    Affixo.track('signup')
  </script>
  ```

  ```liquid Shopify (customer registration) theme={null}
  <!-- In customers/register.liquid or your post-registration template -->
  {% if customer %}
  <script>
    Affixo.track('signup', {
      email: '{{ customer.email }}',
      uid:   '{{ customer.id }}'
    })
  </script>
  {% endif %}
  ```
</CodeGroup>

***

## Step 3 — Track sales (customers)

<Info>
  **Using Stripe? Don't call `track('sale')` — but do wire the visitor id into Checkout (below).** If you've connected Stripe to Affixo (Integrations → Stripe), sales are recorded automatically and server-side — with the exact amount paid *after discounts*, coupon-code attribution, and automatic refund/chargeback reversal. Adding `track('sale')` on top would **double-count** the sale and report the **pre-discount** amount. But for a **cookie-tracked** referral (the visitor clicked an affiliate link, no coupon code), Affixo can only connect the payment back to the affiliate if your Checkout Session carries the visitor id — see [Stripe: pass the visitor id](#stripe-pass-the-visitor-id) immediately below. The manual `track('sale')` further down is only for sites with **no** connected payment integration.
</Info>

### Stripe: pass the visitor id \[#stripe-pass-the-visitor-id]

<Warning>
  Without this, a first-touch **cookie** referral through Stripe cannot be attributed — the sale is recorded but earns the affiliate nothing. (Coupon-code and returning-subscriber attribution still work without it; this is specifically about link/cookie clicks.)
</Warning>

When you create the Stripe Checkout Session, set the Affixo visitor id on it. Affixo reads it from **`client_reference_id`** or **`metadata.sa_visitor_id`** — either works. Read the id on the page with `Affixo.getVisitorId()` and hand it to your backend:

```js theme={null}
// Client: read the Affixo visitor id before starting checkout
const visitorId = await window.Affixo.ready();   // resolves to the visitor id
// send visitorId to your server when creating the session…
```

```js theme={null}
// Server: stamp it on the Checkout Session
const session = await stripe.checkout.sessions.create({
  mode: 'subscription',                 // or 'payment'
  line_items: [{ price: PRICE_ID, quantity: 1 }],
  client_reference_id: visitorId,       // ← Affixo attributes on this
  // For subscriptions, ALSO copy it onto the subscription so RENEWALS attribute:
  subscription_data: { metadata: { sa_visitor_id: visitorId } },
  success_url: 'https://example.com/thanks',
  cancel_url:  'https://example.com/pricing',
})
```

```js theme={null}
// Server: a ONE-OFF purchase — force a Customer so the referral records a UID
const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  line_items: [{ price: PRICE_ID, quantity: 1 }],
  client_reference_id: visitorId,       // ← Affixo attributes on this
  customer_creation: 'always',          // ← without this, Stripe creates no Customer
  success_url: 'https://example.com/thanks',
  cancel_url:  'https://example.com/pricing',
})
```

<Note>
  `customer_creation: 'always'` is **payment-mode only** — Stripe rejects it in subscription mode, which always creates a Customer anyway. Its default, `if_required`, means a one-off purchase mints no Customer at all: the sale still attributes and pays commission normally, but the referral records no **UID**, so a repeat purchase by the same buyer can't be tied back to the same Stripe identity. It has to be set when the session is created — once the checkout completes with no Customer, there is nothing to look up or backfill.
</Note>

<Note>
  For subscriptions, the `subscription_data.metadata.sa_visitor_id` line is what lets **renewal** invoices attribute — a renewal invoice carries no `client_reference_id` of its own. One-off payments don't need it.
</Note>

<Note>
  Already using another affiliate tool (e.g. FirstPromoter) that sets `client_reference_id` to *its* id? Put the Affixo id in `metadata.sa_visitor_id` instead — Affixo prefers a metadata match over a foreign `client_reference_id`.
</Note>

**Where:** Your **payment confirmation** or **order success page** — the page that only appears after a payment has successfully processed. Also callable from your backend webhook (see [Server-side tracking](#server-side-alternative)).

**What it does:** Records the lead as a **paying customer** and triggers the affiliate's commission calculation. Pass the sale amount so the commission engine can apply percentage-based rules correctly.

```html theme={null}
<!-- Paste on your payment / order confirmation page only -->
<!-- Fire AFTER payment has been confirmed, not on the checkout form -->
<script>
  Affixo.track('sale', {
    amount:   99.00,         // sale amount in major units — $99.00 = 99.00
    currency: 'usd',         // ISO 4217 lowercase
    order_id: 'ORDER-123'    // your order / invoice ID — used to deduplicate retries
  })
</script>
```

<Warning>
  `amount` is in **major units** (dollars, euros, …), not cents. $99.00 = `99.00`. Passing cents (`9900`) registers a $9,900 sale and produces a 100× commission overpayment.
</Warning>

<Note>
  `order_id` is strongly recommended. If the page reloads or the user refreshes, the same `order_id` prevents a duplicate commission from being created.
</Note>

### Framework examples

<CodeGroup>
  ```jsx Next.js — order confirmation page theme={null}
  // app/checkout/success/page.tsx
  'use client'
  import { useEffect } from 'react'

  export default function OrderSuccess({ order }) {
    useEffect(() => {
      window.Affixo?.track('sale', {
        amount:   order.amountCents / 100,   // convert your backend's cents to major units
        currency: order.currency,
        order_id: order.id,
      })
    }, [order.id])

    return <div>Order confirmed!</div>
  }
  ```

  ```js Node.js / Express theme={null}
  res.send(`
    <script>
      document.addEventListener('DOMContentLoaded', function () {
        window.Affixo?.track('sale', {
          amount:   ${order.amountCents / 100},
          currency: '${order.currency}',
          order_id: '${order.id}'
        })
      })
    </script>
  `)
  ```

  ```liquid Shopify (order status page) theme={null}
  <!-- In checkout.liquid or the order status additional scripts field -->
  <!-- Shopify's {{ checkout.total_price }} is in cents — divide by 100 -->
  <script>
    Affixo.track('sale', {
      amount:   {{ checkout.total_price | divided_by: 100.0 }},
      currency: '{{ checkout.currency | downcase }}',
      order_id: '{{ checkout.order_number }}'
    })
  </script>
  ```

  ```html WooCommerce (thank-you page) theme={null}
  <!-- In your child theme's functions.php -->
  <?php
  add_action('woocommerce_thankyou', function($order_id) {
    $order = wc_get_order($order_id);
    $amount = floatval($order->get_total());  // WooCommerce totals are already major units
    $currency = strtolower(get_woocommerce_currency());
    echo "<script>
      Affixo.track('sale', {
        amount:   {$amount},
        currency: '{$currency}',
        order_id: '{$order_id}'
      })
    </script>";
  });
  ```
</CodeGroup>

***

## Server-side alternative

If you can't rely on browser JavaScript for sales tracking (server-rendered checkouts, webhooks, payment processors that redirect off-site), use the server-side API instead.

**For Stripe, prefer the native integration** (Integrations → Stripe) — it captures sales automatically with the correct discounted amounts and needs no code. Use the API below for Paddle, Lemon Squeezy, or any provider Affixo isn't directly connected to.

See [Server-side tracking](/tracking-overview) for the full API reference.

**Quick example — record a sale from a Stripe webhook:**

```js theme={null}
// Your Stripe webhook handler
app.post('/webhooks/stripe', async (req, res) => {
  const event = stripe.webhooks.constructEvent(...)

  if (event.type === 'checkout.session.completed') {
    const session = event.data.object

    await fetch('https://go.affixo.dev/v1/track/sale', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.AFFIXO_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        external_id: session.id,                 // Stripe session ID — deduplicates
        amount:      session.amount_total / 100,  // Stripe sends cents — convert to major units
        currency:    session.currency,
        email:       session.customer_details.email,
      }),
    })
  }

  res.json({ received: true })
})
```

***

## Summary checklist

Use this to verify your integration before going live:

* [ ] The snippet (`sa.js?w=YOUR_PUBLIC_KEY`) is in `<head>` on **every page**
* [ ] `Affixo.track('signup')` fires on the **signup/thank-you page** only, after account creation
* [ ] Sales are tracked — via the **connected Stripe integration**, or `Affixo.track('sale', { amount, currency, order_id })` on the **payment confirmation page** if there's no connected payment integration
* [ ] **Stripe only:** the Checkout Session sets `client_reference_id` (and, for subscriptions, `subscription_data.metadata.sa_visitor_id`) to `Affixo.getVisitorId()` — otherwise cookie/link referrals through Stripe don't attribute
* [ ] `amount` is in **major units** (`99.00`), not cents
* [ ] `order_id` is set to your unique order/invoice ID to prevent duplicate commissions
* [ ] The snippet live test in **Settings → Tracking** shows a confirmed hit
* [ ] The end-to-end test in **Settings → Tracking** shows a lead and a sale both register

***

## Common mistakes

| Mistake                                        | Symptom                                                     | Fix                                                                                                 |
| ---------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| Snippet missing `?w=` key                      | Hits appear in wrong workspace or nowhere                   | Copy the full URL from Settings → Tracking                                                          |
| `track('signup')` on the form page             | Every form view counts as a lead                            | Move it to the page after successful account creation                                               |
| `track('sale')` fires before payment confirmed | Failed payments create commissions                          | Only fire on the order success / confirmation page                                                  |
| `amount` passed as cents (`9900`)              | Sale and commission are 100× too big                        | Always use major units (`99.00`)                                                                    |
| No `order_id`                                  | Page refreshes create duplicate commissions                 | Pass your order/invoice/session ID                                                                  |
| `track()` called before snippet loads          | `Affixo is not defined` error                               | Ensure the `<head>` snippet is present on the same page                                             |
| Stripe Checkout with no visitor id             | Sales recorded but link/cookie referrals earn no commission | Set `client_reference_id` (+ `subscription_data.metadata.sa_visitor_id`) to `Affixo.getVisitorId()` |
