blob: 5db05173bcbde9798ffac8ec6658f1c58351e7f0 [file] [log] [blame]
---
title: Tutorials — Getting Started with Apache Unomi 3
description: "Step-by-step tutorials for Apache Unomi 3.0: Docker quick start, manual installation, REST API basics, web tracking integration, and profile personalization."
keywords: "Apache Unomi tutorial, step by step, beginner guide, CDP tutorial, profiles, events, segments, rules"
layout: default
structured_data: >
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "HowTo",
"name": "Getting Started with Apache Unomi 3",
"description": "Learn how to set up and use Apache Unomi 3.0 as a Customer Data Platform with Docker, REST APIs, and web tracking.",
"totalTime": "PT30M",
"step": [
{
"@type": "HowToStep",
"position": 1,
"name": "Start Unomi with Docker",
"text": "Create a docker-compose.yml and run docker-compose up to start Unomi with Elasticsearch."
},
{
"@type": "HowToStep",
"position": 2,
"name": "Verify the cluster",
"text": "Access https://localhost:9443/cxs/cluster with credentials karaf/karaf to verify the installation."
},
{
"@type": "HowToStep",
"position": 3,
"name": "Create a scope and send events",
"text": "Use the REST API to create scopes, send events, and query profiles."
},
{
"@type": "HowToStep",
"position": 4,
"name": "Integrate the web tracker",
"text": "Add the Unomi web tracker to your web pages for automatic event collection and personalization."
}
]
}
</script>
---
<!-- Page Header -->
<section class="page-header">
<div class="container">
<p class="section-label text-hero-label">Tutorials</p>
<h1>Getting Started with Apache Unomi 3</h1>
<p class="lead">From zero to a running CDP in minutes. Pick the path that suits you.</p>
</div>
</section>
<!-- Table of Contents -->
<section class="section section-flush-bottom">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="feature-card">
<h2 class="h6 fw-semibold mb-3"><i class="bi bi-list-ol me-2 text-primary"></i>In this guide</h2>
<div class="row g-3">
<div class="col-md-6">
<ol class="small mb-0">
<li><a href="#docker-quickstart">Quick Start with Docker</a> <span class="badge badge-success">Recommended</span></li>
<li><a href="#manual-install">Manual Installation</a></li>
<li><a href="#first-api-calls">Your First API Calls</a></li>
</ol>
</div>
<div class="col-md-6">
<ol class="small mb-0" start="4">
<li><a href="#web-tracking">Web Tracking Tutorial</a></li>
<li><a href="#rules-personalization">Rules &amp; Personalization</a></li>
<li><a href="#next-steps">Next Steps</a></li>
</ol>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- 1. Docker Quick Start -->
<!-- ============================================================ -->
<section id="docker-quickstart" class="section">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-10">
<span class="badge badge-primary mb-3">Step 1</span>
<h2>Quick Start with Docker</h2>
<p class="text-muted">The fastest way to get Unomi running. You only need <a href="https://docs.docker.com/get-docker/" target="_blank" rel="noopener">Docker</a> installed.</p>
<h3 class="h6 fw-semibold mt-4 mb-3">Create <code>docker-compose.yml</code></h3>
<p>Create a new directory and add a <code>docker-compose.yml</code> file with the following content:</p>
<pre><code>version: '3.8'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:9.1.3
environment:
- discovery.type=single-node
- xpack.security.enabled=false
ports:
- 9200:9200
unomi:
image: apache/unomi:3.0.0
environment:
- UNOMI_ELASTICSEARCH_ADDRESSES=elasticsearch:9200
- UNOMI_THIRDPARTY_PROVIDER1_IPADDRESSES=0.0.0.0/0,::1,127.0.0.1
ports:
- 8181:8181
- 9443:9443
- 8102:8102
links:
- elasticsearch
depends_on:
- elasticsearch</code></pre>
<h3 class="h6 fw-semibold mt-4 mb-3">Start the environment</h3>
<p>From the same directory, run:</p>
<pre><code>docker compose up</code></pre>
<div class="alert alert-warning small" role="alert">
<i class="bi bi-hourglass-split me-1"></i> Wait 1&ndash;2 minutes for both Elasticsearch and Unomi to fully initialize before testing.
</div>
<h3 class="h6 fw-semibold mt-4 mb-3">Verify the cluster</h3>
<p>Open your browser and navigate to:</p>
<pre><code>https://localhost:9443/cxs/cluster</code></pre>
<p class="small text-muted">Default credentials: <code>karaf</code> / <code>karaf</code>. Accept the self-signed certificate warning &mdash; it is expected in development mode.</p>
<p>You can also verify Elasticsearch is running:</p>
<pre><code>curl http://localhost:9200/_cat/health?format=json</code></pre>
<div class="alert alert-info small" role="alert">
<i class="bi bi-info-circle me-1"></i> This Docker setup is for <strong>development and learning only</strong>. For production deployments, see the <a href="https://unomi.apache.org/manual/latest/index.html#_securing_a_production_environment">production security guide</a>.
</div>
</div>
</div>
</div>
</section>
<hr class="section-divider">
<!-- ============================================================ -->
<!-- 2. Manual Installation -->
<!-- ============================================================ -->
<section id="manual-install" class="section">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-10">
<span class="badge badge-primary mb-3">Step 2 (alternative)</span>
<h2>Manual Installation</h2>
<p class="text-muted">Prefer running Unomi directly on your machine? Follow these steps.</p>
<h3 class="h6 fw-semibold mt-4 mb-3">Prerequisites</h3>
<ul class="small">
<li><strong>Java 17</strong> or later &mdash; set <code>JAVA_HOME</code> accordingly. OpenJDK distributions work fine.</li>
<li><strong>Elasticsearch 9.x</strong> &mdash; <a href="https://www.elastic.co/downloads/elasticsearch" target="_blank" rel="noopener">Download Elasticsearch</a></li>
</ul>
<h3 class="h6 fw-semibold mt-4 mb-3">1. Configure &amp; start Elasticsearch</h3>
<p>After extracting Elasticsearch, edit <code>config/elasticsearch.yml</code>:</p>
<pre><code>cluster.name: contextElasticSearch</code></pre>
<p>Then start it:</p>
<pre><code>bin/elasticsearch</code></pre>
<h3 class="h6 fw-semibold mt-4 mb-3">2. Download &amp; start Apache Unomi</h3>
<p>Download the latest binary from the <a href="/download.html">download page</a>, extract it, then:</p>
<pre><code># Start Apache Karaf
./bin/karaf
# In the Karaf shell, start Unomi
karaf@root()&gt; unomi:start</code></pre>
<p>Wait until you see the service initialization messages:</p>
<pre><code>Initializing profile service endpoint...
Initializing cluster service endpoint...</code></pre>
<h3 class="h6 fw-semibold mt-4 mb-3">3. Verify</h3>
<p>Open <a href="https://localhost:9443/cxs/cluster">https://localhost:9443/cxs/cluster</a> (credentials: <code>karaf</code>/<code>karaf</code>).</p>
<p>Request your first context:</p>
<pre><code>http://localhost:8181/cxs/context.json?sessionId=1234</code></pre>
<div class="alert alert-info small" role="alert">
<i class="bi bi-terminal me-1"></i> <strong>Tip:</strong> Connect to the Karaf SSH console at any time with <code>ssh -p 8102 karaf@localhost</code> (password: <code>karaf</code>) for useful commands like <code>profile-list</code>, <code>event-tail</code>, and <code>rule-list</code>.
</div>
</div>
</div>
</div>
</section>
<hr class="section-divider">
<!-- ============================================================ -->
<!-- 3. First API Calls -->
<!-- ============================================================ -->
<section id="first-api-calls" class="section">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-10">
<span class="badge badge-primary mb-3">Step 3</span>
<h2>Your First API Calls</h2>
<p class="text-muted">With Unomi running, let&rsquo;s explore the REST API using <code>curl</code>.</p>
<!-- Read context -->
<h3 class="h6 fw-semibold mt-4 mb-3">Read the current context</h3>
<p>The <code>/cxs/context.json</code> endpoint is the primary public-facing endpoint. It returns the current visitor&rsquo;s profile, session, segments, and scores:</p>
<pre><code>curl -X POST http://localhost:8181/cxs/context.json?sessionId=1234 \
-H "Content-Type: application/json" \
--data-raw '{
"source": {
"itemId": "homepage",
"itemType": "page",
"scope": "example"
},
"requiredProfileProperties": ["*"],
"requiredSessionProperties": ["*"],
"requireSegments": true,
"requireScores": true
}'</code></pre>
<!-- Create a scope -->
<h3 class="h6 fw-semibold mt-4 mb-3">Create a scope</h3>
<p>Events in Unomi must be associated with a <strong>scope</strong>. Create one before sending events:</p>
<pre><code>curl -X POST http://localhost:8181/cxs/scopes \
-u karaf:karaf \
-H "Content-Type: application/json" \
--data-raw '{
"itemId": "my-website",
"itemType": "scope",
"metadata": {
"id": "my-website",
"name": "My Website Scope"
}
}'</code></pre>
<!-- Send a custom event -->
<h3 class="h6 fw-semibold mt-4 mb-3">Send a custom event</h3>
<p>Before sending a custom event, register a <strong>JSON Schema</strong> to validate it (required since Unomi 2.0):</p>
<pre><code>curl -X POST http://localhost:8181/cxs/jsonSchema \
-u karaf:karaf \
-H "Content-Type: application/json" \
--data-raw '{
"$id": "https://unomi.apache.org/schemas/json/events/contactInfoSubmitted/1-0-0",
"$schema": "https://json-schema.org/draft/2019-09/schema",
"self": {
"vendor": "org.apache.unomi",
"name": "contactInfoSubmitted",
"format": "jsonschema",
"target": "events",
"version": "1-0-0"
},
"title": "contactInfoSubmittedEvent",
"type": "object",
"allOf": [{ "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0" }],
"properties": {
"source": { "$ref": "https://unomi.apache.org/schemas/json/item/1-0-0" },
"target": { "$ref": "https://unomi.apache.org/schemas/json/item/1-0-0" },
"properties": {
"type": "object",
"properties": {
"firstName": { "type": ["null", "string"] },
"lastName": { "type": ["null", "string"] },
"email": { "type": ["null", "string"] }
}
}
},
"unevaluatedProperties": false
}'</code></pre>
<p>Now send the event via the <strong>event collector</strong> (the public endpoint for submitting events):</p>
<pre><code>curl -X POST http://localhost:8181/cxs/eventcollector \
-H "Content-Type: application/json" \
--data-raw '{
"sessionId": "1234",
"events": [{
"eventType": "contactInfoSubmitted",
"scope": "my-website",
"source": {
"itemType": "site",
"scope": "my-website",
"itemId": "mysite"
},
"target": {
"itemType": "form",
"scope": "my-website",
"itemId": "contactForm"
},
"properties": {
"firstName": "Jane",
"lastName": "Doe",
"email": "jane.doe@example.com"
}
}]
}'</code></pre>
<!-- Search events -->
<h3 class="h6 fw-semibold mt-4 mb-3">Search events</h3>
<p>Retrieve events matching a condition:</p>
<pre><code>curl -X POST http://localhost:8181/cxs/events/search \
-u karaf:karaf \
-H "Content-Type: application/json" \
--data-raw '{
"offset": 0,
"limit": 20,
"sortby": "timeStamp:desc",
"condition": {
"type": "eventPropertyCondition",
"parameterValues": {
"propertyName": "properties.firstName",
"comparisonOperator": "equals",
"propertyValue": "Jane"
}
}
}'</code></pre>
<!-- View a profile -->
<h3 class="h6 fw-semibold mt-4 mb-3">View a profile</h3>
<p>Use the profile UUID (from the <code>context-profile-id</code> cookie or event response) to look up profile details:</p>
<pre><code>curl http://localhost:8181/cxs/profiles/PROFILE_UUID \
-u karaf:karaf</code></pre>
<div class="alert alert-info small" role="alert">
<i class="bi bi-terminal me-1"></i> <strong>SSH Console shortcuts:</strong> <code>event-tail</code> watches events in real time, <code>event-list</code> shows recent events, <code>profile-list</code> lists recently modified profiles, <code>profile-view PROFILE_UUID</code> shows profile details.
</div>
</div>
</div>
</div>
</section>
<hr class="section-divider">
<!-- ============================================================ -->
<!-- 4. Web Tracking Tutorial -->
<!-- ============================================================ -->
<section id="web-tracking" class="section">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-10">
<span class="badge badge-primary mb-3">Step 4</span>
<h2>Web Tracking Tutorial</h2>
<p class="text-muted">Integrate the built-in Unomi web tracker into any web page to automatically collect page views and enable personalization.</p>
<!-- Install tracker -->
<h3 class="h6 fw-semibold mt-4 mb-3">Add the tracker to your page</h3>
<p>Include the tracker script and initialize it:</p>
<pre><code>&lt;!-- Load the Unomi web tracker --&gt;
&lt;script src="/tracker/unomi-web-tracker.min.js"&gt;&lt;/script&gt;
&lt;script&gt;
(function () {
var conf = {
"scope": "my-website",
"site": {
"siteInfo": { "siteID": "my-website" }
},
"page": {
"pageInfo": {
"pageID": "home",
"pageName": document.title,
"pagePath": document.location.pathname,
"destinationURL": document.location.origin + document.location.pathname,
"language": "en",
"categories": [],
"tags": []
},
"attributes": {},
"consentTypes": []
},
"events:": [],
"wemInitConfig": {
"contextServerUrl": document.location.origin,
"timeoutInMilliseconds": "1500",
"contextServerCookieName": "context-profile-id",
"activateWem": true,
"trackerSessionIdCookieName": "my-website-session-id",
"trackerProfileIdCookieName": "my-website-profile-id"
}
};
// Generate a new session if needed
if (unomiWebTracker.getCookie(conf.wemInitConfig.trackerSessionIdCookieName) == null) {
unomiWebTracker.setCookie(conf.wemInitConfig.trackerSessionIdCookieName, unomiWebTracker.generateGuid(), 1);
}
unomiWebTracker.initTracker(conf);
unomiWebTracker._registerCallback(function() {
console.log("Unomi context loaded:", unomiWebTracker.getLoadedContext());
}, 'My callback');
unomiWebTracker.startTracker();
})();
&lt;/script&gt;</code></pre>
<div class="alert alert-warning small" role="alert">
<i class="bi bi-exclamation-triangle me-1"></i> Remember to <strong>create the scope</strong> first (see <a href="#first-api-calls">Step 3</a>) or events will be rejected.
</div>
<!-- NPM -->
<h3 class="h6 fw-semibold mt-4 mb-3">Using the tracker as an NPM package</h3>
<p>For JavaScript/TypeScript projects, install the tracker from npm:</p>
<pre><code>npm install apache-unomi-tracker
# or
yarn add apache-unomi-tracker</code></pre>
<p>Then import and use it:</p>
<pre><code>import { useTracker } from "apache-unomi-tracker";
const unomiWebTracker = useTracker();
// ... configure and start as shown above</code></pre>
</div>
</div>
</div>
</section>
<hr class="section-divider">
<!-- ============================================================ -->
<!-- 5. Rules & Personalization -->
<!-- ============================================================ -->
<section id="rules-personalization" class="section">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-10">
<span class="badge badge-primary mb-3">Step 5</span>
<h2>Rules &amp; Personalization</h2>
<p class="text-muted">Rules let Unomi react to events in real time &mdash; updating profiles, triggering actions, or integrating with external systems.</p>
<!-- Create a rule -->
<h3 class="h6 fw-semibold mt-4 mb-3">Create a rule that counts page views</h3>
<p>This rule increments a <code>pageViewCount</code> property on the visitor&rsquo;s profile every time a <code>view</code> event is received:</p>
<pre><code>curl -X POST http://localhost:8181/cxs/rules \
-u karaf:karaf \
-H "Content-Type: application/json" \
--data-raw '{
"metadata": {
"id": "viewEventRule",
"name": "View event rule",
"description": "Increments pageViewCount on each page view"
},
"condition": {
"type": "eventTypeCondition",
"parameterValues": {
"eventTypeId": "view"
}
},
"actions": [{
"type": "incrementPropertyAction",
"parameterValues": {
"propertyName": "pageViewCount"
}
}]
}'</code></pre>
<p class="small text-muted">After creating this rule, reload your tracked page a few times, then check the profile with <code>profile-view PROFILE_UUID</code> in the SSH console to see the counter increase.</p>
<!-- Copy event properties to profile -->
<h3 class="h6 fw-semibold mt-4 mb-3">Map event data to a profile</h3>
<p>This rule listens for the <code>contactInfoSubmitted</code> event (from Step 3) and copies its properties to the profile:</p>
<pre><code>curl -X POST http://localhost:8181/cxs/rules \
-u karaf:karaf \
-H "Content-Type: application/json" \
--data-raw '{
"metadata": {
"id": "setContactInfo",
"name": "Copy contact info to profile",
"description": "Maps firstName, lastName, email from event to profile"
},
"condition": {
"type": "eventTypeCondition",
"parameterValues": {
"eventTypeId": "contactInfoSubmitted"
}
},
"actions": [
{
"type": "setPropertyAction",
"parameterValues": {
"setPropertyName": "properties(firstName)",
"setPropertyValue": "eventProperty::properties(firstName)",
"setPropertyStrategy": "alwaysSet"
}
},
{
"type": "setPropertyAction",
"parameterValues": {
"setPropertyName": "properties(lastName)",
"setPropertyValue": "eventProperty::properties(lastName)",
"setPropertyStrategy": "alwaysSet"
}
},
{
"type": "setPropertyAction",
"parameterValues": {
"setPropertyName": "properties(email)",
"setPropertyValue": "eventProperty::properties(email)",
"setPropertyStrategy": "alwaysSet"
}
}
]
}'</code></pre>
<!-- Personalization -->
<h3 class="h6 fw-semibold mt-4 mb-3">Personalize content based on a profile property</h3>
<p>Using the web tracker, you can register personalization rules that show different content based on profile data. For example, show a special message after 5 page views:</p>
<pre><code>&lt;div id="variant1" style="display:none"&gt;
Welcome back! You've visited this page over 5 times.
&lt;/div&gt;
&lt;div id="variant2" style="display:none"&gt;
Welcome! Keep exploring to unlock personalized content.
&lt;/div&gt;
&lt;script&gt;
var variants = {
"var1": { content: "variant1" },
"var2": { content: "variant2" }
};
unomiWebTracker.registerPersonalizationObject({
"id": "pageViewPersonalization",
"strategy": "matching-first",
"strategyOptions": { "fallback": "var2" },
"contents": [{
"id": "var1",
"filters": [{
"condition": {
"type": "profilePropertyCondition",
"parameterValues": {
"propertyName": "properties.pageViewCount",
"comparisonOperator": "greaterThan",
"propertyValueInteger": 5
}
}
}]
}, {
"id": "var2"
}]
}, variants, false, function (successfulFilters, selectedFilter) {
if (selectedFilter) {
document.getElementById(selectedFilter.content).style.display = '';
}
});
&lt;/script&gt;</code></pre>
<p class="small text-muted">The personalization engine evaluates conditions server-side against the visitor&rsquo;s profile and returns which variant to display, keeping your business logic private.</p>
</div>
</div>
</div>
</section>
<hr class="section-divider">
<!-- ============================================================ -->
<!-- 6. Next Steps -->
<!-- ============================================================ -->
<section id="next-steps" class="section">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-10">
<h2>Next Steps</h2>
<p class="text-muted mb-4">You now have a working Unomi environment with event tracking, profile management, rules, and personalization. Here&rsquo;s where to go next.</p>
<div class="row g-4">
<div class="col-md-6">
<div class="feature-card h-100">
<div class="feature-icon"><i class="bi bi-book"></i></div>
<h3 class="h6 fw-semibold mb-2">Full Documentation</h3>
<p class="small text-muted mb-2">Dive deeper into segmentation, scoring, conditions, and actions in the comprehensive manual.</p>
<a href="https://unomi.apache.org/manual/latest/index.html" class="small fw-semibold">Read the manual <i class="bi bi-arrow-right"></i></a>
</div>
</div>
<div class="col-md-6">
<div class="feature-card h-100">
<div class="feature-icon"><i class="bi bi-braces"></i></div>
<h3 class="h6 fw-semibold mb-2">REST API Reference</h3>
<p class="small text-muted mb-2">Explore every endpoint for profiles, events, segments, rules, and more.</p>
<a href="https://unomi.apache.org/rest-api-doc/index.html" class="small fw-semibold">Browse the API <i class="bi bi-arrow-right"></i></a>
</div>
</div>
<div class="col-md-6">
<div class="feature-card h-100">
<div class="feature-icon"><i class="bi bi-puzzle"></i></div>
<h3 class="h6 fw-semibold mb-2">Integrations</h3>
<p class="small text-muted mb-2">See how organizations use Unomi with CMS, CRM, e-commerce, and other platforms.</p>
<a href="/integrations.html" class="small fw-semibold">View integrations <i class="bi bi-arrow-right"></i></a>
</div>
</div>
<div class="col-md-6">
<div class="feature-card h-100">
<div class="feature-icon"><i class="bi bi-chat-dots"></i></div>
<h3 class="h6 fw-semibold mb-2">Get Help</h3>
<p class="small text-muted mb-2">Stuck? The community is friendly and responsive on the mailing list and Slack.</p>
<a href="/community/index.html" class="small fw-semibold">Join the community <i class="bi bi-arrow-right"></i></a>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Debugging Tip -->
<section class="section section-alt">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="feature-card tip-box">
<h3 class="h6 fw-semibold mb-3"><i class="bi bi-bug me-2"></i>Debugging Tip</h3>
<p class="small text-muted mb-2">Enable debug logging via the Karaf SSH console to see detailed event validation and rule execution logs:</p>
<pre><code># Connect to Karaf SSH console
ssh -p 8102 karaf@localhost
# Enable schema validation debug logs
karaf@root()&gt; log:set DEBUG org.apache.unomi.schema.impl.SchemaServiceImpl
# Watch logs in real-time
karaf@root()&gt; log:tail</code></pre>
<p class="small text-muted mb-0">Useful commands: <code>event-tail</code>, <code>event-list</code>, <code>rule-list</code>, <code>rule-tail</code>, <code>profile-list</code>, <code>profile-view &lt;ID&gt;</code></p>
</div>
</div>
</div>
</div>
</section>