Developer documentation

Install the JavaScript snippet

For the developer of a custom-built website (not Shopify or WooCommerce, which have their own plugins). Five steps: get the snippet, paste it, connect your cookie banner, send the shop events, and report real orders from your server.

1. Get your snippet

Sign in to Aopy and open Tracking → Configuration → Website tracking. The Installation Snippet card holds the code for your account, with your public key already filled in. Press Copy Snippet. It looks like this:

<!-- Aopy Tracking -->
<script>
!function(){var c=window.cdm=window.cdm||[];if(!c.init){
c.methods=["identify","track","page","viewedProduct","addedToCart","removedFromCart","startedCheckout","checkoutStep","completedOrder","searched","addedToWishlist","removedFromWishlist","watchProduct","consent","consentState","reset","visitorId","adoptAid"];
c.factory=function(m){return function(){c.push([m].concat([].slice.call(arguments)))}};
for(var i=0;i<c.methods.length;i++)c[c.methods[i]]=c.factory(c.methods[i]);
c.load=function(k,o){c._tk=k;c._opts=o||{};var s=document.createElement("script");s.async=1;
s.src="https://app.aopy.com/tracker/v1/cdm.min.js";document.head.appendChild(s)};
}}();
cdm.load("tk_YOUR_PUBLIC_KEY", { apiUrl: "https://app.aopy.com" });
cdm.page();
</script>

Your own copy may point at a first-party tracking subdomain instead of https://app.aopy.com once you have verified one. Always paste the copy from the dashboard, not the sample above.

The keys themselves are in Tracking → Configuration → Developers. There are two:

2. Paste it in the head of every page

Put the snippet inside <head> on every page of the site: product pages, cart, checkout and the thank-you page included. In a shared layout or template that is one place. The script loads asynchronously, so it does not slow the page down. It sends the page view for you (cdm.page() is the last line of the snippet).

On a single-page app the page does not reload when the visitor moves around. Call cdm.page() yourself after every route change.

4. Tell Aopy what visitors do

These calls take one object each. Call them when the thing happens; the ones made before the tracker has loaded are queued, so ordering is not a worry.

CallWhen
cdm.page(properties?)Every page view. The snippet already calls it once; call it again on each route change in a single-page app.
cdm.viewedProduct({ product_id, product_name, value, currency, category, brand })On a product page.
cdm.addedToCart({ product_id, product_name, value, currency, quantity })When something goes into the cart. value is unit price x quantity.
cdm.removedFromCart({ product_id, quantity })When something leaves the cart.
cdm.startedCheckout({ value, currency, items })When the visitor reaches checkout.
cdm.identify({ email, firstName?, lastName?, phone? })When you learn the visitor's email (login, sign-up, checkout form). email is required.
cdm.completedOrder({ order_id, value, currency, items })On the thank-you page. order_id is required. This is a browser event, not an order.
cdm.searched({ search_query })On a site search. An empty query is never sent.
cdm.track(name, properties?)Your own events.
// Product page. product_id MUST equal the id of the product in your product feed.
cdm.viewedProduct({
  product_id: "SKU-123",
  product_name: "Wireless Headphones",
  value: 99.99,          // MAJOR units: 99.99, never 9999
  currency: "EUR",
  category: "Audio",
  brand: "Acme"
});

// Add to cart - value is unit price x quantity
cdm.addedToCart({
  product_id: "SKU-123",
  product_name: "Wireless Headphones",
  value: 99.99,
  currency: "EUR",
  quantity: 1
});

// Checkout page
cdm.startedCheckout({
  value: 99.99,
  currency: "EUR",
  items: [{ product_id: "SKU-123", quantity: 1 }]
});

// The visitor gives you their email (login, sign-up, checkout form)
cdm.identify({ email: "customer@example.com", firstName: "Ana", lastName: "Popescu" });

// Thank-you page. order_id is REQUIRED, otherwise a page reload records the
// purchase twice. This is a browser event - NOT an order (see below).
cdm.completedOrder({
  order_id: "1042",
  value: 99.99,
  currency: "EUR",
  items: [{ product_id: "SKU-123", quantity: 1 }]
});

5. Orders come from your server

cdm.completedOrder() in the browser is not an order. It records a purchase event in the visitor's journey. It creates no order record, is never sent to Meta, TikTok or Google Ads as a conversion, and anyone could fake it from a browser. A real order has to be reported by your server, after the payment, with your secret key.

Your server calls POST /api/v1/orders with the secret rk_ key (and one for refunds and status changes). The full description, with every field and an example, is in the Orders API v1 section of the Tracking API page. Use the same value for order_id in the browser and external_id on the server, so the two are recognised as the same purchase.

Pass the visitor id to your server

The order only joins the browsing that led to it (which ad, which page) if the server sends cdm.visitorId() as aid. Read it in the browser at checkout and submit it with the order to your own server. In Strict mode it is empty until the visitor has accepted analytics consent and the tracker has loaded (in the other consent modes the id exists as soon as the tracker has loaded); when it is empty, just send the order without it.

// On the page where the buyer pays (checkout), read the visitor id and
// submit it with the order to YOUR server, for example in a hidden field:
var aid = (window.cdm && window.cdm.visitorId && window.cdm.visitorId()) || "";
document.querySelector("#aopy_aid").value = aid;

// Your server then sends it to Aopy as "aid" in POST /api/v1/orders.
// An empty value is fine: send the order without "aid".

Allowed domains

Under Tracking → Configuration → Developers there is an Allowed Domains list. While it is empty, any website that has your public key can send events (“development mode”). Add your real domains (for example shop.example.com, up to 20) and only requests coming from those sites are accepted; events from anywhere else are rejected. Add your staging domain too if you want to test there. Do this before you go live.

How to check it works

  1. Open your site in a private window and accept your cookie banner (the tracker sends nothing before that on the default setting).
  2. Open the browser developer tools, tab Network. A request to /api/track on the address in your snippet should answer 200. A 401 or 403 means the key or the allowed-domains list is wrong. No request at all usually means consent was never given.
  3. In the Console, run the two lines below to see the tracker's own state.
    // In the browser console of your site:
    cdm.consentState()
    // -> { consent: "granted", mode: "strict", purposes: {...}, aid: "..." }
    
    cdm.visitorId()
    // -> a string such as "a3f9c1..." once the visitor has accepted; in Strict mode it is null before that
  4. In Aopy, Tracking → Configuration → Website tracking shows Live visitors (people on your site in the last 5 minutes) and a Test integration button that fetches your storefront and checks that tracking is installed on it.