Guides
Events
Send views, clicks, add-to-carts and orders from your pages and your server. Each index's Events page shows them, and orders are kept for Bought together lists.
Events say what shoppers do with your products: what they view, click, add to carts, and order. Each index's Events page in the dashboard shows them, about a minute after they're sent. Orders are also kept for Bought together, a list of products that were bought in the same orders, which is coming to Recommendations.
Events carry no cookie, ID, or customer details, and they don't count as searches.
From your pages
With the browser SDK (0.8.0) and the index's public ID, the same client that searches:
import { SearchBrowserClient } from "@findlane/browser"
const client = new SearchBrowserClient({ publicId: "pub_…" })
// A search result was clicked: its place in the results, from 1, and the query.
client.events.click("sku-123", { query: "red shoes", position: 3 })
// A recommendation was clicked: the list, and the product whose list it was.
client.events.click("sku-456", { list: "goesWith", from: "sku-123", position: 1 })
client.events.view("sku-123")
client.events.addToCart([{ objectID: "sku-123", quantity: 1, price: 49.9 }], { currency: "ISK" })Events are sent in the background, a batch a second, and whatever is waiting goes when the page is hidden or closed. They're sent as text/plain, so browsers don't ask the API first (no CORS preflight), and by navigator.sendBeacon, which outlives the page. Requests come only from the index's allowed origins, as browser search does.
Events from crawlers and scripts are answered and dropped. The Events page counts them under Not recorded.
Consent
Events hold no cookie or ID, but some EU guidance counts anything a page's script sends about a visit under its consent rules. If your consent banner should cover events, hold them until the shopper agrees:
const client = new SearchBrowserClient({ publicId: "pub_…", events: "afterConsent" })
// When the shopper accepts. consent(false) drops the events held so far.
client.events.consent(true)events: false turns them off.
From your server
Send orders from your server, where they're paid. Use a key with the Events scope (write keys have it too), from the server SDK (0.9.0):
import { SearchServerClient } from "@findlane/server"
const index = new SearchServerClient({
workspaceId: process.env.FINDLANE_WORKSPACE_ID!,
apiKey: process.env.FINDLANE_EVENTS_KEY!,
}).index("products")
await index.events.send([
{
type: "purchase",
orderID: "1001",
currency: "ISK",
items: [
{ objectID: "sku-123", quantity: 1, price: 49.9 },
{ objectID: "sku-456", quantity: 2, price: 9.9 },
],
},
])
// An order that was cancelled or refunded.
await index.events.cancelOrder("1001")An order counts once, however often it's sent, so sending one again after an error is safe.
Past orders
Bought together is better from the first day with your order history. importOrders sends orders up to 400 days old, 200 a request, from a list or an async iterable such as a database cursor:
const result = await index.events.importOrders(
db.orders.find({ createdAt: { $gte: lastYear } }).map((order) => ({
orderID: order.number,
timestamp: order.createdAt,
items: order.lines.map((line) => ({ objectID: line.sku, quantity: line.quantity })),
})),
)
// { accepted: 48210, rejected: [] }Past orders go to Bought together and to the order counts on the Events page. They don't appear among recent events.
The events
| Type | Fields |
|---|---|
view | objectID |
click | objectID; position, from 1; query for a search result, or list (goesWith, similar, together) and from for a recommendation |
addToCart | items; currency |
purchase | orderID and items; currency |
cancel | orderID. From servers only: order numbers are often guessable. |
Each item is { objectID, quantity, price }; only objectID is required. currency is a three-letter code, such as ISK or EUR. From servers, any event may have a timestamp, in milliseconds, up to 400 days back.
Object IDs aren't checked when events arrive. Products that aren't in the index are left out when lists are built.
Orders from browsers
Orders sent from browsers show on the Events page, but don't count for Bought together, unless you turn on Count orders sent from browsers on the index's Events page. It's off because anyone with the public ID can send an order, and a store that also sends orders from its server would count each one twice.
Turn it on if your store has no server to send orders from. Each visitor then counts for at most 3 orders a day. To tell visitors apart for that, a browser order keeps a hash of the visitor's network address and browser, made with a random value that changes every day and is then deleted, so the hash can't be traced back to the address. It's removed from the order after two days.
Limits
| Events a request | 100 from browsers, 200 from servers |
| Requests a minute | 600 per visitor for each public ID, and 600 per API key |
| Items an event | 1,000; an order keeps its first 50 different products |
position | 1 to 1,000 |
query | Kept as search analytics keep queries: lowercase, up to eight words |
timestamp | From servers only, up to 400 days back |
The API answers 202 with what it accepted, and which events it refused and why. One bad event doesn't refuse the rest:
{ "accepted": 3, "rejected": [{ "index": 1, "reason": "position must be a whole number from 1 to 1,000" }] }A request that isn't a list of events answers 400, and over the rate limit 429. If orders can't be accepted for a moment, the request answers 503 and is safe to send again. The SDKs retry it, and wait out the rate limit.
What's kept
- For the Events page: each event's type, product, query words, list, position, quantities, prices and currency, with the country, device type, and site it came from. Never an IP address, a user agent, or an order ID. Kept three months, like search analytics.
- For Bought together: which products were in each order, when, and a hash of its order ID, so it's counted once and can be cancelled. No customer, address, or amounts. Kept 400 days, and deleted with the index.
HTTP
POST /api/public/indexes/{publicId}/events
POST /api/teams/{workspaceId}/indexes/{index}/eventsThe body is { "events": [...] }. Browser requests need an allowed Origin, as browser search does; server requests need an API key with the events or write scope. See the HTTP API.