Quickstart: Send your first search events with the Events API
Introduction
Section titled “Introduction”This guide provides the core steps for tracking user search interactions by sending events directly to the Luigi’s Box Events API. This method gives you complete control over the data you send and is the standard approach for integrations in non-browser environments like mobile applications or for tracking events from your server’s backend.
What you’ll learn
Section titled “What you’ll learn”- How to structure and send a JSON payload for a search event.
- How to structure and send a JSON payload for a click event to track user interactions.
- How to use the Live Session Explorer in the Luigi’s Box app to verify your integration is working in real-time.
Who is this guide for
Section titled “Who is this guide for”This guide is for developers who are:
- Integrating Luigi’s Box in a mobile application (iOS, Android).
- Sending analytics events from their backend server.
- Working on a website but prefer not to use the JavaScript-based DataLayer Collector.
Prerequisites
Section titled “Prerequisites”Before you start, please ensure you have the following:
- The ability to make HTTP POST requests from your application or server.
- Luigi’s Box tracker_id.
- A method for generating a unique client_id for each user/device.
Step-by-step
Section titled “Step-by-step”All events are sent via an HTTP POST request to https://api.luigisbox.com/.
Step 1: Send the search view event
Section titled “Step 1: Send the search view event”When a user sees a list of search results, you must send an event to report precisely what they saw. This is the foundation of the feedback loop.
Structure the JSON payload
Section titled “Structure the JSON payload”{ "id": "f3f6917c-2e94-4e38-a744-24cbb82f284d", // A globally unique ID for this event "type": "event", "tracker_id": "YOUR_TRACKER_ID", "client_id": 6667519810961010000, "lists": { "Search Results": { // This exact name is critical "query": { "string": "white shirt" // The user's raw query }, "items": [ { "title": "White shirt v-neck", "type": "item", // The catalog type of the object "url": "39818", // The unique ID matching your catalog "position": 1, // The item's position in the list "price": 19 }, // ... add all other visible results here ] } }, "platform": "ios" // Optional: "ios", "android", "desktop", etc.}Key fields explained
Section titled “Key fields explained”id: Must be a globally unique id for each event (e.g., a UUID).lists."Search Results": The object key must be exactly “Search Results” to be processed correctly.items.url: This is the object’s unique identifier. It must match the ID used in your product catalog.
Examples
Section titled “Examples”These examples show how to make HTTP POST requests to the Events API.
require 'faraday'require 'json'require 'securerandom'
# --- Configuration ---TRACKER_ID = "YOUR_TRACKER_ID"API_ENDPOINT = "https://api.luigisbox.com/"# In a real app, client_id would come from the user's session or deviceCLIENT_ID = 6667519810961010000 # Example client_id
# --- Helper Function using Faraday ---def send_luigis_box_event(payload) # Create a new Faraday connection conn = Faraday.new(url: API_ENDPOINT) do |faraday| faraday.request :json # Automatically encode request body as JSON faraday.adapter Faraday.default_adapter # Use the default HTTP adapter end
begin # Make the POST request response = conn.post do |req| req.url API_ENDPOINT req.headers['Content-Type'] = 'application/json' req.body = payload.to_json end
puts "Response Code: #{response.status}" puts "Response Body: #{response.body}" # Faraday automatically raises errors for 4xx/5xx responses if you use response.success? or conn.response :raise_error rescue Faraday::Error => e puts "Error sending event: #{e.message}" puts "Response body: #{e.response[:body]}" if e.response endend
# --- Search View Event Data ---search_query_term = "white shirt"search_results_items = [ { title: "White shirt v-neck", type: "item", url: "39818", position: 1, price: 19 }, { title: "Another white shirt", type: "item", url: "39819", position: 2, price: 25 }]
search_view_payload = { id: SecureRandom.uuid, type: "event", tracker_id: TRACKER_ID, client_id: CLIENT_ID, lists: { "Search Results" => { query: { string: search_query_term }, items: search_results_items } }, platform: "backend-ruby-faraday"}
# --- Send the Event ---puts "Sending Search View Event..."send_luigis_box_event(search_view_payload)<?php// Make sure to include the Composer autoloaderrequire 'vendor/autoload.php';
use GuzzleHttp\Client;use GuzzleHttp\Exception\RequestException;
// --- Configuration ---$trackerId = "YOUR_TRACKER_ID";$apiEndpoint = "https://api.luigisbox.com/";$clientId = 6667519810961010000;
// --- Helper Function to Generate UUID v4 ---// (Same guidv4 function as before)function guidv4($data = null) { $data = $data ?? random_bytes(16); assert(strlen($data) == 16); $data[6] = chr(ord($data[6]) & 0x0f | 0x40); $data[8] = chr(ord($data[8]) & 0x3f | 0x80); return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));}
// --- Helper Function using Guzzle ---function sendLuigisBoxEvent($url, $payload) { // Create a new Guzzle client $client = new Client();
try { // Make the POST request $response = $client->request('POST', $url, [ 'json' => $payload // 'json' option automatically encodes body and sets headers ]);
echo "Response Code: " . $response->getStatusCode() . "\n"; echo "Response Body: " . $response->getBody()->getContents() . "\n"; } catch (RequestException $e) { echo "Error sending event: " . $e->getMessage() . "\n"; if ($e->hasResponse()) { echo "Error Response Body: " . $e->getResponse()->getBody()->getContents() . "\n"; } }}
// --- Search View Event Data ---$searchQueryTerm = "white shirt";$searchResultsItems = [ ["title" => "White shirt v-neck", "type" => "item", "url" => "39818", "position" => 1, "price" => 19], ["title" => "Another white shirt", "type" => "item", "url" => "39819", "position" => 2, "price" => 25]];
$searchViewPayload = [ "id" => guidv4(), "type" => "event", "tracker_id" => $trackerId, "client_id" => $clientId, "lists" => [ "Search Results" => [ "query" => ["string" => $searchQueryTerm], "items" => $searchResultsItems ] ], "platform" => "backend-php-guzzle"];
// --- Send the Event ---echo "Sending Search View Event...\n";sendLuigisBoxEvent($apiEndpoint, $searchViewPayload);?>const axios = require('axios');const { v4: uuidv4 } = require('uuid'); // For generating unique event IDs
// --- Configuration ---const TRACKER_ID = "YOUR_TRACKER_ID";const API_ENDPOINT = "https://api.luigisbox.com/";// In a real app, client_id would come from the user's session or deviceconst CLIENT_ID = 6667519810961010000; // Example client_id
// --- Helper Function to Send Event ---async function sendLuigisBoxEvent(payload) { try { const response = await axios.post(API_ENDPOINT, payload, { headers: { 'Content-Type': 'application/json' } }); console.log(`Response Code: ${response.status}`); console.log('Response Body:', response.data); // Add error handling based on response.status if needed } catch (error) { if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx console.error(`Error sending event: ${error.response.status}`, error.response.data); } else if (error.request) { // The request was made but no response was received console.error('Error sending event: No response received', error.request); } else { // Something happened in setting up the request that triggered an Error console.error('Error sending event:', error.message); } }}
// --- Search View Event Data ---const searchQueryTerm = "white shirt";const searchResultsItems = [ { title: "White shirt v-neck", type: "item", url: "39818", // Unique ID matching your catalog position: 1, price: 19 }, { title: "Another white shirt", type: "item", url: "39819", position: 2, price: 25 }];
const searchViewPayload = { id: uuidv4(), // Generate a unique ID for this event type: "event", tracker_id: TRACKER_ID, client_id: CLIENT_ID, lists: { "Search Results": { // This exact name is critical query: { string: searchQueryTerm }, items: searchResultsItems // Optional: filters: { "brand": "MyBrand" } } }, platform: "backend-nodejs" // Optional};
// --- Send the Event ---(async () => { console.log("Sending Search View Event..."); await sendLuigisBoxEvent(searchViewPayload);})();Step 2: Send a click event
Section titled “Step 2: Send a click event”When a user clicks on a search result, send a click event. This tells Luigi’s Box that the user showed interest in a specific item from the list you sent in Step 1.
Structure the JSON payload
Section titled “Structure the JSON payload”{ "id": "cecceeef-f82f-4fd0-9caf-aaeef2981370", // A new unique ID for this event "type": "click", "tracker_id": "YOUR_TRACKER_ID", "client_id": 6667519810961010000, "action": { "type": "click", "resource_identifier": "39818" // The unique ID of the item that was clicked }, "platform": "ios"}The resource_identifier must match the url of the item from the search results list so Luigi’s Box can link the click to the original search.
Examples
Section titled “Examples”# (Continuing from the Ruby example above, assuming send_luigis_box_event and config are defined)
# --- Click Event Data ---clicked_item_id = "39818"
click_payload = { id: SecureRandom.uuid, type: "click", tracker_id: TRACKER_ID, client_id: CLIENT_ID, action: { type: "click", resource_identifier: clicked_item_id }, platform: "backend-ruby-faraday"}
# --- Send the Event ---puts "Sending Click Event..."send_luigis_box_event(click_payload)<?php// (Continuing from the PHP example above, assuming sendLuigisBoxEvent, guidv4 and config are defined)
// --- Click Event Data ---$clickedItemId = "39818";
$clickPayload = [ "id" => guidv4(), "type" => "click", "tracker_id" => $trackerId, "client_id" => $clientId, "action" => [ "type" => "click", "resource_identifier" => $clickedItemId ], "platform" => "backend-php-guzzle"];
// --- Send the Event ---echo "Sending Click Event...\n";sendLuigisBoxEvent($apiEndpoint, $clickPayload);?>// (Continuing from the NodeJS example above, assuming sendLuigisBoxEvent, uuidv4 and config are defined)
// --- Click Event Data ---const clickedItemId = "39818"; // ID of the item that was clicked
const clickPayload = { id: uuidv4(), type: "click", tracker_id: TRACKER_ID, client_id: CLIENT_ID, action: { type: "click", resource_identifier: clickedItemId }, platform: "backend-nodejs"};
// --- Send the Event ---(async () => { console.log("Sending Click Event..."); await sendLuigisBoxEvent(clickPayload);})();Step 3: Send an event for no results queries
Section titled “Step 3: Send an event for no results queries”You must send a search event even when a query returns zero results. This is critical for Luigi’s Box to identify queries that need improvement. Send the same event structure with an empty items array.
Structure the JSON payload
Section titled “Structure the JSON payload”{ "id": "a7b8c9d0-e1f2-3456-a7b8-c9d0e1f23456", "type": "event", "tracker_id": "YOUR_TRACKER_ID", "client_id": 6667519810961010000, "lists": { "Search Results": { "query": { "string": "xyznonexistent" }, "items": [] } }}Step 4: Verify your integration
Section titled “Step 4: Verify your integration”You can watch your events arrive in real-time inside the Luigi’s Box application.
- Log in to your Luigi’s Box application.
- Navigate to “General settings” in the menu, and then select Live session explorer.
- From your app or backend, trigger the events from Step 1 and Step 2.
- You will see a new session appear in the explorer for your
client_id, and clicking on it will show you the “Search event” and “Click event” you just sent.
Best practices
Section titled “Best practices”To avoid common issues and ensure high-quality data collection, keep these points in mind:
- Always send an event: If a search returns zero results, you should still send a “Search event” with an empty
itemsarray ([]). This is crucial for identifying poorly performing queries. - Use stable, matching IDs: The identifier you use in the
urlfield for an item must be the unique and immutable (unchanging) identifier that matches your product catalog. - Handle timestamps correctly: If you choose to send the optional
local_timestampfield, ensure the value is in seconds, not milliseconds. Sending milliseconds will cause the event to be decoded as a date far in the future and dropped. - Generate unique event IDs: Every single event you send must have a new, globally unique value for the top-level
idfield. - Handle pagination correctly: The position for each item must be its absolute position in the full list of results. For example, if you show 20 items per page, the first item on page 2 should have
"position": 21, not"position": 1.
Next steps
Section titled “Next steps”Now that you have foundational search tracking working, you can expand it to track critical conversions that provide even stronger signals to the AI models:
- Track “add to cart”: To track this, send a
clickevent, but change theaction.typeto a descriptive name like"add_to_cart". - Track purchases: To track a completed sale, send a
transactionevent. This event has its own specific structure for listing all the items in the order.
Was this page helpful?
Thanks.