Skip to Content
WidgetMessage Interceptor

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:

  1. Cancel the interceptor execution
  2. Show an error message to the user
  3. Emit an error event with type: "interceptor"
widget.on("error", (error) => {
  if (error.type === "interceptor") {
    console.error("Interceptor timed out:", error.message);
  }
});

Thrown Errors

If the interceptor throws an error:

  1. The error will be caught
  2. An error message will be shown to the user
  3. An error event 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