>_bandit
changelog

Integrating Tracking into Your Store

Who this document is for: store owners and developers connected to Advertorial Bandit.
Time to integrate: 15–30 minutes, depending on store complexity.
You will need: access to your site's HTML templates. No server-side changes required.


Table of Contents

  1. What It Is and Why You Need It
  2. How It Works (diagram)
  3. Step 0. What You Need to Know Before Starting
  4. Step 1. Adding the Script — Exactly One Tag
  5. Step 2. Marking Up Buttons — The Engine of Your Funnel
  6. Step 3. The Most Important Part — The Thank-You Page
  7. Step 4. Public JS API — For Advanced Users
  8. Step 5. Verification — What You Should See
  9. FAQ — 18 Questions and Answers
  10. Shopify — Step-by-Step Guide
  11. Custom-Built Stores (PHP/Laravel/Django/etc) — Examples
  12. Privacy and Legal

What It Is and Why You Need It

Imagine: your advertorial brings a visitor to your store, and they make a purchase. But how do you know the purchase happened specifically after reading that article? Without tracking, these are two unrelated events: someone read something, someone bought something.

Our script connects them.

Ad → Article (our domain) → Your store (your domain) → Purchase ↑ this link is our job

A ~5 KB script does exactly three things:

  1. Reads the visitor identifier from the URL (it arrives via the ?tid= parameter from the article)
  2. Sends events to our system — store page view, add to cart, purchase
  3. Does not break your site — all code is in try/catch, zero dependencies, loads asynchronously

After integration, you will see in the dashboard: how many people came from an article, how many reached the cart, how many purchased. This is the foundation for calculating the ROI of each specific article.


How It Works

Article
article domain
?tid=abc123user clicks CTA
Your store
your domain
  • ·script reads tid from URL
  • ·saves it in localStorage
events
store_page_viewadd_to_cartpurchase
Tracking API
yourfragranceworld.com

collects everything into one journey

The cross-domain identifier tid is a UUID (36 characters), for example:
019b2f80-1234-7abc-89de-fedcba987654

It travels between domains via ?tid= in the URL. Once in your store, it is saved in the browser's localStorage — so it survives page refreshes or a return visit an hour later.

What If the User Loses Their tid?

If a person came directly (bookmark, search, typed URL) — the script generates a new tid. Such visits will not be attributed to an article, but are still recorded. This is normal: an article only gets attribution for visitors who actually arrived via a link from it.


Step 0. What You Need to Know Before Starting

QuestionAnswer
Are server-side changes needed?No. The script works entirely on the client side (browser).
Will the script slow down the site?No. ~5 KB, async loading, does not block rendering.
Does the script break your JS?No. All code is in try/catch, only window.bandit is used.
Is SSL required on the store?No, but if the store is on HTTPS, the script must also load over HTTPS (we do).
Which browsers?All modern ones. Chrome 60+, Firefox 60+, Safari 12+, Edge 79+.
Is CORS needed?Already configured. The server responds with Access-Control-Allow-Origin: *.
What about the cookie banner?See the "Privacy and Legal" section.

Step 1. Adding the Script — Exactly One Tag

The simplest way is to insert one <script> tag into the <head> of your site. On every page.

<script src="https://yourfragranceworld.com/bandit-store.js" data-endpoint="https://yourfragranceworld.com" async></script>

Insert it into your shared template — usually header.tpl, layout.twig, _document.ejs, or equivalent. If you have multiple pages with different templates — insert it into each one.

Where Exactly in <head>?

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>My Store</title> <!-- ... other tags ... --> → <script src="https://yourfragranceworld.com/bandit-store.js" → data-endpoint="https://yourfragranceworld.com" → async></script> </head> <body> <!-- store content --> </body> </html>

Script Attributes

AttributeRequiredWhat It Does
srcYesURL of the script on our server
data-endpointYesTracking API address. Always https://yourfragranceworld.com
data-consentNo"granted" (default) or "denied" — if the visitor has not given consent
data-debugNoIf specified — events are logged to the browser console. Useful during setup, remove in production

After Insertion

The script loads asynchronously and begins working immediately: it sends a store_page_view event on page load. Next — we mark up the buttons.

Installing via Google Tag Manager (GTM)

If you add the script through GTM (Custom HTML tag), there's a nuance: GTM injects the script asynchronously into <body>, and data- attributes on the tag may not be read. But it still works — the endpoint https://yourfragranceworld.com is already baked into the script by default, so it's enough to just insert the tag:

<!-- GTM → Tags → New → Custom HTML --> <script src="https://yourfragranceworld.com/bandit-store.js" async></script>

Trigger: All Pages (on all store pages).

If you need to override settings (endpoint, consent) — set them via window.bandit.config before the script loads, in a separate Custom HTML tag with higher priority:

<script> window.bandit = window.bandit || {}; window.bandit.config = { endpoint: "https://yourfragranceworld.com", consent: "granted" }; </script>

Important: the script locates its own tag by src and picks up data- attributes even when document.currentScript is unavailable (as in GTM). And if nothing is configured — it uses the baked-in default endpoint. So in the simplest case, one line is enough.


Step 2. Marking Up Buttons — The Engine of Your Funnel

The script listens for clicks on elements with the data-bandit-event attribute. You simply add a few data attributes to your existing buttons. No JavaScript coding required.

Complete List of Events

EventWhen It TriggersWhat Is Sent
add_to_cartClick on an "add to cart" buttonproduct_id, value, currency
begin_checkoutClick on a "checkout" buttonproduct_id
payment_clickClick on a "pay" buttonproduct_id, value, currency
purchaseClick on the "thank you for your order" pageorder_id, value, currency

Button Template

<button data-bandit-event="EVENT_TYPE" data-bandit-product="PRODUCT_SKU" data-bandit-value="AMOUNT" data-bandit-currency="USD"> Button text </button>

Where:

  • data-bandit-event — event type (required)
  • data-bandit-product — SKU, article number, or product ID (recommended)
  • data-bandit-value — amount in the store's currency, a number without spaces, e.g. 29.90 or 1499.90
  • data-bandit-currency — currency code: USD, EUR, GBP, RUB, etc.

The attribute can be on any element. Click on a button, link, div — the script will catch it.

Example 1. Product Card

<div class="product-card"> <img src="kettle.jpg" alt="SmartBoil Kettle Pro"> <h2>SmartBoil Kettle Pro</h2> <p class="price">$29.90</p> <p>New-generation induction kettle. Boils in 90 seconds.</p> <button type="button" class="btn btn-primary" data-bandit-event="add_to_cart" data-bandit-product="SB-KETTLE-PRO" data-bandit-value="29.90" data-bandit-currency="USD"> 🛒 Add to Cart </button> </div>

Example 2. Cart — Multiple Items

If you have multiple items in the cart, it is enough to mark up only the total amount before checkout:

<div class="cart-summary"> <p>Items: 3</p> <p>Total: $89.70</p> <a href="/checkout" class="btn btn-checkout" data-bandit-event="begin_checkout" data-bandit-product="SB-KETTLE-PRO,SB-FILTER,SB-MUG" data-bandit-value="89.70" data-bandit-currency="USD"> 📦 Proceed to Checkout </a> </div>

Example 3. Payment Page

<form action="/pay" method="POST"> <!-- card fields, address... --> <button type="submit" id="pay-button" data-bandit-event="payment_click" data-bandit-product="SB-KETTLE-PRO" data-bandit-value="29.90" data-bandit-currency="USD"> 💳 Pay $29.90 </button> </form>

Example 4. Dynamic Store (React/Vue/SPA)

If buttons are added dynamically (via JavaScript), data attributes work exactly the same way — the script listens for clicks on document, it does not attach handlers to specific elements:

// React <button data-bandit-event="add_to_cart" data-bandit-product={product.sku} data-bandit-value={product.price} data-bandit-currency="USD" onClick={handleAddToCart} > Add to Cart </button> <!-- Vue --> <button data-bandit-event="add_to_cart" :data-bandit-product="product.sku" :data-bandit-value="product.price" data-bandit-currency="USD" @click="addToCart" > Add to Cart </button>

Best practice: do not remove your store's native JS. Data attributes go in addition to your onClick, not instead of it.


Step 3. The Most Important Part — The Thank-You Page

Purchase is the final event of the funnel. Without it, there is no conversion data. There are three ways to send it. Choose whichever is easiest for your store.

Simply add a container with attributes to the thank-you page. On a click anywhere inside this container, the script sends the purchase event.

<!-- /thank-you page --> <div data-bandit-event="purchase" data-bandit-order-id="ORD-2026-001" data-bandit-value="89.70" data-bandit-currency="USD"> <h1>✅ Order Placed!</h1> <p>Order number: ORD-2026-001</p> <p>Amount: $89.70</p> <p>We will send the tracking number to your email.</p> </div>

Why a click and not page load?
The thank-you page can be refreshed (F5) — a duplicate purchase. The script is protected against duplicates (deduplication by order_id), but a click is more reliable: the user sees the page → understands the order is accepted → clicks somewhere → the event fires once.

Method B. Programmatic (Maximum Control)

If your store is an SPA, or the thank-you page is generated on the fly, use the JavaScript API:

<script> // After the order is created on the server and the thank-you page has loaded: window.bandit.purchase({ orderId: "ORD-2026-001", // unique order number (required) value: 89.70, // order amount (required) currency: "USD" // currency (required) }); </script>

Important: orderId must be unique. The server uses it to deduplicate repeated submissions. Use the real order number from your system.

Method C. Server-Side Injection (Laravel/Django/PHP/etc)

If your backend knows the order data when rendering the page, inject it directly into the template:

<!-- Laravel Blade --> @if($order) <script> window.bandit.purchase({ orderId: "{{ $order->number }}", value: {{ $order->total }}, currency: "{{ $order->currency }}" }); </script> @endif <!-- Django Template --> {% if order %} <script> window.bandit.purchase({ orderId: "{{ order.number }}", value: {{ order.total|floatformat:2 }}, currency: "{{ order.currency }}" }); </script> {% endif %} <!-- PHP / Twig / Smarty --> <?php if ($order): ?> <script> window.bandit.purchase({ orderId: "<?= htmlspecialchars($order['number']) ?>", value: <?= (float) $order['total'] ?>, currency: "<?= htmlspecialchars($order['currency']) ?>" }); </script> <?php endif; ?>

Purchase Summary

MethodWhen to UseComplexity
A. data attributeSimple HTML/template-based storeMinimal
B. JS APISPA (React/Vue), dynamic storesModerate
C. Server-side injectionLaravel, Django, PHP — order is known on the serverModerate

Step 4. Public JS API — For Advanced Users

After the script loads, the window.bandit object is available. If your store uses complex logic (modal carts, AJAX checkout, web components) — the API gives you full control.

// Any event with arbitrary data window.bandit.track("custom_action", { action: "watched_video", video_id: "tutorial-1", duration_sec: 45 }); // Purchase window.bandit.purchase({ orderId: "ORD-" + Date.now(), value: 59.90, currency: "USD" }); // Get the current tid (useful for debugging) console.log(window.bandit.getTid()); // → "019b2f80-1234-7abc-89de-fedcba987654" // Consent management (cookie banner) window.bandit.setConsent("denied"); // stop sending events window.bandit.setConsent("granted"); // resume

Load Ordering (Important for SPAs!)

If your JS code calls window.bandit before the script has loaded — no problem. The script supports a command queue:

<head> <!-- 1. Your code that registers the queue --> <script> window.bandit = window.bandit || function() { (window.bandit.q = window.bandit.q || []).push(arguments); }; // These calls go into the queue and execute after the script loads: bandit('track', 'early_event', { note: 'I was here before the script loaded' }); </script> <!-- 2. The script loads later, reads the queue, executes commands --> <script src="https://yourfragranceworld.com/bandit-store.js" data-endpoint="https://yourfragranceworld.com" async></script> </head>

Step 5. Verification — What You Should See

5.1 Quick Console Check

Add the data-debug attribute to the <script> tag (for testing only):

<script src="https://yourfragranceworld.com/bandit-store.js" data-endpoint="https://yourfragranceworld.com" data-debug async></script>

Open the browser console (F12 → Console). You should see:

[bandit] track store_page_view {} [bandit] track add_to_cart { product_id: "SB-KETTLE-PRO", value: 29.90, currency: "USD" } [bandit] purchase ORD-001 29.90 USD

5.2 Network Check

Open the Network tab in DevTools (F12 → Network), filter by track. You should see POST requests to:

  • https://yourfragranceworld.com/api/track/v1/collect — response 204 No Content
  • https://yourfragranceworld.com/api/track/v1/purchase — response 204 No Content

5.3 Integration Checklist

Go through the items, checking off completed ones:

  • Script inserted in <head> on all store pages
  • Attribute data-endpoint="https://yourfragranceworld.com" is specified
  • "Add to cart" buttons are marked up with data-bandit-event="add_to_cart"
  • "Checkout" button is marked up with data-bandit-event="begin_checkout"
  • "Pay" button is marked up with data-bandit-event="payment_click"
  • Thank-you page sends purchase (data attribute or window.bandit.purchase(...))
  • order_id is unique for each order (no hardcoding!)
  • data-debug attribute is removed in production
  • When visiting the store via a link with ?tid=..., events are visible in the console
  • Network tab shows POST requests with 204 No Content

FAQ

Q1: What happens if the script fails to load?

Nothing. The store continues to work as usual. The script does not block rendering (async) and does not affect the site's functionality. Events for that visit will be lost — this is a deliberate trade-off (~5 KB script, on CDN, failure probability is near zero).

Q2: Does the script see form field contents, passwords, card numbers?

No. The script reads only the data- attributes that you yourself specified, and the page URL. It does not scan <input> elements, does not intercept form submissions, and does not touch cookies from other services.

Q3: Can I use the script only on some pages?

Technically — yes. But we recommend on all pages. Because:

  • store_page_view should fire when entering any store page
  • A user may land on /product/abc directly, bypassing the homepage
  • A purchase can happen on any URL

Q4: What if I have an AJAX cart (item added without page reload)?

Data attributes on the add_to_cart button work regardless of whether the page reloads or not. A click is a click.

Q5: What if the user clicked "pay" but the payment failed?

The payment_click event is specifically the click, not payment confirmation. It is needed to measure the funnel (how many people reached the payment stage). A purchase is only recorded on the thank-you page — i.e., after the transaction has actually completed.

Q6: What if an order is not created immediately (e.g., cash on delivery)?

Use window.bandit.purchase(...) at the moment the order is actually created in your system. This could be via webhook, cron, or admin — the API is always available.

Q7: Is the script protected against duplicate purchase sends on F5 on the thank-you page?

Yes. The server deduplicates by (tid, order_id). Even if the event was sent twice, there will be only one record in the database.

Q8: How does the script distinguish between different users?

By tid — the cross-domain identifier. It arrives in the URL (?tid=...) from the article. If the user came on their own — the script generates a new tid and saves it in localStorage. A cookie is set, but only SameSite=Lax, without linking to other domains.

Q9: What if the user uses an ad blocker?

Ad blockers do not affect the script. It loads from our domain (yourfragranceworld.com), not from ad networks. The endpoint /api/track/v1/collect is a regular POST, not resembling trackers (google-analytics.com, facebook.com/tr, etc.). In our experience, ad blockers do not touch such requests.

Q10: How do I disable tracking for admin test orders?

If you know an order is a test — simply do not call window.bandit.purchase(...) in that scenario. Or temporarily call:

window.bandit.setConsent("denied");

Q11: Do I need to update the script?

No. You reference https://yourfragranceworld.com/bandit-store.js — we update the file on our side. The browser cache (1 hour via Cache-Control) will automatically pick up the new version.

Q12: Does the script support multiple currencies in one store?

Yes. The data-bandit-currency attribute is specified separately for each button. You can pass USD, EUR, GBP, etc. depending on the context.

Q13: What if I have two stores on different domains?

Install the script on each. tid is transmitted via URL, so:

  • From article → store 1 (shop1.com?tid=...) — tid is saved
  • From article → store 2 (shop2.com?tid=...) — tid is saved separately
  • If the user navigated from store 1 to store 2 — the link is lost (different localStorage)

Q14: Does the script work on mobile devices?

Yes. sendBeacon and fetch keepalive are supported by all mobile browsers. The script size (~5 KB) is unnoticeable even on 3G.

Q15: Does the script affect Core Web Vitals (LCP, INP)?

No. The script loads with async, does not block HTML parsing. Event listeners use delegation (one handler on document) and passive mode.

Q16: Can I host the script on my own server?

Technically — yes. The bandit-store.js bundle is self-contained and has no dependencies. But we recommend referencing our URL — then you automatically receive updates.

Q17: Is X-Frame-Options or CSP required?

No. The script runs inside the page, does not use iframes. Content Security Policy does not block yourfragranceworld.com unless you have explicitly forbidden external scripts.

Q18: I've set everything up — how do I make sure the data is coming through?

Write to us in chat — we will check your tid in the admin panel and confirm that events are being recorded.


Shopify — Step-by-Step Guide

Shopify Specifics

  • Theme customization is done via theme.liquid — the main theme file
  • Thank-you pagecheckout/thank_you (Order Status). Available in Settings → Checkout → Order Status page → Additional scripts
  • Cart attributes — Shopify allows passing arbitrary data to the order via hidden form fields

Step S1. Add the Script to the Theme

Open Shopify Admin → Online Store → Themes → three dots → Edit code.

Find theme.liquid (usually in the root or layout/ folder). Inside <head>, add:

<!-- theme.liquid --> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> {{ content_for_header }} <!-- ... other theme tags ... --> <!-- Advertorial Bandit Tracking --> <script src="https://yourfragranceworld.com/bandit-store.js" data-endpoint="https://yourfragranceworld.com" async></script> </head>

Save the file.

Step S2. Mark Up Buttons in the Theme

In theme files (e.g., sections/main-product.liquid, snippets/product-form.liquid), find the buttons and add data attributes:

<!-- product-form.liquid: add to cart button --> <button type="submit" name="add" class="product-form__submit button button--full-width" data-bandit-event="add_to_cart" data-bandit-product="{{ product.selected_or_first_available_variant.sku }}" data-bandit-value="{{ product.selected_or_first_available_variant.price | money_without_currency }}" data-bandit-currency="{{ cart.currency.iso_code }}"> {{ 'products.product.add_to_cart' | t }} </button> <!-- cart.liquid: checkout button --> <button type="submit" name="checkout" class="cart__checkout-button button" data-bandit-event="begin_checkout" data-bandit-value="{{ cart.total_price | money_without_currency }}" data-bandit-currency="{{ cart.currency.iso_code }}"> {{ 'sections.cart.checkout' | t }} </button>

Step S3. Thank-You Page (Order Status)

Shopify does not allow editing the Order Status page HTML directly, but allows inserting "Additional scripts":

  1. Settings → Checkout → Order status page → Additional scripts
  2. Insert:
{% if first_time_accessed %} <script> window.bandit.purchase({ orderId: "{{ order.name }}", value: {{ order.total_price | money_without_currency }}, currency: "{{ order.currency }}" }); </script> {% endif %}

first_time_accessed — critically important! Without it, the script would execute on every page refresh, sending duplicates (though the server would filter them out). With first_time_accessed, the code only runs once on the first display.

Step S4 (Optional). Pass tid to the Order

If you want to additionally link tid with the order on the Shopify side, you can pass it through cart attributes:

In theme.liquid or the cart form, add a hidden field:

<input type="hidden" name="attributes[tid]" id="bandit-tid-input"> <script> document.getElementById('bandit-tid-input').value = window.bandit?.getTid() || ''; </script>

Then, in Shopify Admin, the order details will show a tid field with the identifier.


Custom-Built Stores (PHP/Laravel/Django/etc) — Examples

General Principle

Your server knows everything about the order: number, amount, currency, products. When rendering the thank-you page, you pass this data either via data attributes in HTML or via JavaScript.

Laravel (Blade)

{{-- resources/views/checkout/thank-you.blade.php --}} @extends('layouts.shop') @section('content') <div class="thank-you-page"> <!-- Method 1: data attribute --> <div data-bandit-event="purchase" data-bandit-order-id="{{ $order->number }}" data-bandit-value="{{ $order->total }}" data-bandit-currency="{{ $order->currency }}"> <h1>Order #{{ $order->number }} placed!</h1> <p>Amount: {{ number_format($order->total, 2, '.', ',') }} {{ $order->currency }}</p> </div> </div> {{-- Method 2: JS API (alternative) --}} @push('scripts') <script> window.bandit.purchase({ orderId: @json($order->number), value: {{ $order->total }}, currency: @json($order->currency) }); </script> @endpush @endsection

Django

{# templates/shop/thank_you.html #} {% extends "base.html" %} {% block content %} <div class="thank-you-page" data-bandit-event="purchase" data-bandit-order-id="{{ order.number }}" data-bandit-value="{{ order.total|floatformat:2 }}" data-bandit-currency="{{ order.currency }}"> <h1>Order #{{ order.number }}</h1> <p>Thank you for your purchase!</p> </div> {% endblock %}

WordPress / WooCommerce

If your site is on WordPress with WooCommerce, you can insert code via a hook:

// functions.php or a plugin add_action('woocommerce_thankyou', function($order_id) { $order = wc_get_order($order_id); ?> <script> window.bandit.purchase({ orderId: "<?= esc_js($order->get_order_number()) ?>", value: <?= (float) $order->get_total() ?>, currency: "<?= esc_js($order->get_currency()) ?>" }); </script> <?php });

Or simpler — via a code insertion plugin (WPCode, Custom CSS & JS):

  1. Create a new snippet, type — JavaScript
  2. Pages → Thank You Page only
  3. Code:
if (window.bandit) { var orderNumber = document.querySelector('.woocommerce-order-overview__order strong')?.textContent?.trim(); var totalText = document.querySelector('.woocommerce-order-overview__total .woocommerce-Price-amount')?.textContent?.trim(); var total = parseFloat(totalText?.replace(/[^0-9,.]/g, '').replace(',', '.')) || 0; if (orderNumber && total > 0) { window.bandit.purchase({ orderId: orderNumber, value: total, currency: 'USD' }); } }

PHP (Plain, No Framework)

<?php // thank-you.php — after order creation $order = getOrderFromDatabase($orderId); ?> <!DOCTYPE html> <html> <head> <title>Order Placed</title> <script src="https://yourfragranceworld.com/bandit-store.js" data-endpoint="https://yourfragranceworld.com" async></script> </head> <body> <h1>Thank you for your order, <?= htmlspecialchars($order['customer_name']) ?>!</h1> <p>Order number: <?= htmlspecialchars($order['number']) ?></p> <script> // Wait for bandit-store.js to load var check = setInterval(function() { if (window.bandit) { clearInterval(check); window.bandit.purchase({ orderId: <?= json_encode($order['number']) ?>, value: <?= (float) $order['total'] ?>, currency: <?= json_encode($order['currency']) ?> }); } }, 100); </script> </body> </html>

What Is Collected

DataExamplePurpose
tid (UUID)019b2f80-...Cross-domain visit identifier
User-AgentMozilla/5.0 ... Chrome/125Mobile/desktop detection, bot filtering
IP (hashed)a1b2c3d4... (SHA-256)Anti-fraud, geo (country only). Irreversible hash.
Event timestamps2026-06-15T14:23:00ZAction sequence, session duration
Page URLhttps://shop.com/product/abcContext: which page the event occurred on

What Is NOT Collected

The script never reads or transmits:

  • Input field contents (<input>, <textarea>)
  • Passwords, card numbers, CVC
  • Cookies from other sites
  • localStorage of other services
  • Geolocation via browser API

The script sets one first-party cookie:

_bandit_tid = <UUID>
  • Purpose: preserving tid between visits to your domain
  • Lifetime: 90 days
  • SameSite: Lax (not sent on cross-domain requests)
  • HttpOnly: no (needed for reading from JavaScript)
  • Third-party cookies: the script does not set them

GDPR / CCPA

tid does not contain personal data. It is a random UUID that cannot be linked to a specific person without additional information (which we do not have). IP is hashed irreversibly (SHA-256 + salt).

Recommendation: if you have a cookie banner and the user declined analytics, call:

window.bandit.setConsent("denied");

The script will stop sending events.


Contacts

Have questions? Having trouble setting things up?