AACsearch
Analytics

Conversions

Search-to-click, search-to-conversion, and revenue attribution — how to instrument and interpret conversion data.

A conversion is whatever your business defines as "the user did the thing": purchase, signup, add-to-cart, lead form submission. AACSearch can attribute conversions back to the search that led to them — but only if you instrument it.

This page covers the data model, the instrumentation, and the metrics teams actually look at.

Two conversion concepts

1. Search-to-click (CTR)

Did the user click any result from the search? Computed automatically from search_query and result_click events tied by queryId.

Formula:

CTR = clicks / searches

No extra instrumentation required. CTR is the cheapest health signal: a healthy storefront search usually lands in 15–40 % depending on catalog.

2. Search-to-conversion

Did the user complete a downstream action after the search? Requires you to emit a conversion event with the same queryId and (optionally) a value.

Formula:

Conversion rate = conversions / searches
Revenue per search = sum(conversion.value) / searches

Instrumentation: see below.

Instrumenting a conversion

The conversion event is just another track call. Use sendBeacon for purchase flows so the event still fires when the user navigates away:

import { trackEvent } from "@aacsearch/client";

trackEvent({
  type: "conversion",
  conversionType: "purchase",
  productId: "sku-123",
  queryId: lastSearchQueryId,    // tie back to the search that led here
  metadata: {
    value: 9999,                 // BigInt minor units (kopecks/cents)
    currency: "USD",
    orderId: "ord_…",
  },
});

Tips:

  • Always include queryId if a search occurred earlier in the same session — without it, you cannot attribute the conversion.
  • conversionType is a free string but stay disciplined: "purchase", "signup", "add_to_cart", "contact", "lead". Mixed taxonomies make every dashboard worse.
  • metadata.value is BigInt minor units (Invariant 8) so the same row works for revenue aggregation without floating-point rounding.

The widget ships with helpers for the common cases — see Widget analytics events — but the same hand-rolled call works from any storefront.

Attribution windows

A conversion is attributed to the most recent search in the same sessionId. The default attribution window is 30 minutes since the last search_query event from that session. After that, the conversion is recorded but unattributed.

You can override the window via metadata.attributionWindowSec on the conversion event — useful for slow purchase funnels (B2B) — but consistency matters more than precision: pick a window and stick to it.

Filter and sort attribution

Conversions also carry the filters and sort that were active when the user clicked through:

trackEvent({
  type: "conversion",
  ...,
  filters: { brand: ["Sony"], priceRange: [50, 200] },
  sort: "_text_match:desc",
});

This makes "what filters drive revenue?" a sliced view of the same data. A standard cut: conversion rate per filters.brand value over a 7-day window.

Reading conversions

Top converting queries

const top = await orpc.search.topQueries.call({
  organizationId,
  period: "30d",
  orderBy: "conversion_rate_desc",
  limit: 20,
});

Each row carries conversions, conversionRate, and revenueKopecks (if metadata.value was set on the events). Look for:

  • High searches + high conversion rate. Champion queries. Make sure they keep working — pin curations defensively.
  • High searches + low conversion rate. The user gets there but doesn't buy. Often a pricing / merchandising issue, not a search issue.
  • High conversion rate + low search volume. Hidden gem queries. Promote them in suggestions / popular-queries surfaces.

Conversion funnel

search → click → product page → add to cart → checkout → purchase

Each step has a type you can emit (result_click, metadata.step = "product_view" | "add_to_cart" | "checkout_start" | "purchase"). Pull all of them by queryId, group by step, and you get the funnel.

The dashboard ships with a basic search → click → conversion view; deeper funnel views are a custom-dashboard exercise via analyticsEvents and the export feed.

Revenue per search is the most useful single number in e-commerce search analytics. Compute it as:

revenue per search = sum(conversion.metadata.value) / searches

Both numerator and denominator should be from the same period, ideally a rolling 28-day window so you average out day-of-week effects.

Track it weekly. A 10 % drop without an obvious cause usually means a curation or synonym change regressed something — bisect the rule changes in RelevanceCurations for the same window.

Conversions on the AI surfaces

AI answers and Knowledge RAG also emit conversion events when wired up:

  • ai_answerresult_clickconversion is the same chain as a regular search.
  • knowledge_ask rarely has a conversion in the e-commerce sense, but you can emit conversion with conversionType: "support_resolved" from the help-center feedback widget — making the AI's value measurable in dollars saved per ticket.

Privacy

Conversions can carry an orderId or a transaction reference. Treat them as PII for retention purposes:

  • Don't put PII (email, phone) inside metadata.
  • The default 90-day retention applies to conversion events the same way it applies to search_query.
  • For right-to-be-forgotten, deletion by anonymousUserId cascades to conversion rows.

See Data privacy for the full retention and erasure policy.

Sampling

For very high-traffic storefronts, you can sample non-conversion events at the SDK level to reduce write volume — but never sample conversion events. The cost of an under-counted conversion is much higher than the cost of an over-counted view.

The widget ships with a 1.0 sample rate for conversions and a tunable rate for visit / widget_open.

On this page