Message Interceptor
The Message Interceptor API allows you to intercept, modify, or handle chat messages before they reach the Nexus backend. This enables custom logic, message routing, enrichment, validation, and more.
Overview
The message interceptor is a powerful feature that gives you complete control over message flow:
- Intercept messages before they reach the backend
- Handle messages locally without backend calls
- Enrich messages with additional metadata
- Route messages based on content or conditions
- Validate input and provide immediate feedback
- Implement custom logic for specific use cases
Basic Usage
Configure the message interceptor in the advanced section:
const widget = window.NexusChatWidget.init({
experienceId: "your-experience-id",
apiUrl: "http://localhost:3001",
advanced: {
messageInterceptor: async (message, context) => {
// Your custom logic here
console.log("User message:", message.text);
// Return null to let Nexus handle it
return null;
},
},
});API Reference
Interceptor Function
async function messageInterceptor(
message: Message,
context: Context,
): Promise<InterceptorResult | null>;Parameters
message Object
The user’s message with the following properties:
interface Message {
text: string; // The message text
metadata?: Record<string, any>; // Optional metadata
role: "user"; // Always "user" for intercepted messages
}context Object
Contextual information about the current session:
interface Context {
store: {
events: {
value: Event[]; // Array of all session events
};
addEvent: (event: Event) => void; // Add custom events
};
sessionId: string; // Current session ID
}Return Values
The interceptor can return one of several values:
1. Return null - Pass Through
Let Nexus handle the message normally:
return null;2. Return Modified Message
Enrich or modify the message before sending to Nexus:
return {
handled: false, // false = send to Nexus
message: {
text: message.text,
metadata: {
customField: "value",
timestamp: new Date().toISOString(),
},
},
};3. Return Custom Response
Handle the message locally without backend call:
return {
handled: true, // true = don't send to Nexus
response: {
text: "Your custom response",
role: "assistant",
format: "markdown", // optional: "text" or "markdown"
metadata: {
// optional metadata
source: "interceptor",
},
},
};4. Return Error
Return an error message to the user:
return {
handled: true,
error: "Unable to process your request",
format: "markdown", // optional
};Configuration Options
Interceptor Timeout
Set a timeout for the interceptor function (default: 5000ms):
{
advanced: {
messageInterceptor: async (message, context) => {
// Your logic
},
interceptorTimeout: 10000 // 10 seconds
}
}If the interceptor exceeds the timeout, the widget will show an error and the message will not be sent to Nexus.
Common Patterns
Pattern 1: No Interceptor
Default behavior - all messages go directly to Nexus:
window.NexusChatWidget.init({
experienceId: "demo-experience",
apiUrl: "http://localhost:3001",
// No messageInterceptor configured
});Pattern 2: Custom Response
Handle all messages locally without backend calls:
{
advanced: {
messageInterceptor: async (message, context) => {
console.log("[Interceptor] Handling:", message.text);
// Simulate thinking delay
await new Promise((resolve) => setTimeout(resolve, 500));
// Return custom response (no backend call)
return {
handled: true,
response: {
text: `Custom response to: "${message.text}"`,
role: "assistant",
metadata: {
source: "custom-interceptor",
timestamp: new Date().toISOString(),
},
},
};
};
}
}Pattern 3: Message Enrichment
Add metadata to messages before sending to Nexus:
{
advanced: {
messageInterceptor: async (message, context) => {
console.log("[Interceptor] Enriching:", message.text);
// Add metadata and continue to Nexus
return {
handled: false,
message: {
text: message.text,
metadata: {
...message.metadata,
pageUrl: window.location.href,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString(),
sessionLength: context.store.events.value.length,
},
},
};
};
}
}Pattern 4: Conditional Routing
Route messages based on content:
{
advanced: {
messageInterceptor: async (message, context) => {
// Handle "help" messages locally
if (message.text.toLowerCase().includes("help")) {
return {
handled: true,
response: {
text:
"**Help Menu**\n\n" +
'- Type "help" for this menu\n' +
"- Type anything else to talk to Nexus AI\n" +
`- Current session has ${context.store.events.value.length} events`,
role: "assistant",
format: "markdown",
},
};
}
// All other messages go to Nexus
return null;
};
}
}Pattern 5: Error Handling
Demonstrate various error scenarios:
{
advanced: {
messageInterceptor: async (message, context) => {
let messageCount = 0;
messageCount++;
// First message: Simulate timeout (caught after 5s)
if (messageCount === 1) {
await new Promise(resolve => setTimeout(resolve, 10000));
}
// Second message: Throw error
if (messageCount === 2) {
throw new Error('Simulated interceptor error!');
}
// Third message: Return error with custom format
if (messageCount === 3) {
return {
handled: true,
error: '**Custom Error**: Unable to process your request.\n\n' +
'Please try again or [contact support](https://example.com/support).',
format: 'markdown'
};
}
// Fourth+ messages: Normal behavior
return {
handled: true,
response: {
text: 'All error scenarios demonstrated.',
role: 'assistant'
}
};
},
interceptorTimeout: 5000 // 5 second timeout
}
}Use Cases
Input Validation
Validate user input before processing:
messageInterceptor: async (message) => {
// Check for profanity or inappropriate content
if (containsProfanity(message.text)) {
return {
handled: true,
error: "Please keep the conversation professional.",
};
}
// Check message length
if (message.text.length > 1000) {
return {
handled: true,
error: "Message too long. Please keep it under 1000 characters.",
};
}
// Pass through to Nexus
return null;
};FAQ Handling
Handle frequently asked questions locally:
const faqs = {
hours: "We're open Monday-Friday, 9am-5pm EST.",
pricing: "Visit our pricing page at https://example.com/pricing",
contact: "Email us at support@example.com or call 1-800-555-0123",
};
messageInterceptor: async (message) => {
const lowerText = message.text.toLowerCase();
// Check for FAQ keywords
for (const [keyword, answer] of Object.entries(faqs)) {
if (lowerText.includes(keyword)) {
return {
handled: true,
response: {
text: answer,
role: "assistant",
metadata: { source: "faq" },
},
};
}
}
// Not a FAQ, send to Nexus
return null;
};User Context Enrichment
Add user information to messages:
messageInterceptor: async (message, context) => {
// Get current user info from your app
const user = getCurrentUser();
return {
handled: false,
message: {
text: message.text,
metadata: {
userId: user.id,
userEmail: user.email,
userTier: user.subscriptionTier,
sessionLength: context.store.events.value.length,
pageContext: {
url: window.location.href,
title: document.title,
},
},
},
};
};Rate Limiting
Implement client-side rate limiting:
let messageTimestamps = [];
const MAX_MESSAGES_PER_MINUTE = 10;
messageInterceptor: async (message) => {
const now = Date.now();
const oneMinuteAgo = now - 60000;
// Remove old timestamps
messageTimestamps = messageTimestamps.filter((t) => t > oneMinuteAgo);
// Check rate limit
if (messageTimestamps.length >= MAX_MESSAGES_PER_MINUTE) {
return {
handled: true,
error: "You're sending messages too quickly. Please slow down.",
};
}
// Add current timestamp
messageTimestamps.push(now);
// Continue to Nexus
return null;
};A/B Testing
Route messages to different backends for testing:
messageInterceptor: async (message, context) => {
const experimentGroup = getExperimentGroup(context.sessionId);
return {
handled: false,
message: {
text: message.text,
metadata: {
experiment: "model-comparison",
group: experimentGroup, // "control" or "treatment"
modelVersion: experimentGroup === "control" ? "v1" : "v2",
},
},
};
};Analytics Tracking
Track message patterns before sending:
messageInterceptor: async (message, context) => {
// Send analytics event
analytics.track("Chat Message Sent", {
messageLength: message.text.length,
wordCount: message.text.split(" ").length,
sessionLength: context.store.events.value.length,
sessionId: context.sessionId,
});
// Continue to Nexus
return null;
};Error Handling
Timeout Errors
If the interceptor exceeds interceptorTimeout, the widget will:
- Cancel the interceptor execution
- Show an error message to the user
- Emit an
errorevent withtype: "interceptor"
widget.on("error", (error) => {
if (error.type === "interceptor") {
console.error("Interceptor timed out:", error.message);
}
});Thrown Errors
If the interceptor throws an error:
- The error will be caught
- An error message will be shown to the user
- An
errorevent will be emitted
messageInterceptor: async (message) => {
try {
// Your logic that might fail
const result = await riskyOperation(message.text);
return result;
} catch (error) {
console.error("Interceptor error:", error);
return {
handled: true,
error: "Something went wrong. Please try again.",
};
}
};Custom Error Messages
Return custom formatted error messages:
return {
handled: true,
error:
"**Error**: Unable to process your request.\n\n" +
"Please check:\n" +
"- Your message is not empty\n" +
"- You are not sending too many messages\n" +
"- You have a stable internet connection\n\n" +
"[Contact Support](https://example.com/support)",
format: "markdown",
};Best Practices
1. Keep It Fast
The interceptor blocks message sending, so keep it fast:
// Good - quick validation
messageInterceptor: async (message) => {
if (message.text.length > 500) {
return { handled: true, error: "Message too long" };
}
return null;
};
// Avoid - slow external API call
messageInterceptor: async (message) => {
// This will make the chat feel slow
const result = await fetch("https://slow-api.com/validate");
return null;
};2. Handle Errors Gracefully
Always catch errors and provide user-friendly messages:
messageInterceptor: async (message) => {
try {
// Your logic
} catch (error) {
console.error("Interceptor error:", error);
return {
handled: true,
error: "Something went wrong. Please try again.",
};
}
};3. Use Appropriate Timeouts
Set realistic timeouts based on your interceptor’s complexity:
{
advanced: {
messageInterceptor: async (message) => {
// Quick validation - 2 second timeout is fine
},
interceptorTimeout: 2000
}
}4. Log for Debugging
Use console logs to debug interceptor behavior:
messageInterceptor: async (message, context) => {
console.log("[Interceptor] Received:", message.text);
console.log(
"[Interceptor] Session events:",
context.store.events.value.length,
);
const result = await yourLogic(message);
console.log("[Interceptor] Returning:", result);
return result;
};5. Return Consistent Types
Be consistent with your return types:
// Good - consistent structure
messageInterceptor: async (message) => {
if (condition) {
return { handled: true, response: { text: "...", role: "assistant" } };
}
return null;
};
// Avoid - mixing different return patterns
messageInterceptor: async (message) => {
if (condition) {
return { handled: true, response: { text: "..." } };
}
if (otherCondition) {
return "some string"; // Wrong!
}
return undefined; // Should return null
};Interactive Demo
See the Message Interceptor Demo to experiment with all 5 patterns and see the interceptor in action.
Next Steps
- Events System - Track interceptor events
- Configuration - All widget options
- Interactive Demo - Try the patterns yourself