Quickstart: Send your first search events with the Events API

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

  • 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

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

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.
Info

Your Luigi's Box tracker_id can be found in your Luigi's Box application in "General settings" > "Integration settings".

Step-by-step

All events are sent via an HTTP POST request to https://api.luigisbox.com/.

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

{
  "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

  • 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

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 device
CLIENT_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
  end
end

# --- 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 autoloader
require '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 device
const 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

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

{
  "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

# (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: Verify your integration

You can watch your events arrive in real-time inside the Luigi's Box application.

  1. Log in to your Luigi's Box application.
  2. Navigate to "General settings" in the menu, and then select Live session explorer.
  3. From your app or backend, trigger the events from Step 1 and Step 2.
  4. 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.
Note

Understanding user sessions & data visibility:

In Luigi's Box, a user session is a sequence of events from a single user (identified by their client_id).

The live session explorer is designed for real-time debugging. It shows you events from active, in-progress sessions as they are received by Luigi's Box, often with only a minimal delay. This allows you to verify your integration immediately as you send test events.

Separately, a user session is considered closed or finished for main analytics processing after 30 minutes of inactivity (meaning no new events have been sent for that client_id for 30 minutes). It's typically after a session is closed and processed that its data will appear in the main Luigi's Box analytics dashboards and reports.

So, use the live session explorer for immediate event verification, and expect data in the main dashboards to appear after sessions have fully concluded.

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 items array ([]). This is crucial for identifying poorly performing queries.
  • Use stable, matching IDs: The identifier you use in the url field 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_timestamp field, 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 id field.
  • 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

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 click event, but change the action.type to a descriptive name like "add_to_cart".
  • Track purchases: To track a completed sale, send a transaction event. This event has its own specific structure for listing all the items in the order.