Session Metadata
Session metadata is a flexible, client-controlled JSON object that enables custom tracking and analytics for your conversations.
What is Metadata?
Metadata is a flexible JSON object that clients can use to store custom information about a session.
Characteristics
Client Ownership:
- Clients decide what data to store
- No schema validation or restrictions
- Can store any JSON-serializable values
Type Support:
- Strings:
"mobile-app","user@example.com" - Numbers:
42,3.14,1234567890 - Booleans:
true,false - Arrays:
["tag1", "tag2"],[1, 2, 3] - Nested objects:
{"key": "value", "nested": {"deep": true}} - Null: Used to delete fields in PATCH operations
Shallow Merge Updates:
- PATCH operations merge at top level only
- Existing keys are overwritten
- New keys are added
- Unmentioned keys are preserved
- Null values delete fields: Set field to
nullto remove it
Persistence:
- Stored in session database record
- Returned in all session responses
- Preserved through session lifecycle
- Available for filtering and analysis
Common Use Cases
1. Source Tracking
Track where the session originated:
{
metadata: {
source: "website",
page_url: "https://example.com/support",
referrer: "https://google.com/search?q=help",
utm_campaign: "spring_2025",
utm_source: "email",
},
}2. User Information
Store non-sensitive user context:
{
metadata: {
user_segment: "premium",
account_age_days: 45,
preferred_language: "en-US",
timezone: "America/New_York",
},
}3. Session Analytics
Track interaction metrics:
{
metadata: {
interaction_count: 5,
feedback_submitted: true,
survey_shown: false,
session_rating: 4,
helpful_responses: 3,
},
}4. Widget State
Maintain UI state for resumption:
{
metadata: {
window_position: "bottom-right",
theme: "light",
collapsed: false,
last_input: "How do I...",
draft_message: "",
},
}5. Business Logic
Custom application data:
{
metadata: {
order_id: "ORD-12345",
product_category: "electronics",
support_tier: "priority",
assigned_agent_id: "agent-789",
escalation_required: false,
},
}6. Complex Data Structures
Store rich, structured data:
{
metadata: {
// String values
userId: "user@example.com",
source: "mobile-app",
// Number values
sessionDuration: 342.5,
pageViews: 15,
timestamp: 1234567890,
// Boolean values
authenticated: true,
premiumUser: false,
// Array values
tags: ["support", "billing", "urgent"],
visitedPages: ["/home", "/products", "/checkout"],
scores: [4.5, 4.8, 5.0],
// Nested object values
deviceInfo: {
type: "mobile",
os: "iOS",
version: "17.2",
screenResolution: {
width: 1920,
height: 1080,
},
},
preferences: {
theme: "dark",
language: "en-US",
notifications: {
email: true,
push: false,
sms: false,
},
},
analytics: {
campaign: "summer-sale",
medium: "email",
source: "newsletter",
conversionValue: 149.99,
},
},
}7. Tag-Based Content Filtering
Control which knowledge base entries are accessible during queries using tags. The tags field supports two modes:
Simple Mode (Array): Filter using a list of tags with AND/OR logic
{
"tags": [
"premium",
"v2"
],
"tagFilterMode": "OR"
}All queries in this session will only retrieve KB entries that have tag “premium” OR “v2”. Entries with no tags are always included (global content).
Expression Mode (String): Advanced filtering with complex logic
{
"tags": "(tier1,tier2,tier3),(capability-a+capability-b+capability-c)"
}This expression means: Entry must have (tier1 OR tier2 OR tier3) OR (capability-a AND capability-b AND capability-c).
Expression Syntax:
,(comma) - OR operator: Match entries with ANY of the tags+(plus) - AND operator: Match entries with ALL of the tags@(at sign) - SUBSET operator: Match entries where ALL entry tags are in the specified set()(parentheses) - Grouping: Control evaluation order
Precedence (highest to lowest): @ (subset) > + (and) > , (or)
Reserved Characters:
,- OR operator+- AND operator@- SUBSET operator(- Left parenthesis (grouping))- Right parenthesis (grouping)- Whitespace (ignored, used for readability)
These characters have special meaning in tag expressions and cannot be used in tag names. Tag names are any sequence of characters that don’t include these reserved characters.
More Examples:
{
"tags": "admin+(read,write)"
}Translates to: Entry must have tag “admin” AND (tag “read” OR tag “write”)
Matches: ["admin", "read"], ["admin", "write"], ["admin", "read", "write"]
Does NOT match: ["admin"] (missing read/write), ["read", "write"] (missing admin)
{
"tags": "premium,(basic+verified)"
}Translates to: Entry must have tag “premium” OR (tag “basic” AND tag “verified”)
Matches: ["premium"], ["basic", "verified"]
Does NOT match: ["basic"] (missing verified), ["verified"] (missing basic)
{
"tags": "(region-us,region-eu)+(v2,v3)"
}Translates to: Entry must have (tag “region-us” OR tag “region-eu”) AND (tag “v2” OR tag “v3”)
Matches: ["region-us", "v2"], ["region-eu", "v3"], ["region-us", "region-eu", "v2", "v3"]
Does NOT match: ["region-us"] (missing version), ["v2"] (missing region)
{
"tags": "(entitle-a@entitle-b@entitle-c),no-entitlement-required"
}Translates to: ALL entry tags must be in the set {entitle-a, entitle-b, entitle-c} OR entry has tag “no-entitlement-required”
Use case: User has entitlements {entitle-a, entitle-b, entitle-c}. Content tagged with required entitlements will only match if user has all those requirements.
Matches:
["entitle-a"]- user has this entitlement["entitle-a", "entitle-b"]- user has both["no-entitlement-required"]- public content[]- untagged content (always accessible)
Does NOT match:
["entitle-x"]- user doesn’t have this entitlement["entitle-a", "entitle-x"]- user doesn’t have entitle-x
Key Points:
- Untagged KB entries (no tags or empty tags array) are always included regardless of filtering
- Use array mode for simple OR/AND filtering
- Use string mode for complex multi-level expressions
- Use
@operator for entitlement/capability checking where content specifies requirements - Perfect for role-based access control, tiered content, and multi-tenant scenarios
Deleting Metadata Fields
Use null values to delete fields:
// Delete metadata fields by setting them to null
const sessionId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
const experienceId = "550e8400-e29b-41d4-a716-446655440000";
await fetch(
`https://nexus-api.uat.knowbl.com/api/v2/sessions/${sessionId}/metadata?experienceId=${experienceId}`,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_API_KEY",
},
body: JSON.stringify({
// Delete fields by setting to null
temporaryFlag: null,
oldPageUrl: null,
// Update or add other fields normally
currentPageUrl: "https://example.com/new-page",
lastUpdated: new Date().toISOString(),
}),
},
);
Example:
Initial metadata:
{
"temporaryFlag": true,
"sessionStartTime": 1234567890,
"pageUrl": "https://example.com/page1"
}PATCH request:
{
"temporaryFlag": null,
"pageUrl": "https://example.com/page2"
}Result:
{
"sessionStartTime": 1234567890,
"pageUrl": "https://example.com/page2"
}Updating Metadata
Use the PATCH endpoint for updates:
// Update session metadata (shallow merge)
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}/metadata?${params}`,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_ACCESS_TOKEN",
},
body: JSON.stringify({
page_url: "https://example.com/support",
interaction_count: 5,
}),
},
);
const updated = await response.json();
console.log("Metadata updated:", updated.metadata);
Shallow Merge Behavior
Initial Metadata:
{
"source": "website",
"page_url": "https://example.com/home",
"user_segment": "free"
}PATCH Request:
{
"page_url": "https://example.com/support",
"interaction_count": 1
}Resulting Metadata:
{
"source": "website",
"page_url": "https://example.com/support",
"user_segment": "free",
"interaction_count": 1
}Key Points:
sourceanduser_segmentare preserved (not in PATCH body)page_urlis overwritten (in PATCH body)interaction_countis added (new key)
Best Practices
Do:
- Use metadata for client-specific tracking needs
- Store analytics and attribution data
- Include source/context information at creation
- Update metadata incrementally as needed
- Use consistent naming conventions (snake_case recommended)
Don’t:
- Store sensitive data (passwords, tokens, PII)
- Use deeply nested objects if you need to update nested fields (only top-level merge)
- Store large binary data or files
- Assume metadata is validated beyond JSON compatibility
Limitations
- No Nested Merging: Only top-level keys are merged, nested objects are fully replaced
- Size Limit: Total metadata JSON should be reasonable (< 10KB recommended)
- No Schema Validation: Clients responsible for data consistency
- No Indexing: Cannot efficiently filter sessions by arbitrary metadata keys
- Type Preservation: All JSON types are preserved, but JavaScript-specific types (Date, RegExp) must be serialized to JSON-compatible formats (ISO strings, plain objects)
Next Steps
- Session Lifecycle - Understand session states and transitions