Events
Events are the behavioral signals that Odyssey collects to understand how users navigate through journeys. The ingest API receives batches of events from the embed script or your own tracking implementation.
Event models
Odyssey tracks several types of events, all sharing a common base structure with event-specific properties.
Base event properties
All events include these properties:
- Name
_tag- Type
- string
- Description
The event type (e.g.,
session_start,step_view,field_focus).
- Name
schemaVersion- Type
- integer
- Description
Event schema version (currently 1).
- Name
id- Type
- string
- Description
Unique identifier for this event.
- Name
ts- Type
- timestamp
- Description
ISO 8601 timestamp when the event occurred.
- Name
orgKey- Type
- string
- Description
The organization slug.
- Name
journeyKey- Type
- string
- Description
The journey slug.
- Name
sessionId- Type
- string
- Description
Unique session identifier (UUID v4).
- Name
clientId- Type
- string
- Description
Persistent anonymous client identifier.
- Name
url- Type
- string
- Description
The page URL where the event occurred.
- Name
referrer- Type
- string
- Description
The referrer URL (optional).
- Name
embedVersion- Type
- string
- Description
Version of the embed script (optional).
- Name
source- Type
- string
- Description
Event source (e.g., "simpligov", "custom") (optional).
Event types
Session events
- Name
session_start- Description
Fired when a user begins a new journey session.
- Name
session_end- Description
Fired when a session completes or times out.
Navigation events
- Name
page_view- Description
Fired on page load. Includes optional
titleproperty.
- Name
step_view- Description
Fired when entering a journey step. Includes
stepKeyproperty.
Interaction events
- Name
field_focus- Description
Fired when focusing on a field. Includes optional
fieldKeyproperty.
- Name
field_blur- Description
Fired when leaving a field. Includes optional
fieldKeyproperty.
- Name
field_change- Description
Fired when a field value changes. Includes optional
fieldKeyproperty.
Validation and submission events
- Name
validation_error- Description
Fired on form validation failure. Includes optional
fieldKeyanderrorTypeproperties.
- Name
submit_attempt- Description
Fired when user attempts form submission.
- Name
submit_success- Description
Fired on successful form submission.
Analytics events
- Name
dropoff_signal- Description
Fired when user behavior indicates likely abandonment.
Send events
This endpoint receives batches of events from the embed script or your custom tracking implementation.
This endpoint does not require API key authentication, but it validates that the organization and journey exist and respects CORS restrictions.
Request body
- Name
schemaVersion- Type
- integer
- Description
Batch schema version (currently 1).
- Name
context- Type
- object
- Description
Context object with
orgKey,journeyKey, optionallanguage, and optionaldeviceClass.
- Name
events- Type
- array
- Description
Array of event objects to ingest.
Request
curl -X POST https://odysseylabs.ai/api/ingest \
-H "Content-Type: application/json" \
-H "Origin: https://grants.example.com" \
-d '{
"schemaVersion": 1,
"context": {
"orgKey": "example-org",
"journeyKey": "small-business-grant",
"language": "en-US",
"deviceClass": "desktop"
},
"events": [
{
"_tag": "session_start",
"schemaVersion": 1,
"id": "evt_abc123",
"ts": "2024-01-15T10:30:00.000Z",
"orgKey": "example-org",
"journeyKey": "small-business-grant",
"sessionId": "sess_xyz789",
"clientId": "client_def456",
"url": "https://grants.example.com/apply",
"referrer": "https://google.com",
"embedVersion": "1.0.0"
},
{
"_tag": "step_view",
"schemaVersion": 1,
"id": "evt_abc124",
"ts": "2024-01-15T10:30:01.000Z",
"orgKey": "example-org",
"journeyKey": "small-business-grant",
"sessionId": "sess_xyz789",
"clientId": "client_def456",
"url": "https://grants.example.com/apply/eligibility",
"stepKey": "eligibility",
"embedVersion": "1.0.0"
},
{
"_tag": "field_focus",
"schemaVersion": 1,
"id": "evt_abc125",
"ts": "2024-01-15T10:30:05.000Z",
"orgKey": "example-org",
"journeyKey": "small-business-grant",
"sessionId": "sess_xyz789",
"clientId": "client_def456",
"url": "https://grants.example.com/apply/eligibility",
"fieldKey": "business-name",
"embedVersion": "1.0.0"
}
]
}'
Response
204 No Content
Using the embed script
The easiest way to send events is using the Odyssey embed script, which automatically tracks user behavior:
Embed script installation
<script
src="https://odysseylabs.ai/embed/v1.js"
data-org="example-org"
data-journey="small-business-grant"
data-endpoint="https://odysseylabs.ai/api/ingest"
data-env="prod"
async
></script>
The embed script:
- Creates anonymous session and client IDs
- Automatically tracks navigation and interaction events
- Buffers events and sends them in batches
- Uses
navigator.sendBeaconfor reliable delivery - Never collects PII or field values
See the Events & Analytics guide for complete details.
Manual event tracking
For custom tracking scenarios, you can call the optional API exposed by the embed script:
Manual tracking
// Track a custom event
window.JourneyEmbed.track('step_view', {
stepKey: 'custom-step'
})
// Force flush buffered events
window.JourneyEmbed.flush()
Or send events directly to the ingest API from your backend:
Server-side tracking
await fetch('https://odysseylabs.ai/api/ingest', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
schemaVersion: 1,
context: {
orgKey: 'example-org',
journeyKey: 'small-business-grant',
deviceClass: 'desktop'
},
events: [
{
_tag: 'submit_success',
schemaVersion: 1,
id: generateId(),
ts: new Date().toISOString(),
orgKey: 'example-org',
journeyKey: 'small-business-grant',
sessionId: sessionId,
clientId: clientId,
url: currentUrl
}
]
})
})
Rate limiting
The ingest endpoint applies rate limiting to prevent abuse:
Per-organization rate limits
- Default: 1,000 requests per hour per organization/journey pair
- Custom limits: Configurable via organization settings
- Unknown origins: 20 requests per minute (more restrictive)
Rate limit headers
Rate limit information is not currently included in response headers, but you'll receive a 429 Too Many Requests status when the limit is exceeded.
To request a rate limit increase, contact support from your organization dashboard.
CORS and security
The ingest endpoint validates requests based on origin:
- If a journey has
allowedOriginsconfigured, only requests from those origins are accepted - If
allowedOriginsis empty, all origins are allowed (not recommended for production) - Unknown origins receive more aggressive rate limiting
Set allowed origins when creating or updating a journey:
Set allowed origins
curl -X PUT https://odysseylabs.ai/api/journeys/small-business-grant \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"allowedOrigins": [
"https://grants.example.com",
"https://staging.grants.example.com"
]
}'
Event validation
The ingest API validates events against the schema:
- Required fields must be present
- Field types must match (strings, integers, timestamps)
- Timestamps must be valid ISO 8601 format
- Event tags must be recognized types
- Session and client IDs must be valid UUIDs or strings
Invalid events return a 400 Bad Request response with details:
Validation error
{
"error": "Invalid request: ts must be a valid ISO 8601 timestamp"
}
Common errors
Organization not found (404)
{
"error": "Organization not found"
}
Journey not found (404)
{
"error": "Journey not found"
}
Rate limit exceeded (429)
{
"error": "Rate limit exceeded for organization"
}
Invalid event data (400)
{
"error": "Invalid request: events array is required"
}
Privacy considerations
Odyssey is designed with privacy as a core principle:
What is collected:
- Event types and timestamps
- Step and field identifiers
- Anonymous session/client IDs
- Page URLs and referrers
- Device class and language
What is NOT collected:
- Field values or user input
- Personally identifiable information
- Uploaded files
- Form content
- Names, emails, addresses, etc.
The clientId stored in localStorage allows repeat user tracking without revealing identity.