Skip to content

Impressions and clicks

The SDK renders inside your page, so it is the only party that knows whether a creative was ever actually on screen. It therefore owns the count: every request tells the server not to count that delivery server-side, and the browser reports what a visitor really saw.

An impression fires once the block has actually been seen

Section titled “An impression fires once the block has actually been seen”

Two conditions, both required:

  1. The element has been at least 50% visible for 300 milliseconds.
  2. The copy has settled: the final text for this page view is on screen.

Then exactly one impression is sent, per block, per page view. Not per render, not per state change, and not twice under React strict mode.

Waiting for settle means the report describes what the visitor read, including whether adapted copy was on screen, rather than a half-finished guess.

Attach the ref and impressions count themselves

Section titled “Attach the ref and impressions count themselves”

Visibility is measured on the element you attach ref to:

const { state, ref } = useIsland({ campaignId, island: "hero", fallback });
return <section ref={ref}>…</section>;

Hero and Cta do this for you. A custom component that forgets it renders perfectly and reports zero traffic. Because the SDK has already told the server it owns the count, nothing else counts it either. There is no warning.

Cta sends a click event and then runs your onClick and the navigation, in that order and without waiting. Build your own button and you send it yourself:

import { track } from "@sparkletree/core";
track({ apiBase, organizationId, campaignId, variantId }, "click", { island: "cta" });
SentNotes
Organization, campaign, variant, surfaceWhatever applies to the block.
Event typeimpression, click, conversion or custom.
Per-tab session idRandom, stored in sessionStorage. Not a cross-site identifier.
Where the copy came fromGenerated for this context, served from cache, or your own fallback.
Whether adapted copy was on screenSo what visitors actually saw is measurable rather than assumed.
Degraded reasonPresent when the server served a fixed version instead of generating one.

Events are fire-and-forget with keepalive, so an impression recorded as the visitor navigates away still gets out. A failed request is a lost data point and never an exception in your render tree.

import { track } from "@sparkletree/core";
track(
{ apiBase, organizationId, campaignId },
"conversion",
{ plan: "pro", value: 49 },
);

track never throws and never blocks (the await is optional and changes nothing).

A block that names a surfaceId instead of a campaignId does not know its campaign up front. The server names the campaign it served in the final message, and impressions and clicks are attributed to that.

The gap: a request that fails before the campaign is resolved records nothing, because there is nothing to attribute it to.

CopyCard and ProductCard do not open a request of their own, so they do not suppress server-side counting and do not need a ref. They are counted exactly as any non-SDK consumer of the same content is.

Fragments are served from a shared cached read keyed by context, not per visitor, and they fire no impression or click beacons at all. What you see in the dashboard for fragments is mint and serving activity, not visitor-level analytics. Islands are the instrumented surface; if a fragment needs click tracking, it is your own button and your own analytics call.