Session Lifecycle
Understanding how sessions progress through their lifecycle is essential for effective session management.
Lifecycle States
Sessions can be in one of three states:
1. Active
The default state when a session is created. An active session:
- Can receive new queries
- Accumulates conversation turns
- Can have metadata updated
Characteristics:
status: "active"completedAt: null
2. Completed
A terminal state indicating the session was explicitly finished by the client:
Characteristics:
status: "completed"completedAtis set to completion timestamp- Cannot be reopened or receive new queries
- Metadata cannot be updated
- Indicates successful conversation conclusion
Use Cases:
- User explicitly ends chat session
- Conversation reaches natural conclusion
- User navigates away from page and signals end
- User objective is met or conversation workflow completes
3. Expired
A terminal state indicating the session was marked as expired by the client application:
Characteristics:
status: "expired"completedAtis set to the timestamp when the client marked it expired- Cannot be reopened or receive new queries
- Metadata cannot be updated
- Indicates the client application determined the session should be expired based on business rules
Use Cases:
- Client implements time-based expiration (e.g., 24 hours since creation)
- Client implements inactivity-based expiration (e.g., 30 minutes idle)
- User abandons conversation and client cleanup policy triggers
- Client business logic determines the session should end
State Transition Diagram
┌─────────┐
│ Created │
└────┬────┘
│
v
┌─────────┐
│ active │ ───────────────────────────────┐
└────┬────┘ │
│ │
│ POST /complete │ POST /complete
│ (status=completed) │ (status=expired)
│ │
v v
┌───────────┐ ┌──────────┐
│ completed │ │ expired │
└───────────┘ └──────────┘
│ │
└────────────┬────────────────────────┘
│
(terminal)Key Transition Rules:
active → completed: Client calls completion endpoint withstatus=completedactive → expired: Client calls completion endpoint withstatus=expired- Both terminal states are permanent (no transitions out)
Session Creation Patterns
There are two ways sessions can be created:
1. Explicit Creation (Recommended)
Pre-create sessions before the first query using the /v2/sessions endpoint:
// Create a new session
const response = await fetch("https://nexus-api.uat.knowbl.com/api/v2/sessions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_ACCESS_TOKEN",
},
body: JSON.stringify({
experienceId: "your-experience-id",
userId: "user@example.com",
metadata: {
source: "mobile-app",
version: "2.0.1",
},
}),
});
const session = await response.json();
console.log("Session created:", session.id);
Benefits:
- Full control over session timing
- Can set metadata before first query
- Enables session tracking for analytics
- Supports pre-chat form data collection
Use Cases:
- Chat widget initialization
- Multi-step workflows
- Authenticated user sessions
- Session pre-warming for analytics
2. Implicit Creation
Sessions are automatically created during query processing if no sessionId is provided:
// Query without sessionId - creates implicit session
// Note: /query endpoint is public and does not require authentication
const response = await fetch("https://nexus-api.uat.knowbl.com/api/v2/query", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "What are your business hours?",
experienceId: "your-experience-id",
}),
});
const result = await response.json();
// result.sessionId contains the auto-created session ID
Characteristics:
- Session created automatically by query API
- Uses request metadata for initial context
sessionIdreturned in query response- Subsequent queries can reference this sessionId
Use Cases:
- Simple one-off queries
- Anonymous interactions
- Embedded forms or quick questions
- When session tracking is not required
Conversation Turns
Sessions store conversation history as a sequence of turns:
Turn Structure
Each turn represents one query-response pair:
{
"turnNumber": 1,
"query": {
"text": "How do I reset my password?",
"timestamp": "2025-10-28T12:00:00.000Z"
},
"response": {
"answer": "To reset your password, click the 'Forgot Password' link...",
"timestamp": "2025-10-28T12:00:01.500Z"
}
}Turn Sequencing
- Turns are numbered sequentially starting at 1
- Each turn is immutable once created
- Turn order reflects conversation chronology
- All turns within a session are included in conversation context while the session is active
Session Completion
When to Complete Sessions
Mark sessions as completed when:
- User explicitly ends the conversation
- Conversation workflow reaches natural conclusion
- User session ends (logout, navigation)
- Business logic determines the conversation is complete
How to Complete Sessions
Use the complete endpoint:
// Mark session as completed
const sessionId = "session-uuid";
const params = new URLSearchParams({
experienceId: "your-experience-id",
});
const response = await fetch(
`https://nexus-api.uat.knowbl.com/api/v2/sessions/${sessionId}/complete?${params}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_ACCESS_TOKEN",
},
body: JSON.stringify({
status: "completed",
}),
},
);
const session = await response.json();
console.log("Session completed at:", session.completedAt);
Client-Controlled Session Expiration
The Nexus platform does not automatically expire sessions. Sessions remain in the active state indefinitely until the client application explicitly marks them as completed or expired. This design gives client applications full control over session lifecycle management based on their specific business requirements.
Expiration API
To mark a session as expired, use the session completion endpoint:
Endpoint: POST /v2/sessions/:sessionId/complete
Request:
POST /v2/sessions/{sessionId}/complete?experienceId={experienceId}
Authorization: Bearer {api-key}
Content-Type: application/json
{
"status": "expired"
}Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"experienceId": "660e8400-e29b-41d4-a716-446655440000",
"status": "expired",
"completedAt": "2025-10-29T15:30:45.123Z"
}Authentication: Requires bearer token with sessions:complete scope
Widget Integration
After marking a session as expired via the API, clear the chat widget UI:
// Mark session as expired via API
const response = await fetch(
`https://nexus-api.uat.knowbl.com/api/v2/sessions/${sessionId}/complete?experienceId=${experienceId}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ status: "expired" }),
},
);
// Clear the widget UI
widget.clearChat();
The widget emits events that you can use for tracking:
widget.on("sessionClear", (data) => {
console.log("Session cleared:", data.sessionId);
console.log("Messages removed:", data.messageCount);
});
widget.on("sessionEnd", (data) => {
console.log("Session ended after", data.duration, "ms");
});Implementation Patterns
Client applications can implement various expiration strategies based on their requirements:
Pattern 1: Time-Based Expiration
Expire sessions after a fixed duration since creation:
// Configuration
const SESSION_TIMEOUT_MS = 24 * 60 * 60 * 1000; // 24 hours
// Track session creation time
const sessionCreatedAt = new Date(session.createdAt);
// Check if session should expire
async function checkSessionExpiration(sessionId, createdAt) {
const now = Date.now();
const age = now - new Date(createdAt).getTime();
if (age > SESSION_TIMEOUT_MS) {
// Expire the session
await fetch(
`https://nexus-api.uat.knowbl.com/api/v2/sessions/${sessionId}/complete?experienceId=${experienceId}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ status: "expired" }),
},
);
// Clear the widget
widget.clearChat();
}
}
Pattern 2: Inactivity-Based Expiration
Expire sessions after a period of user inactivity:
// Configuration
const INACTIVITY_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
// Track last activity time
let lastActivityTime = Date.now();
// Update activity time on user interactions
widget.on("messageComplete", () => {
lastActivityTime = Date.now();
});
widget.on("querySent", () => {
lastActivityTime = Date.now();
});
// Check for inactivity periodically
setInterval(async () => {
const now = Date.now();
const inactiveTime = now - lastActivityTime;
const currentSessionId = widget.store.currentSessionId.value;
if (inactiveTime > INACTIVITY_TIMEOUT_MS && currentSessionId) {
// Expire the session
await fetch(
`https://nexus-api.uat.knowbl.com/api/v2/sessions/${currentSessionId}/complete?experienceId=${experienceId}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ status: "expired" }),
},
);
// Clear the widget
widget.clearChat();
}
}, 60000); // Check every minute
Pattern 3: Event-Driven Expiration
Expire sessions based on application events:
// Example 1: Expire on user logout
async function onUserLogout() {
const sessionId = widget.store.currentSessionId.value;
if (sessionId) {
await fetch(
`https://nexus-api.uat.knowbl.com/api/v2/sessions/${sessionId}/complete?experienceId=${experienceId}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ status: "expired" }),
},
);
widget.clearChat();
}
}
// Example 2: Expire on page unload (if not persisting across page loads)
window.addEventListener("beforeunload", () => {
const sessionId = widget.store.currentSessionId.value;
if (sessionId && !shouldPersistSession) {
// Use fetch with keepalive for reliable delivery with auth headers during page unload
fetch(
`https://nexus-api.uat.knowbl.com/api/v2/sessions/${sessionId}/complete?experienceId=${experienceId}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ status: "expired" }),
keepalive: true, // Ensures request completes during page unload
},
).catch(() => {
// Silently fail - page is unloading anyway
});
}
});
Session Hijack Protection
The platform prevents session hijacking through userId validation:
How It Works
- Session Creation:
userIdis stored with session - Session Reuse: Subsequent queries must include same
userId - Validation: System compares provided userId with session’s userId
- Enforcement: Mismatched userId results in error
userId Normalization
- Email addresses: Case-insensitive (normalized to lowercase)
- UUID format: Case-insensitive
- Custom strings: Case-insensitive
Examples:
// These are considered the same userId:
"user@example.com";
"USER@EXAMPLE.COM";
"User@Example.Com";Error Handling
Attempting to use a session with wrong userId:
{
"statusCode": 403,
"message": "Session hijack detected: userId mismatch"
}Best Practices
Session Creation
- Pre-create sessions for widget interactions (enables tracking and metadata)
- Use meaningful userId values such as email addresses or customer IDs
- Include initial metadata for tracking source, context, and analytics
- Validate experienceId before session creation to catch configuration errors early
Session Management
- Complete sessions explicitly when user closes widget or logs out
- Use
completedstatus for normal user-initiated termination - Use
expiredstatus for client-determined timeout scenarios or cleanup jobs - Don’t complete sessions that may resume later (e.g., page refresh, browser reopen)
Session Expiration
When implementing client-controlled expiration:
- Choose the right pattern: Time-based for absolute limits, inactivity-based for engagement, event-driven for state changes
- Coordinate API and UI: Always call the completion API before clearing the widget
- Use appropriate timing: Check less frequently for time-based (every few minutes), more frequently for inactivity (every minute)
- Handle edge cases: Account for page refreshes, browser tabs, and network failures
- Warn users proactively: Give advance notice before expiring sessions, especially for inactivity
- Track for analytics: Use widget events to monitor session lifecycle for optimization
Security & Session Hijacking
- Always pass the same userId when reusing sessions across requests
- Store userId alongside sessionId in client state to ensure consistency
- Use consistent userId format (e.g., always lowercase for email addresses)
- Handle 403 hijack errors by creating a new session rather than retrying
Multi-turn Conversations
- Always pass sessionId for follow-up queries to maintain conversation context
- Monitor turn count for very long conversations (consider completion for performance)
- Preserve important context in metadata rather than relying only on conversation history
Error Handling
- Handle 404 errors by creating a new session (previous session may have expired)
- Implement retry logic for transient failures with exponential backoff
- Cache session data client-side to reduce API calls and improve resilience
Performance
- Batch metadata updates when possible to reduce API calls
- Use pagination and filtering efficiently when listing sessions (don’t fetch all sessions)
- Monitor rate limits and implement backoff strategies to prevent throttling
Next Steps
- Session Metadata - Learn how to use metadata for tracking