POST /events
Content-Type: text/plain:
that keeps the request a CORS simple request, so the browser skips the preflight
round trip, and it works with keepalive when the page is unloading.
202 with the number of events accepted:
- All events in one request must share the same
merchant_id; a mixed batch is rejected with400. - At most 100 events and 256 kB per request; larger requests are
rejected with
413. - Malformed events fail the whole batch with
422. - Delivery is fire-and-forget. Ignore the response body in production code.
POST /events?depict_token=YOUR_TOKEN. The token is safe to ship in
client-side code — it only authorises writing events for your store.
Fields on every event
string
required
Which event this is. See the table below.
string
required
Your merchant id.
string
required
The same per-tab id you send on search requests.
string
The same per-browser id you send on search requests. Without it, every visit
from a returning shopper counts as a new user.
string
Storefront language, matching the search request.
string
Country code, matching the search request.
string
A UUID you generate per event. Used to de-duplicate, since
keepalive
requests can be delivered more than once. Strongly recommended.string
ISO 8601 timestamp of when the event happened in the browser. The server also
stamps its own arrival time; sending both makes batching and unload delay
measurable.
Event types
Every search-related event carriessearch_id: the id of the search whose
results the shopper acted on, or null if the interaction did not come from a
search. Getting this right is what makes per-query analytics work.
position is the zero-based index of the product in the result list. It is what
makes click-through rate comparable across result sets — send it.
For add_to_cart, product_id must be the main product id, the same id
space as product_click and product_impression, or per-product funnels will
not join. Keep the variant in variant_id and the variant SKU in sku.
For purchase, transaction_product_ids are the variant SKUs, and prices are
per line item after discounts. quantities, prices and
transaction_product_ids are parallel arrays.
Attribution
add_to_cart and purchase usually happen long after the search, often on a
different page, so the search context has to be carried across. The Depict modal
does it like this, and a custom UI should follow the same rules:
- When the shopper clicks a result — not when the search runs — store the
search_id, thequery, and a timestamp insessionStorage. A click is engagement; an abandoned search should not claim credit for a later purchase. - On
add_to_cartandpurchase, read that record back and send itssearch_idandqueryon the event. - Expire it after 30 minutes. A click half an hour before checkout is unlikely to have caused the conversion, and unbounded attribution inflates per-query conversion rates.
- Clear it after a purchase. A later cart event should not be re-attributed to a search that already converted.
"search_id": null and "query": null rather than
omitting the fields. That records the conversion as non-search traffic instead
of silently dropping it.
Batching and volume
Impressions are the highest-volume event by far. Buffer them briefly and send them as one array — the Depict modal collects impressions for 50 ms and flushes whatever accumulated:Worked example
Verifying
Confirm each event type reaches Depict once before you launch:POST /eventsreturns202withacceptedequal to the number of events you sent.accepted: 0for a non-empty batch means every event was dropped.- Analytics in the Depict portal appear with a processing delay — check for presence first, exact counts later.
- Confirm
search_idis populated on click, impression, add-to-cart and purchase events. Attribution silently disappears when it isnulleverywhere, and nothing about the response tells you it happened.

