XeniaXenia Docs

Events and conversions

PreviousNext

Start journeys from your site, record analytics events, and credit purchases to the journey that earned them.

Every event goes through one call, track(eventName, props?, opts?), to one endpoint. What happens to it depends on opts.trigger:

triggerDeliveryWhat it does
trueSent immediatelyStarts or advances any journey listening for this event, and records it. Never sampled or held for consent.
false (default)Batched, consent-gatedRecords an analytics event only. No journey moves.

Journey triggers

The event name must match the event configured on the journey's Event Trigger node, exactly.

Without JavaScript

Add data-xenia="<event>&<interaction>" to any element. Elements added to the page later are picked up too, and every attribute fire is a journey trigger.

<button data-xenia="signup&click">Sign up</button>
<section data-xenia="pricing&view-port">Pricing</section>
<div data-xenia="landing&page-load"></div>
InteractionFires when
clickThe element, or anything inside it, is clicked
view-portThe element scrolls into view
page-loadThe element is on the page

In React, <XeniaTrack> does the same without the attribute:

import { XeniaTrack } from "@xenia-martech/sdk-js/client/react";
 
<XeniaTrack event="signup" on="click" as="button" props={{ placement: "footer" }}>
  Sign up
</XeniaTrack>;

In code

// Browser
xenia.track("add_to_cart", { productId: "p1", price: 49.99 }, { trigger: true });
 
// Server
await xenia.track("add_to_cart", {
  trigger: true,
  identity: { userId: "c1" },
  properties: { productId: "p1", price: 49.99 },
});

Every browser event carries the visitor's identity, UTM parameters, journey, and the page's url, path and query automatically. On the server, bind those once per request (see Server-side).

Analytics events

With the default trigger: false, track() records a fact for analytics and reporting.

track("ctaClick", { productId, label: "Buy now" });
track("pageView", { referrer: document.referrer });
  • Batched. Events flush every 20 events or 3 seconds, whichever comes first, plus a best-effort flush when the page is hidden.
  • Consent first. Nothing is sent until consent({ analytics: true }) is called. Earlier events are queued and flush once consent is given.
  • Do Not Track respected. With DNT or Global Privacy Control on, analytics-only events are dropped entirely. Turn this off with respectDnt: false.
  • Any name works. A camelCase set (pageView, ctaClick, formSubmitted, and more) autocompletes in TypeScript. Other names are recorded as custom events.

Identity

An analytics event needs at least one way to know who it belongs to: the visitor id, a userId, email or phone, or a utm_id. An event with none is dropped.

In the browser the visitor id is resolved for you, so anonymous events still count. Once you know who the visitor is, tell Xenia:

xenia.identify("<your customer id>");

identify() keeps the id locally and links the visitor to that customer in the background. For a customer id of your own, pass it as identity.userId. There is no customerId field on events.

xenia.consent({ analytics: true, marketing: false, personalization: false });

The browser client also writes the consent to a xenia_consent cookie, so your server can read it with getConsent() or, in Next.js, consentFromNext().

Conversions

A purchase is a track() whose name is a recognized conversion name, such as purchase or order_complete. For it to be recorded and credited correctly, include all of these:

IncludeWhy
Identityemail, userId or the visitor id, so the right customer is credited
orderValue, currency, orderIdIn properties, so revenue can be measured
utm_idThe id from the journey link the shopper clicked. Credits the exact message they clicked.
eventIdA stable key such as ord-1001, so sending the same order twice counts once
Analytics consentConversions are not recorded without it

In the browser, utm_id is captured from the landing URL and added for you. On the server, pass it yourself, since there is no URL to read it from.

await xenia.track("purchase", {
  trigger: true,
  eventId: "ord-1001",
  identity: { email: "buyer@example.com" },
  consent: { analytics: true },
  properties: {
    customEventName: "purchase",
    orderId: "1001",
    orderValue: 120,
    currency: "USD",
    utm_id: "<from the journey link>",
  },
});

eventId is capped at 26 characters. Longer keys are folded into a stable 26 character digest, so the same order still produces the same id. Omit it and the SDK generates one with generateUlid().

Crediting a server-side purchase to a browser journey

A purchase recorded on your server is only credited to a journey the shopper entered in the browser if both use the same visitor id. The browser SDK writes it to the xenia_vid cookie for this reason. Read it back when you create the server client:

const xenia = createXeniaServer({
  ...config,
  visitorId: getVisitorId(request.headers.get("cookie")) ?? undefined,
});

A fully server-rendered app with no browser SDK has nobody writing that cookie. Create one on first use and reuse it for the whole session, or use xeniaProxy() in Next.js, which does it for you.

What the server tells you back

On the server, track() returns how the event was handled: accepted and rejected counts, how many were sampled, deduped or shed, and per event in results, whether it was accepted, why not if rejected, and whether it triggered a journey.