Custom events

Custom events

Tocadule ships with a set of standard commerce events (product_view, add_to_cart, checkout_started, purchase, …). But tocadule.track() accepts any event name you want. If you have a behaviour that matters to your store — wishlist_added, size_guide_opened, filter_applied, gift_message_written — you can send it, and Tocadule stores it, counts it, and lets you drill into it.

This page explains exactly what happens to a non-standard event once you fire it, and where it shows up in the dashboard so you can confirm it arrived.

tocadule.track('size_guide_opened', {
  product_id: 'SKU-102',
  category: 'shoes'
})

Prerequisite: the install stub

tocadule.track() only works once the tracking object exists on the page. The snippet loads asynchronously, so any inline call that runs before it finishes would be lost — unless you install the tiny stub loader in your site-wide <head>. The stub queues every track / identify / cart / requestPush call into window.tocadule._q and the real snippet replays the queue in order once it boots.

<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>
⚠️

Install the stub — don't rely on the raw async snippet on its own. Without the stub, window.tocadule doesn't exist until snippet.js finishes loading, so an inline tocadule.track(...) that runs first either throws (if it's unguarded) or is silently skipped by an if (window.tocadule) guard. The stub defines window.tocadule synchronously, so once it's in your <head> every call — guarded or not — is queued into _q and replayed in order. Tocadule's standard event scripts wrap the call in if (window.tocadule) { … } as a belt-and-braces check; with the stub installed that guard is simply always true.

Naming rules

  • tocadule.track(name, props) accepts any string as the event name — there is no server-side allow-list on the track path. The request only needs a workspace and an event name to be stored.
  • Convention: use snake_case (lowercase letters, digits, underscores). It keeps your event catalog readable and matches the standard event names. Tocadule's no-code Event Rules builder enforces snake_case; hand-written track() calls are not forced to, but you should follow it anyway.
  • Event names are case-sensitive and matched exactly. Size_Guide and size_guide become two separate rows in your catalog — pick one spelling and stick to it.

What Tocadule does with a custom event

When a custom event arrives at /events/track, it goes through the same ingestion pipeline as a standard event. Nothing is thrown away for being non-canonical — the event_name is stored verbatim. Concretely, for every event:

StepWhat happens
StoredA row is written to the events table with your exact event_name and the properties JSON you sent.
Contact resolutionIf the visitor has an anonymous_id, we link the event to their contact. If the event carries an email (and analytics consent isn't denied), we auto-create/link the contact from it — no separate identify() needed.
Geo enrichmentCountry / region / city / postal / timezone are resolved from the request IP (MaxMind, offline) and merged into properties (your own values win). Lat/lon only with analytics consent.
Auto-attached propsThe snippet automatically adds UTM params, device / browser / OS, traffic source, page locale, and ad click IDs (gclid, gbraid, fbclid, …) to every event before it's sent.
ConsentBoth consent lanes are recorded on the row: consent_state (analytics) and ad_consent (advertising).
Bot flagThe user agent is checked; bot traffic is stored with is_bot = true and excluded from analytics by default.
DedupBrowser events dedup on anonymous_id; external/webhook events on source_event_id. You never set these yourself.

Identity works on any event. You can put email, phone, first_name, last_name, or external_id on a custom event's properties. If we don't know the visitor yet we create and link the contact; if we do, we enrich it. This is what lifts match quality on otherwise-anonymous browsing events.

Canonical mapping vs. passthrough

Tocadule keeps a fixed set of canonical commerce events and a table of known aliases (ViewContent → product_view, orders/create → purchase, and so on). This mapping is what powers the built-in funnel, the standard-event analytics, and the alias-tolerant segment presets.

  • A canonical event (or a known alias of one) is aggregated onto its canonical row and can flow into the standard funnel, gap-fill measurement, and default ad-destination forwarding.
  • A custom event has no canonical key. It is passed through and stored as-is. It is fully queryable, but it is not folded into the canonical Data Collection funnel, it is not part of the commerce gap-fill counters, and it is not forwarded to Meta / TikTok / Google unless you explicitly add it to a destination's forward list.

In short: standard events get the built-in commerce machinery for free; custom events are stored, counted, and available for you to build on, but you opt them into forwarding and funnels yourself.

Where custom events show up

Once you fire a custom event, you can confirm it landed and inspect it in the dashboard:

  • Events feed — the most recent events across your workspace, newest first, each with its event_name, properties, contact, and timestamp. Your custom event appears here within seconds of firing.
  • Event catalog — a per-event summary (over the last 30 days) with total fires, unique contacts, and last-fired time. Every distinct event name you've sent — standard or custom — has a row here. This is the fastest way to verify a new event is arriving and how often.
  • Event drill-in — filter the feed by a single event_name to see the raw rows and the exact property keys/values each one carried. Use this to confirm your properties payload is shaped the way you expect.
  • Property inspector — for any event name, Tocadule lists the distinct property keys (and sample values) it has seen in the last 30 days, so you can see which fields your custom event is actually delivering.

Custom events do not appear on the canonical Data Collection page — that page only lists the standard commerce events and their sources. Use the Events feed / catalog to find and verify custom events.

Example: a fully-shaped custom event

Custom events use the same track() signature as the standard ones. A good pattern is to guard placeholder tokens so an un-replaced template variable never sends junk (the same sub() helper used in Tocadule's standard scripts):

<script>
  if (window.tocadule) {
    var sub = function(v){ return v && String(v).indexOf('{') < 0 ? v : undefined; };
    tocadule.track('wishlist_added', {
      product_id: sub('{productId}'),
      name: sub('{productName}'),
      price: parseFloat('{productPrice}') || 0,
      currency: 'TRY',
      // Optional — a logged-in shopper's email auto-links the contact.
      email: sub('{email}')
    });
  }
</script>

Firing events without code (Event Rules)

If you can't add script to a page, you can create custom events from Tocadule's no-code Event Rules. A rule watches for a URL match, an element click, a form submit, or a custom JS event, and fires an event name you choose with properties pulled from the page (static values, CSS selectors, form fields, URL params, or cookies).

Event Rules require the event name to be snake_case, and each rule has a Test action that fires a synthetic event so you can confirm it lands in your Events feed before wiring it to the real trigger condition.

Good to know

  • No schema to declare. You don't register custom events ahead of time — the first time you fire one, its row appears in the catalog.
  • Properties are free-form JSON. Send whatever scalar fields are useful. Product fields (name/title, price/amount, image/image_url, url/link, id/product_id) are recognised by common aliases so product cards render without extra mapping.
  • Keep PII off the cart. tocadule.cart() is intentionally PII-free — never put an email on it. Put contact identifiers on track() events instead.