> For the complete documentation index, see [llms.txt](https://docs.rafflex.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rafflex.io/advanced/conversion-event-tracking.md).

# Conversion Event Tracking

Send add to cart and purchase events from your RaffleX site to advertising platforms such as TikTok and Google Ads, using ready-made snippets that go in your Header Code.

This page is for developers, agencies and anyone comfortable adding tracking code who wants to report sales and basket activity to advertising platforms such as **TikTok** and **Google Ads**. AI assistants helping a RaffleX operator set up tracking are welcome to use it as a reference too.

RaffleX gives you two JavaScript hooks you can listen to:

* **AddToCart**, when a customer adds tickets to their basket using the ticket selector on a competition page.
* **Purchase**, when a customer completes an order and reaches the **Order Successful** page.

The sections below explain how to listen for each one and send the right event to each platform. **Facebook and Meta tracking is built in**, so you do not need any code for it (see below).

{% hint style="info" %}
New to tracking? If you only want to see visitors and sales in Google Analytics, you do not need this page. Follow [Google Analytics](/helpful-info/google-analytics.md) instead. RaffleX sends purchases to Google Analytics automatically.
{% endhint %}

***

### Where the code goes

All of the code on this page goes in the **Header Code** box:

1. In the admin panel, find the **Content** group in the left-hand menu and click **Settings**.
2. In the Settings side menu, click **Advanced** (the page title reads **Advanced & Configuration**).
3. In the **Code Injection** section, click at the very end of the existing code in **Header Code** and press **Enter** to start a new line.
4. Paste your snippet, keeping it **below** the platform's base pixel or tag.
5. Click **Save Settings**.

Header Code is added to the `<head>` of every page, which means it loads before the rest of the page. That is important for the purchase hook, which needs to be in place before the **Order Successful** page sends its purchase data.

{% hint style="danger" %}
Code in Header Code goes onto your live site exactly as written. A mistake can break pages or your checkout. Test after every change, and if anything breaks, remove the snippet and click **Save Settings** again. See [Advanced Settings](/additional-settings/settings/advanced-settings.md) for more about the Code Injection section.
{% endhint %}

***

### Before you start: install each platform's base code

Each ad platform's own base pixel or tag must be in Header Code before any event tracking will work. Paste the base code first, then the event snippets from this page underneath it.

{% tabs %}
{% tab title="TikTok" %}
Add the TikTok base pixel to Header Code, including the `ttq.load('YOUR_PIXEL_ID')` line. You can find the code in **TikTok Ads Manager**, under **Tools**, then **Events**, then your web pixel's setup or **Manage** screen. Choose the option to install the pixel code manually. TikTok changes its menus from time to time, so the names you see may be slightly different.
{% endtab %}

{% tab title="Google Ads" %}
Load the Google tag (`gtag.js`) in Header Code with your Google Ads ID (it looks like `AW-XXXXXXXXX`). You can find it in **Google Ads**, under **Goals**, then **Conversions**, by opening the conversion action you want and choosing to install the tag yourself.

If you also use Google Analytics, you only need one Google tag. Add a second `gtag('config', 'AW-XXXXXXXXX');` line to your existing Google Analytics tag rather than pasting a whole second copy. See [Google Analytics](/helpful-info/google-analytics.md) and [UTM tags](/helpful-info/utm-tags.md).
{% endtab %}

{% tab title="Facebook and Meta" %}
**Nothing to add.** RaffleX installs the Meta Pixel for you once your **Facebook Pixel ID** is saved on the **Marketing** settings page. See [Facebook and Meta tracking](/marketing/facebook-and-meta/tracking.md) and the section on Facebook further down this page.

Do not paste the Meta Pixel into Header Code as well, or every event will be counted twice.
{% endtab %}
{% endtabs %}

***

### AddToCart

Fires when a customer adds tickets to their basket using the ticket selector on a competition page.

It does **not** fire for:

* tickets added through the upsell popup at checkout (see [Upsell](/marketing/website-tools/upsell.md))
* tickets added with the quick add buttons that some competition card designs and homepage blocks offer

Your add to cart figures may therefore be lower than the true number of times tickets go into baskets.

#### Hook

```html
<script>
document.addEventListener('livewire:init', () => {
    Livewire.on('addtobasket', (event) => {
        // Your tracking code here
    });
});
</script>
```

{% hint style="warning" %}
Keep the outer `document.addEventListener('livewire:init', ...)` wrapper. Header Code runs before the rest of the site's scripts have loaded, so without the wrapper the hook is not ready yet and the snippet will not work.
{% endhint %}

#### Available data

| Property             | Type   | Description                                                                                                                 |
| -------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------- |
| `event.productId`    | number | The competition ID                                                                                                          |
| `event.productPrice` | string | The current price per ticket, for example `"0.99"`. Use `parseFloat()` before doing maths with it or sending it as a price. |
| `event.tickets`      | number | Number of tickets added (the quantity)                                                                                      |
| `event.content`      | string | The competition title                                                                                                       |
| `event.fbEventId`    | string | An event ID RaffleX uses to stop Facebook counting the same add to cart twice                                               |

#### TikTok

```html
<!-- TikTok: add to cart -->
<script>
document.addEventListener('livewire:init', () => {
    Livewire.on('addtobasket', (event) => {
        if (typeof ttq !== 'undefined') {
            var price = parseFloat(event.productPrice);
            ttq.track('AddToCart', {
                content_id: event.productId.toString(),
                content_name: event.content,
                content_type: 'product',
                quantity: event.tickets,
                price: price,
                value: parseFloat((price * event.tickets).toFixed(2)),
                currency: 'GBP'
            });
        }
    });
});
</script>
```

If your site uses euros, replace `'GBP'` with `'EUR'`. RaffleX sites use one of these two currencies, set in **Settings**, then **Checkout**. The code must be in capitals.

#### Google Ads

```html
<!-- Google Ads: add to cart -->
<script>
document.addEventListener('livewire:init', () => {
    Livewire.on('addtobasket', (event) => {
        if (typeof gtag === 'function') {
            var price = parseFloat(event.productPrice);
            gtag('event', 'add_to_cart', {
                currency: 'GBP',
                value: parseFloat((price * event.tickets).toFixed(2)),
                items: [{
                    item_id: event.productId.toString(),
                    item_name: event.content,
                    quantity: event.tickets,
                    price: price
                }]
            });
        }
    });
});
</script>
```

If your site uses euros, replace `'GBP'` with `'EUR'`.

This sends a standard `add_to_cart` event through your Google tag, which also reaches Google Analytics if it is on the same tag. If you want it to count as a specific Google Ads conversion action, add a `send_to` line with your conversion ID and label, in the same way as the purchase example further down.

#### Facebook (reference only)

RaffleX already sends this event to Facebook automatically. The code below is for reference only. **Do not add it to Header Code**, or add to cart events will be counted twice.

```javascript
// ALREADY HANDLED AUTOMATICALLY. DO NOT ADD.
fbq('track', 'AddToCart', {
    content_name: event.content,
    content_type: 'product',
    content_ids: [event.productId.toString()],
    value: parseFloat((event.productPrice * event.tickets).toFixed(2)),
    currency: 'GBP',
    num_items: event.tickets
}, {
    eventID: event.fbEventId
});
```

***

### Purchase

Fires once per order, on the **Order Successful** page. RaffleX pushes a `purchase` event containing the full order details to `window.dataLayer`, the standard browser data layer used by Google Tag Manager. Because Header Code loads in the `<head>`, a snippet there can watch the data layer and react when the purchase arrives.

If the customer refreshes the **Order Successful** page or comes back to it shortly after, the purchase is not sent again.

#### Hook

The pattern below watches the data layer for the purchase event and calls your function with the order details.

```html
<script>
window.dataLayer = window.dataLayer || [];
(function () {
    var previousPush = window.dataLayer.push;
    window.dataLayer.push = function () {
        var result = previousPush.apply(this, arguments);
        for (var i = 0; i < arguments.length; i++) {
            var entry = arguments[i];
            if (entry && entry.event === 'purchase' && entry.ecommerce) {
                onPurchase(entry.ecommerce);
            }
        }
        return result;
    };

    function onPurchase(data) {
        // Your tracking code here
        // data.transaction_id  the order number
        // data.value           the order total (number)
        // data.currency        "GBP" or "EUR"
        // data.items           one entry per competition in the order
    }
})();
</script>
```

{% hint style="info" %}
**Using several platforms?** Each snippet on this page passes the purchase on to the one before it, so you can paste a TikTok snippet and a Google Ads snippet one after the other and both will fire. If you prefer, you can also use one hook and put all of your platform calls inside its `onPurchase` function.
{% endhint %}

{% hint style="info" %}
**Using Google Tag Manager?** You do not need this hook. In Tag Manager, create a **Custom Event** trigger for the `purchase` event and build your TikTok and Google Ads tags there, using Data Layer Variables to read the fields below. See [What Google Analytics Tracks](/helpful-info/what-google-analytics-tracks.md) for a step by step example.
{% endhint %}

#### Available data

| Property                       | Type   | Description                                                                                       |
| ------------------------------ | ------ | ------------------------------------------------------------------------------------------------- |
| `data.transaction_id`          | string | The order number (the **#** column in your **Orders** list, shown to the customer as **Order #**) |
| `data.value`                   | number | The order total, after any discount code                                                          |
| `data.currency`                | string | `GBP` or `EUR`                                                                                    |
| `data.tax`                     | number | Always `0`                                                                                        |
| `data.shipping`                | number | Always `0`                                                                                        |
| `data.items`                   | array  | One entry per competition in the order                                                            |
| `data.items[].item_name`       | string | The competition title                                                                             |
| `data.items[].item_id`         | number | The competition ID                                                                                |
| `data.items[].quantity`        | number | Number of tickets, including any free bonus tickets from a promotion                              |
| `data.items[].price`           | number | Price per ticket                                                                                  |
| `data.items[].currency`        | string | `GBP` or `EUR`                                                                                    |
| `data.user_data.email_address` | string | The customer's email address                                                                      |
| `data.user_data.phone_number`  | string | The customer's phone number                                                                       |
| `data.user_data.address`       | object | The customer's `first_name`, `last_name`, `street`, `city`, `region`, `postal_code` and `country` |

{% hint style="warning" %}
**Handle customer details with care.** `user_data` contains personal information. Only send it to a platform that supports enhanced or advanced matching, follow that platform's rules on how it should be sent (most expect it hashed or sent through their own matching feature), and make sure your privacy policy and cookie consent cover it.
{% endhint %}

#### TikTok

```html
<!-- TikTok: purchase -->
<script>
window.dataLayer = window.dataLayer || [];
(function () {
    var previousPush = window.dataLayer.push;
    window.dataLayer.push = function () {
        var result = previousPush.apply(this, arguments);
        for (var i = 0; i < arguments.length; i++) {
            var entry = arguments[i];
            if (entry && entry.event === 'purchase' && entry.ecommerce) {
                var data = entry.ecommerce;
                if (typeof ttq !== 'undefined') {
                    ttq.track('CompletePayment', {
                        content_ids: data.items.map(function (item) {
                            return item.item_id.toString();
                        }),
                        content_type: 'product',
                        quantity: data.items.reduce(function (sum, item) {
                            return sum + item.quantity;
                        }, 0),
                        value: data.value,
                        currency: data.currency
                    });
                }
            }
        }
        return result;
    };
})();
</script>
```

TikTok uses `CompletePayment` as the event name for purchases, not `Purchase`. The currency is read from the order, so you do not need to change it for a euro site.

#### Google Ads

RaffleX already sends a standard `gtag('event', 'purchase', ...)` call through your Google tag on the **Order Successful** page. Google Ads only counts that as a conversion if you have a conversion action set up to use it (for example, by importing your Google Analytics `purchase` key event into Google Ads).

To fire a specific Google Ads conversion action directly, use this snippet:

```html
<!-- Google Ads: purchase conversion -->
<script>
window.dataLayer = window.dataLayer || [];
(function () {
    var previousPush = window.dataLayer.push;
    window.dataLayer.push = function () {
        var result = previousPush.apply(this, arguments);
        for (var i = 0; i < arguments.length; i++) {
            var entry = arguments[i];
            if (entry && entry.event === 'purchase' && entry.ecommerce) {
                var data = entry.ecommerce;
                if (typeof gtag === 'function') {
                    gtag('event', 'conversion', {
                        send_to: 'AW-XXXXXXXXX/CONVERSION_LABEL',
                        value: data.value,
                        currency: data.currency,
                        transaction_id: data.transaction_id
                    });
                }
            }
        }
        return result;
    };
})();
</script>
```

Replace `AW-XXXXXXXXX/CONVERSION_LABEL` with your own Google Ads conversion ID and label. You can find both in Google Ads on the conversion action's tag setup screen.

{% hint style="warning" %}
Pick one way of counting purchases in Google Ads. If you import the Google Analytics `purchase` key event **and** fire this conversion snippet, and both are used as primary conversions, sales may be counted twice. Sending the `transaction_id`, as this snippet does, helps Google Ads recognise duplicates of the same order.
{% endhint %}

#### Facebook (reference only)

RaffleX already sends order events to Facebook automatically, from the browser and (with your access token saved) from the Conversions API. It does **not** use the data layer purchase above. Instead, RaffleX decides which event each order sends, so that a Facebook **Purchase** always means money came in:

* **Purchase** for orders paid by card or PayPal and for Site Credit top ups. `value` is only the money taken, so for an order paid partly with Site Credit it is just the card part, and the credit is in a separate `credit_used` field.
* **CreditSpend** (custom event) for orders paid entirely with Site Credit. There is no `value`. The credit spent is in `credit_value`.
* **FreeEntry** (custom event) for £0 orders, such as free competitions, free entries and 100% discount codes. There is no `value`.
* Nothing for Winlink prize tickets or the entries Club members receive automatically.

The code below shows the shape of what is sent and is for reference only. **Do not add it to Header Code.**

```javascript
// ALREADY HANDLED AUTOMATICALLY. DO NOT ADD.

// Paid orders and Site Credit top ups
fbq('track', 'Purchase', {
    value: 9.99,               // money taken only
    currency: 'GBP',
    content_type: 'product',
    content_ids: [123],
    content_name: 'Competition title',
    num_items: 10,
    credit_used: 2.00          // only when part paid with Site Credit
}, {
    eventID: 'order_1234'
});

// Orders paid entirely with Site Credit
fbq('trackCustom', 'CreditSpend', {
    currency: 'GBP',
    content_type: 'product',
    content_ids: [123],
    content_name: 'Competition title',
    num_items: 10,
    credit_value: 5.00         // credit spent, never in value
}, {
    eventID: 'order_1234'
});

// £0 orders
fbq('trackCustom', 'FreeEntry', {
    currency: 'GBP',
    content_type: 'product',
    content_ids: [123],
    content_name: 'Competition title',
    num_items: 1
}, {
    eventID: 'order_1234'
});
```

The browser and the Conversions API send the same event name, the same details and the same `order_` event ID, so Meta counts each order once.

{% hint style="info" %}
**The data layer purchase is different.** The `purchase` event in `window.dataLayer` (used by the TikTok and Google Ads snippets above) is still sent for every order, including free orders and orders paid with Site Credit, with `data.value` set to the order total. That is why Meta's Purchase figures can be lower than the figures you see on other platforms. If you want another platform to ignore free orders, skip any purchase where `data.value` is `0`.
{% endhint %}

***

### Facebook and Meta: what RaffleX does for you

Once your **Facebook Pixel ID** is saved on the **Marketing** settings page, RaffleX installs the Meta Pixel on every page and sends these standard events:

| Event                    | When it fires                                                                          | How it is sent                    |
| ------------------------ | -------------------------------------------------------------------------------------- | --------------------------------- |
| **PageView**             | Every page                                                                             | Browser pixel                     |
| **ViewContent**          | A customer views a competition                                                         | Browser pixel                     |
| **AddToCart**            | Tickets are added using the ticket selector on a competition page                      | Browser pixel and Conversions API |
| **InitiateCheckout**     | A customer opens the checkout                                                          | Browser pixel and Conversions API |
| **Purchase**             | An order takes money (card, PayPal or a Site Credit top up), valued at the money taken | Browser pixel and Conversions API |
| **CreditSpend** (custom) | An order is paid entirely with Site Credit                                             | Browser pixel and Conversions API |
| **FreeEntry** (custom)   | A £0 order is completed                                                                | Browser pixel and Conversions API |
| **CompleteRegistration** | A customer creates an account                                                          | Browser pixel and Conversions API |

The **Conversions API** sends events straight from RaffleX to Meta, so they still count when a customer's browser blocks tracking. It only runs once you have also saved your **Facebook Conversion Access Token** on the **Marketing** settings page. Events sent both ways carry a matching event ID, so Meta counts each one once. Order events sent this way also carry the customer's own IP address, browser, customer ID and the page the order was placed from, which helps Meta match them to the right person.

**CreditSpend** and **FreeEntry** appear in Events Manager without any setup. To optimise campaigns or build audiences on them, create a custom conversion for each. See [Use FreeEntry and CreditSpend in your campaigns](/marketing/facebook-and-meta/tracking.md#use-freeentry-and-creditspend-in-your-campaigns).

The **Marketing** settings page also has an optional **New Customer FBQ Purchase Event** setting. When it is on, RaffleX sends an extra custom Facebook event called `NewPaidCustomerPurchase` on a customer's first order that took money (card, PayPal or a Site Credit top up). Free orders and orders paid entirely with Site Credit never count. It is sent from the browser and, with your access token saved, from the Conversions API under the same event ID. The browser copy does not include the customer's email address, phone number or postal address, because Meta does not allow plain text personal details in event data. You define it in Meta Events Manager and can then use it to build audiences of new paying customers.

Adding your own `fbq` calls to Header Code will cause duplicate events and inflated reporting. For setup steps, see [Facebook and Meta tracking](/marketing/facebook-and-meta/tracking.md).

***

### Testing your tracking

1. Install the platform's browser helper: **TikTok Pixel Helper**, **Google Tag Assistant** or **Meta Pixel Helper** (all free browser extensions).
2. Open your site in a normal browser window with ad blockers switched off.
3. Add tickets to your basket from a competition page and check the helper shows your add to cart event.
4. Complete a small test order and check the helper shows the purchase event on the **Order Successful** page.
5. Check the platform's own events screen (for example **Events** in TikTok Ads Manager or **Conversions** in Google Ads). Some platforms take a few hours to show new events.

{% hint style="info" %}
If an event does not appear, open your browser's developer console (press **F12** and click **Console**) and look for red error messages. The most common causes are a snippet placed above the platform's base code, a missing `<script>` tag, or a typo when pasting.
{% endhint %}

***

### Important notes

* **Facebook is automatic.** Do not add your own `fbq` calls. See the table above for what RaffleX sends.
* **Only two currencies.** RaffleX sites use either `GBP` or `EUR`, always in capitals. Match the currency set in **Settings**, then **Checkout**.
* **AddToCart only fires for tickets added with the ticket selector on a competition page.** Tickets added through the upsell popup at checkout, or with quick add buttons on competition cards and homepage blocks, do not trigger it.
* **Purchase fires once per order**, on the **Order Successful** page. A refresh or quick return visit does not send it again.
* **Purchases are sent from the customer's browser.** If a customer closes the tab before the **Order Successful** page loads, or blocks tracking, TikTok and Google will not see that sale. (Facebook still does, as long as your Conversion Access Token is saved, because the Conversions API sends the purchase from RaffleX itself.) Always use your RaffleX **Orders** list and [Analytics](/helpful-info/analytics.md) for your true sales figures.
* **Put the purchase hook in Header Code.** That way it is always in place before the **Order Successful** page sends the purchase data.
