Overview

Overview

Tocadule is a first-party event pipeline with an activation layer on top. A tiny browser snippet sends events from your storefront to the Tocadule API. On the server we resolve the visitor's identity, enrich the event (geo, device, traffic source, ad click IDs), store it, and forward it server-side to your ad destinations (Meta CAPI, TikTok Events API, GA4). The same event stream powers audiences, segments, on-site popups, abandoned-cart automations, and analytics.

Because the send happens server-to-server, it survives ad blockers and Safari ITP, and it carries stronger match signals than a browser pixel alone.

One install gives you both: a clean first-party data layer and the activation channels that read from it. You keep your existing pixels — Tocadule sits in their call path and dedupes against them rather than replacing them.

Install

Paste these three lines into your storefront <head> (or your theme's global template). Replace YOUR_WORKSPACE_ID with your workspace ID from Connections.

<link rel="preconnect" href="https://app.tocadule.com" crossorigin>
<script>window.tocadule=window.tocadule||{};window.tocadule._q=window.tocadule._q||[];['track','identify','cart','requestPush'].forEach(function(m){window.tocadule[m]=window.tocadule[m]||function(){window.tocadule._q.push([m,arguments])}});</script>
<script async data-workspace="YOUR_WORKSPACE_ID" src="https://app.tocadule.com/snippet.js"></script>

Why three lines? The middle line is a tiny stub. It makes window.tocadule exist immediately, so any tracking code that runs before the async snippet.js finishes loading queues its calls into window.tocadule._q instead of silently no-oping. When snippet.js loads, it builds the real API and replays the queue in order. This is what lets you drop tocadule.track('purchase', …) directly into a template that renders above your snippet tag.

💡

If you have a verified custom domain, the install serves snippet.js from your own subdomain (e.g. data.yourshop.com/snippet.js) for full first-party cookies. The snippet auto-derives its API base from wherever it was loaded — no extra config.

The API

The snippet exposes four functions on window.tocadule:

CallPurpose
tocadule.track(name, props)Send an event (page view, product view, purchase, or any custom name).
tocadule.cart(data)Send the visitor's whole current cart on every change. PII-free.
tocadule.identify(email, traits)One-time richer profile call at sign-up / login.
tocadule.requestPush(cb)Prompt for web-push permission and subscribe the visitor.

A lot happens automatically. On every track() call the snippet attaches UTM parameters, device / browser / OS, traffic source (including AI-assistant referrers), ad click IDs (gclid, gbraid, wbraid, fbclid, ttclid, …), page URL / path / title, locale, and screen metrics — so your event payloads stay small and you don't repeat yourself. Common commerce interactions — product / collection / cart views, add_to_cart, and search — are also auto-detected on supported platforms; you only need manual track() calls for data we can't observe from the page: most importantly purchase (with order_id + revenue), plus checkout_started, sign_up, and identify.

Identity: put the email on the event

You do not need to call identify() before every event. You can put contact identifiers — email, phone, first_name, last_name, external_id — on any event. If the visitor isn't known yet, we auto-create the contact and link it to their anonymous_id; if we already know them, we enrich the existing contact.

There are two ways to link a visitor to a contact, and every visitor needs at least one:

Linking signalWhat it does
identify() onceLinks the contact and backfills all of that visitor's prior anonymous events.
Email on an eventLinks from that event forward only — earlier anonymous events are not backfilled.

Passing the email on browsing events is what lifts match quality (EMQ): a logged-in shopper's product_view or add_to_cart carries their email, so the server-side forward to Meta / TikTok / Google is a strong match instead of an anonymous one.

<script>
  if (window.tocadule) {
    var sub = function(v){ return v && String(v).indexOf('{') < 0 ? v : undefined; };
    tocadule.track('product_view', {
      product_id: sub('{productId}'),
      name: sub('{productName}'),
      price: parseFloat('{productPrice}') || 0,
      currency: 'TRY',
      brand: sub('{productBrand}'),
      category: sub('{productCategory}'),
      product_image_url: sub('{productImage}'),
      product_url: window.location.href,
      // Optional — if the shopper is logged in, pass their email. We auto-link the
      // contact (no separate identify needed) and match quality on this event jumps.
      email: sub('{email}')
    });
  }
</script>
⚠️

The sub() guard returns undefined for any value that still contains a { — an un-replaced template token like {email} never gets sent as junk. Keep it.

identify() stays as the one-time profile call — use it at sign-up, login, or newsletter opt-in. It sets subscription intent and full name / location, and because it backfills, it's the cleanest single linking signal:

<script>
  // Call this ONCE, at sign-up / login / newsletter opt-in — it's the richer
  // one-time profile call (sets subscription intent + full name/location).
  // After this you do NOT need to call identify again: just pass email on your
  // events (optional) and we keep the contact linked. Everything except email is
  // optional — country + city unlock location segments.
  if (window.tocadule) {
    tocadule.identify('{email}', { first_name: '{firstName}', last_name: '{lastName}', phone: '{phone}', country: '{country}', city: '{city}' });
  }
</script>

Cart tracking is separate — and PII-free

Send your whole current cart on every change with tocadule.cart(...) (not track). The latest cart is the source of truth for cart segments, product-in-cart popups, and abandoned-cart automations. On supported platforms (Shopify, IdeaSoft, WooCommerce, T-Soft, and any store the universal reader recognises by shape) this is captured automatically.

<script>
  // Send your WHOLE current cart on EVERY change (add / remove / quantity).
  // Build items[] by looping over every line in the cart — one object per line.
  // The values below are EXAMPLES — replace them with your real cart data.
  //   REQUIRED per item: product_id, name, price (number), quantity (number)
  //   OPTIONAL per item: image, url  (make the cart cards look nice)
  if (window.tocadule) {
    tocadule.cart({
      items: [
        { product_id: '101', name: 'Blue T-Shirt', price: 199.90, quantity: 2, image: 'https://yourshop.com/img/blue.jpg', url: 'https://yourshop.com/blue-tshirt' },
        { product_id: '250', name: 'Black Cap',    price: 149.00, quantity: 1, image: 'https://yourshop.com/img/cap.jpg',  url: 'https://yourshop.com/black-cap' }
        // …repeat one object for EACH product currently in the cart
      ],
      total: 549.80,     // cart subtotal as a number
      currency: 'TRY',
      item_count: 3       // total quantity across all lines
      // No email / PII here on purpose — the cart stays lean. Once any other event
      // carries the shopper's email (purchase, or a logged-in product_view /
      // checkout), we link the contact and the cart resolves off the anonymous_id.
    });
  }
</script>
⚠️

Never put an email or other PII on tocadule.cart(). The cart carries only the anonymous_id and resolves to a contact through the identity map. Keep it lean.

Custom events are welcome

tocadule.track() accepts any event name — you're not limited to the standard set. A custom event is stored on the events table exactly like a standard one, with all the same automatic enrichment. It shows up in your Events / Data Collection list (which lists every distinct event name your workspace has received) and is available as a segment / automation trigger and in analytics. Standard commerce names (product_view, add_to_cart, checkout_started, purchase, view_cart, view_item_list, search) get extra downstream handling — canonical mapping, gap-fill, and ad-destination forwarding — so prefer them for commerce steps and use custom names for everything else.

What happens to an event

  1. Snippet attaches enrichment (UTM, device, traffic source, click IDs, page context) and POSTs to /events/track with your workspace_id, the anonymous_id, and both consent lanes.
  2. Identity — we look up the anonymous_id in the identity map. If the event carries an email and the visitor isn't linked yet, we resolve-or-create the contact and link it.
  3. Enrichment — server-side geo (see below) and device / IP are merged into the event (values you passed always win).
  4. Bot check — requests from known bots are flagged is_bot; analytics and ad forwarding exclude them.
  5. Store — the event is written to the events table.
  6. Forward — non-bot events are enqueued for server-side delivery to your matched ad destinations (Meta CAPI, TikTok, GA4), subject to the ad-consent lane.

Consent: two lanes

Tocadule reads consent from your CMP (Google Consent Mode v2, IAB TCF v2, OneTrust, Cookiebot, Iubenda, GPC…) and treats it as two independent lanes:

LaneFieldGates
Analytics / generalconsent_stateStoring the anonymous_id + PII. We store unless it is explicitly denied.
Advertisingad_consentForwarding to ad destinations, as full / ldu / modeled.

Email / SMS opt-in (subscribed_email, subscribed_sms) is a separate merchant preference set via identify() traits or your preference center — it is not a CMP lane.

💡

No CMP? Drive consent directly: tocadule.consent.set('granted' | 'denied') for the analytics lane and tocadule.consent.setAd('granted' | 'denied') for the ad lane.

Geo & dedup, briefly

  • Location comes from MaxMind (offline GeoLite2 City) resolved from the real client IP — the single source of truth for event country / region / city / postal / lat-lon / timezone. CloudFront is a fallback and provides the ASN used for bot detection. Location segments are event-based (they read properties.country from events).
  • Contact profile location is declared-only — set from identify() traits (country, city), never inferred from IP.
  • Dedup is handled for you: browser events dedup on anonymous_id, external events (Shopify, connectors) on source_event_id. You never set dedup IDs yourself.

Next steps

  • Standard events — the exact tocadule.track() script for each commerce event.
  • Identity & contacts — linking, backfill, and the email-on-event contract.
  • Cart tracking — tocadule.cart(), supported platforms, and cart health.
  • Consent & privacy — the two lanes, CMP detection, and cookieless mode.
  • Destinations — connecting Meta, TikTok, and GA4 for server-side forwarding.