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 / searchesNo 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) / searchesInstrumentation: 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
queryIdif a search occurred earlier in the same session — without it, you cannot attribute the conversion. conversionTypeis a free string but stay disciplined:"purchase","signup","add_to_cart","contact","lead". Mixed taxonomies make every dashboard worse.metadata.valueis 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 → purchaseEach 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
Revenue per search is the most useful single number in e-commerce search analytics. Compute it as:
revenue per search = sum(conversion.metadata.value) / searchesBoth 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 Relevance → Curations for the same window.
Conversions on the AI surfaces
AI answers and Knowledge RAG also emit conversion events when wired up:
ai_answer→result_click→conversionis the same chain as a regular search.knowledge_askrarely has a conversion in the e-commerce sense, but you can emitconversionwithconversionType: "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
conversionevents the same way it applies tosearch_query. - For right-to-be-forgotten, deletion by
anonymousUserIdcascades 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.
Related pages
- Analytics overview
- Queries — search and click data without the conversion layer
- No-results — conversion-zero queries to fix at the content level
- Widget analytics events — what the widget emits, including conversions
- Billing wallet usage — the kopecks money model used in
metadata.value