
How to Create a Website Chatbot in Minutes: A Complete FlowHunt Guide
Learn how to build a powerful AI chatbot for your website in just 10 minutes using FlowHunt. This guide covers setup, knowledge base configuration, and deployme...

Complete reference for the FlowHunt JS API v2 integration. Embed the chatbot, subscribe to all 11 events, use flow variables, fire the Chat Hook trigger mid-conversation with sendHook(), track interactions with URL parameters, and control the chat window programmatically.
The FlowHunt JS API gives you full control over how your chatbot integrates with your website. Using the v2 integration code, you can embed the chatbot, subscribe to lifecycle and interaction events, pass dynamic data via flow variables, fire the Chat Hook trigger mid-conversation with sendHook(), track interactions with URL parameters, and programmatically control the chat window.
This guide covers every aspect of the JS API with code examples you can copy and adapt to your project.
Copy and paste the snippet below into your HTML just before the closing </body> tag. Replace YOUR_CHATBOT_ID and YOUR_WORKSPACE_ID with the values from your FlowHunt chatbot settings.
<script id="fh-chatbot-script-YOUR_CHATBOT_ID">
var currentScript = document.currentScript
|| document.getElementById('fh-chatbot-script-YOUR_CHATBOT_ID');
var script = document.createElement('script');
script.async = true;
script.src = 'https://app.flowhunt.io/api/chatbot/YOUR_CHATBOT_ID'
+ '?workspace_id=YOUR_WORKSPACE_ID&v=2';
script.onload = function () {
window.FHChatbot_YOUR_CHATBOT_ID.init(function (chatbotManager) {
// The chatbot is ready — use chatbotManager here
});
};
if (currentScript && currentScript.parentNode) {
currentScript.parentNode.insertBefore(script, currentScript.nextSibling);
} else {
document.head.appendChild(script);
}
</script>
The chatbot ID in the global variable name (window.FHChatbot_YOUR_CHATBOT_ID) uses underscores instead of hyphens.
setConfig()Before calling init(), you can override the default chatbot settings using setConfig():
<script id="fh-chatbot-script-YOUR_CHATBOT_ID">
var currentScript = document.currentScript
|| document.getElementById('fh-chatbot-script-YOUR_CHATBOT_ID');
var script = document.createElement('script');
script.async = true;
script.src = 'https://app.flowhunt.io/api/chatbot/YOUR_CHATBOT_ID'
+ '?workspace_id=YOUR_WORKSPACE_ID&v=2';
script.onload = function () {
window.FHChatbot_YOUR_CHATBOT_ID.setConfig({
headerTitle: 'Support Assistant',
maxWindowWidth: '700px',
showChatButton: false,
flowVariables: {
userId: '12345',
plan: 'enterprise',
},
urlSuffix: '?utm_source=chatbot',
});
window.FHChatbot_YOUR_CHATBOT_ID.init(function (chatbotManager) {
// Chatbot initialised with custom config
});
};
if (currentScript && currentScript.parentNode) {
currentScript.parentNode.insertBefore(script, currentScript.nextSibling);
} else {
document.head.appendChild(script);
}
</script>
| Option | Type | Description |
|---|---|---|
headerTitle | string | Custom header title text |
maxWindowWidth | string | Maximum chat window width (e.g. "700px") |
maxWindowHeight | string | Maximum chat window height |
inputPlaceholder | string | Placeholder text for the message input |
showChatButton | boolean | Show or hide the default floating chat button |
openChatPanel | boolean | Automatically open the chat panel on page load |
flowVariables | object | Key–value pairs of custom data passed to the flow. Values may be any JSON-serialisable type (string, number, boolean, object, array). |
urlSuffix | string | Query string appended to all chatbot-generated URLs |
cookieConsent | boolean | Enable session persistence via cookies |
embedded | string | Set to enable embedded mode (no close button) |
theme | string | Theme mode |
flowVariables are merged into every session the chatbot creates. They are typically used as static context (known at page load): the user’s ID, plan, current locale, etc.
window.FHChatbot_YOUR_CHATBOT_ID.setConfig({
flowVariables: {
userId: getCurrentUserId(),
userEmail: getCurrentUserEmail(),
currentPage: window.location.pathname,
plan: 'enterprise',
},
});
If the user navigates after the chat has opened, values passed here become stale. To update them mid-conversation, call
chatbotManager.sendHook()withoptions.flowVariables— see Mid-conversation host → flow communication below.
The urlSuffix parameter appends a query string to every URL generated by the chatbot. This is useful for tracking chatbot-originated traffic in analytics tools:
window.FHChatbot_YOUR_CHATBOT_ID.setConfig({
urlSuffix: '?utm_source=chatbot&utm_medium=widget',
});
Use cases:
Added in April 2026 as part of the FlowHunt Chat Hook feature.
In single-page applications the chat window typically stays open while the user navigates between screens. Once the chat is running, flowVariables passed via setConfig() become stale, and there is no way to nudge the flow about something that happened on the host page. A single manager method — sendHook(name, payload, options?) — covers both “fire a trigger” and “just update context” use cases:
name and (optionally) a payload to fire the flow’s Chat Hook trigger. The flow author drops one Chat Hook node on the canvas and branches on {ChatHook.hook_name} to decide what to do.options.flowVariables to merge session variables at the same time — the values are persisted before the trigger fires and remain available to the rest of the session.options.flowVariables is still merged, but no trigger runs and no credits are charged. This means host pages can call sendHook() optimistically without knowing whether the flow author has wired a trigger yet.sendHook() is a safe no-op before the session exists (internally buffered and flushed once the session is created) and never throws to the host page — network errors are logged via console.warn only.
Server- and client-side bounds that apply to sendHook(). Violating them is not a crash — the backend returns HTTP 422 and the manager logs via console.warn without throwing.
| Constraint | Limit | Where enforced | On violation |
|---|---|---|---|
sendHook name length | 1–256 chars | Backend (Pydantic) | HTTP 422, trigger does not fire |
Number of keys in options.flowVariables | ≤ 64 | Backend (Pydantic) | HTTP 422, nothing persisted |
Length of each key in options.flowVariables | ≤ 128 chars | Backend (Pydantic) | HTTP 422, nothing persisted |
| Pre-session calls buffered by the manager | 50 | Widget (in browser) | Oldest queued call is dropped and a console.warn logs |
The pre-session buffer cap only matters on pages where session creation fails indefinitely (e.g. a permanent network error). Under normal conditions the queue is flushed as soon as onFHChatbotSessionCreated fires.
onFHError).hook_name, payload, flow_variables) into whatever downstream steps you want to fire (Generator, Chat Output, Tool Calls, conditional branches on hook_name, etc.).The name the host page passes to sendHook() is a label for your flow to branch on, not a routing key — the backend does not match names against nodes. Instead, the flow’s single Chat Hook trigger fires and exposes the name as {ChatHook.hook_name}, which you reference in your flow logic to decide what to do.
Example system prompt:
If {ChatHook.hook_name} is "screen_changed", briefly summarise the page at
{ChatHook.payload.url}. If it is "user_action", acknowledge the action and
update memory. Otherwise, continue the conversation normally.
For more complex routing, wire a conditional step on {ChatHook.hook_name} and branch into multiple downstream paths.
chatbotManager.sendHook(name, payload, options?)chatbotManager.sendHook(
name: string,
payload?: Record<string, unknown>,
options?: { flowVariables?: Record<string, unknown> }
): Promise<void>;
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
name | string | yes | Label forwarded to the flow as {ChatHook.hook_name}. The trigger fires regardless of the value; your flow logic branches on it. |
payload | object | no | JSON payload handed to the trigger as {ChatHook.payload}. Validated against the node’s schema if enabled. Default {}. |
options.flowVariables | object | no | Session variables to merge before the trigger fires. Available to downstream steps and future user messages. |
Behaviour
onFHChatbotSessionCreated is safe; the call is buffered and flushed once the session exists. The buffer is capped — see Input limits above.sendHook() optimistically before the flow author wires a trigger. options.flowVariables is still persisted even in that case, so the same call serves as a context-only update.onFHError).name and options.flowVariables are bounded — see Input limits above. Violations return HTTP 422 and nothing is persisted.onFHChatbotFlowVariablesUpdate event if options.flowVariables was supplied (see Event Reference).console.warn.Example — proactive suggestion on SPA navigation
window.FHChatbot_YOUR_CHATBOT_ID.init(function (chatbotManager) {
window.addEventListener('hashchange', function () {
chatbotManager.sendHook('screen_changed', {
url: window.location.href,
screen_name: getScreenName(),
}, {
flowVariables: { current_page_url: window.location.href },
});
});
});
Example — context-only update (no Chat Hook wired)
If the flow has no Chat Hook trigger, the call is a silent 200 — so the same API can keep flow_variables in sync without firing anything:
window.FHChatbot_YOUR_CHATBOT_ID.init(function (chatbotManager) {
window.addEventListener('hashchange', function () {
chatbotManager.sendHook('navigate', {}, {
flowVariables: {
current_page_url: window.location.href,
screen_name: getScreenName(),
},
});
});
});
{ChatHook.hook_name} and {ChatHook.payload.foo} — values passed via sendHook('x', { foo: 1 }) are exposed on the Chat Hook trigger’s execution path. Only available on the path started by the hook fire.{flow_variables.foo} — values passed via options.flowVariables are merged into the session’s variable bag before the trigger fires. Every execution path (including normal Chat Input–triggered user messages) can read them.If you want the next user message to see a new value, put it in options.flowVariables — the payload alone only affects the execution run started by that hook.
The FlowHunt JS API dispatches 11 custom events on the window object. All events use the CustomEvent
API with bubbles: true and composed: true.
onFHChatbotReadyFired when the chatbot widget has fully rendered and is ready to use.
onFHChatbotSessionCreatedFired when a new chat session is created on the server.
event.detail.sessionId — the newly created session’s ID.onFHChatbotWindowOpenedFired when the user opens the chat window. Not fired in embedded mode.
onFHChatbotWindowClosedFired when the user closes the chat window. Not fired in embedded mode.
onFHMessageSentFired when the user sends a message.
event.detail.metadata = {
content: 'Hello, I need help with...',
createdAt: '2026-02-19T10:30:00.000Z',
};
onFHMessageReceivedFired when the chatbot receives and displays a response.
event.detail.metadata = {
flow_id: 'abc123',
message_id: 'msg_456',
message: 'Sure, I can help you with that!',
sender: {
sender_name: 'Support Agent',
sender_avatar: 'https://example.com/avatar.png',
},
};
sender is optional and only present when a human agent is involved.
onFHFormDataSentFired when the user submits form data through the chatbot.
event.detail.metadata = {
objectData: { name: 'John', email: 'john@example.com' },
createdAt: '2026-02-19T10:31:00.000Z',
};
onFHFeedbackFired when a user gives thumbs-up / thumbs-down feedback on a chatbot message.
event.detail.metadata = {
message_id: 'msg_456',
content: 'Optional feedback text',
feedback: 'P', // 'P' = positive, 'N' = negative
};
onFHToolCallFired when a tool or action is executed during flow processing. Only fired in flowAssistant and flowAssistantV3 modes.
event.detail.metadata = {
metadata: {
flow_id: 'abc123',
message_id: 'msg_789',
message: 'Calling search API...',
},
createdAt: '2026-02-19T10:32:00.000Z',
};
onFHErrorFired when an error occurs during chatbot operation.
event.detail.metadata = {
metadata: {
flow_id: 'abc123',
message_id: 'msg_err',
message: 'Flow execution failed',
},
createdAt: '2026-02-19T10:33:00.000Z',
};
onFHChatbotFlowVariablesUpdateAdded in April 2026.
Fired after a successful chatbotManager.sendHook(...) call that supplied options.flowVariables. Not fired for sendHook() calls that omit flowVariables.
event.detail = {
variables: {
current_page_url: 'https://example.com/products',
screen_name: 'products',
},
};
Use it to observe merged-in variables (e.g. to sync your own host-side state, for debugging, or to re-render a UI element that depends on the same data).
There are two ways to subscribe to chatbot events.
Use window.addEventListener anywhere in your page. This works even before the chatbot has loaded:
<script>
document.addEventListener('DOMContentLoaded', function () {
window.addEventListener('onFHChatbotReady', function () {
console.log('Chatbot is ready');
});
window.addEventListener('onFHChatbotSessionCreated', function (event) {
console.log('Session created:', event.detail.sessionId);
});
window.addEventListener('onFHChatbotWindowOpened', function () {
console.log('Chat window opened');
});
window.addEventListener('onFHChatbotWindowClosed', function () {
console.log('Chat window closed');
});
window.addEventListener('onFHMessageSent', function (event) {
console.log('User sent:', event.detail.metadata.content);
});
window.addEventListener('onFHMessageReceived', function (event) {
console.log('Bot replied:', event.detail.metadata.message);
});
window.addEventListener('onFHFormDataSent', function (event) {
console.log('Form submitted:', event.detail.metadata.objectData);
});
window.addEventListener('onFHFeedback', function (event) {
var fb = event.detail.metadata;
console.log('Feedback on message', fb.message_id, ':', fb.feedback);
});
window.addEventListener('onFHToolCall', function (event) {
console.log('Tool called:', event.detail.metadata);
});
window.addEventListener('onFHError', function (event) {
console.error('Chatbot error:', event.detail.metadata);
});
window.addEventListener('onFHChatbotFlowVariablesUpdate', function (event) {
console.log('Variables merged:', event.detail.variables);
});
});
</script>
To remove a listener, hold onto the handler reference:
var handleMessage = function (event) {
console.log(event.detail.metadata);
};
window.addEventListener('onFHMessageReceived', handleMessage);
// Later …
window.removeEventListener('onFHMessageReceived', handleMessage);
Inside the init() callback, use the chatbot manager’s built-in methods:
window.FHChatbot_YOUR_CHATBOT_ID.init(function (chatbotManager) {
chatbotManager.onSessionCreated(function () {
console.log('Session created');
});
chatbotManager.onWindowOpened(function () {
console.log('Window opened');
});
chatbotManager.onWindowClosed(function () {
console.log('Window closed');
});
chatbotManager.onMessageSent(function (event) {
console.log('User sent:', event.metadata);
});
chatbotManager.onMessageReceived(function (event) {
console.log('Bot replied:', event.metadata);
});
chatbotManager.onFormDataSent(function (event) {
console.log('Form data:', event.metadata);
});
chatbotManager.onFeedback(function (event) {
console.log('Feedback:', event.metadata);
});
chatbotManager.onToolCall(function (event) {
console.log('Tool call:', event.metadata);
});
chatbotManager.onError(function (event) {
console.error('Error:', event.metadata);
});
});
| Method | Parameters | Description |
|---|---|---|
onSessionCreated(fn) | fn: () => void | Listen for session creation |
onWindowOpened(fn) | fn: () => void | Listen for window open |
onWindowClosed(fn) | fn: () => void | Listen for window close |
onMessageSent(fn) | fn: (event) => void | Listen for user messages |
onMessageReceived(fn) | fn: (event) => void | Listen for bot responses |
onFormDataSent(fn) | fn: (event) => void | Listen for form submissions |
onFeedback(fn) | fn: (event) => void | Listen for user feedback |
onToolCall(fn) | fn: (event) => void | Listen for tool executions |
onError(fn) | fn: (event) => void | Listen for errors |
openChat() | — | Open the chat panel |
closeChat() | — | Close the chat panel |
sendHook(name, payload?, options?) (new) | name: string, payload?: object, options?: { flowVariables?: object } | Fire the Chat Hook trigger in the running flow (or merge options.flowVariables silently if no trigger is wired) |
To fully control when the chatbot appears, hide the default floating button and open the chat programmatically — for example, from your own custom button.
<button id="my-chat-button">Chat with us</button>
<script id="fh-chatbot-script-YOUR_CHATBOT_ID">
var currentScript = document.currentScript
|| document.getElementById('fh-chatbot-script-YOUR_CHATBOT_ID');
var script = document.createElement('script');
script.async = true;
script.src = 'https://app.flowhunt.io/api/chatbot/YOUR_CHATBOT_ID'
+ '?workspace_id=YOUR_WORKSPACE_ID&v=2';
script.onload = function () {
window.FHChatbot_YOUR_CHATBOT_ID.setConfig({ showChatButton: false });
window.FHChatbot_YOUR_CHATBOT_ID.init(function (chatbotManager) {
document.getElementById('my-chat-button')
.addEventListener('click', function () {
chatbotManager.openChat();
});
});
};
if (currentScript && currentScript.parentNode) {
currentScript.parentNode.insertBefore(script, currentScript.nextSibling);
} else {
document.head.appendChild(script);
}
</script>
You can combine hidden activation with event listeners to create advanced interactions:
<button id="open-chat" style="display:none;">Need help?</button>
<script>
window.addEventListener('onFHChatbotReady', function () {
document.getElementById('open-chat').style.display = 'block';
});
</script>
<script id="fh-chatbot-script-YOUR_CHATBOT_ID">
var currentScript = document.currentScript
|| document.getElementById('fh-chatbot-script-YOUR_CHATBOT_ID');
var script = document.createElement('script');
script.async = true;
script.src = 'https://app.flowhunt.io/api/chatbot/YOUR_CHATBOT_ID'
+ '?workspace_id=YOUR_WORKSPACE_ID&v=2';
script.onload = function () {
window.FHChatbot_YOUR_CHATBOT_ID.setConfig({ showChatButton: false });
window.FHChatbot_YOUR_CHATBOT_ID.init(function (chatbotManager) {
document.getElementById('open-chat')
.addEventListener('click', function () {
chatbotManager.openChat();
});
});
};
if (currentScript && currentScript.parentNode) {
currentScript.parentNode.insertBefore(script, currentScript.nextSibling);
} else {
document.head.appendChild(script);
}
</script>
A full working example that demonstrates configuration overrides, event tracking, custom chat activation, and the new sendHook() method together:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FlowHunt Chatbot Integration</title>
</head>
<body>
<h1>My Website</h1>
<button id="open-chat-btn">Talk to our AI assistant</button>
<button id="close-chat-btn">Close chat</button>
<!-- Subscribe to events before the chatbot loads -->
<script>
document.addEventListener('DOMContentLoaded', function () {
window.addEventListener('onFHChatbotReady', function () {
console.log('Chatbot widget is ready');
});
window.addEventListener('onFHChatbotSessionCreated', function (event) {
console.log('New chat session started:', event.detail.sessionId);
});
window.addEventListener('onFHMessageSent', function (event) {
console.log('User message:', event.detail.metadata.content);
});
window.addEventListener('onFHMessageReceived', function (event) {
console.log('Bot response:', event.detail.metadata.message);
});
window.addEventListener('onFHChatbotFlowVariablesUpdate', function (event) {
console.log('Context updated:', event.detail.variables);
});
window.addEventListener('onFHError', function (event) {
console.error('Chatbot error:', event.detail.metadata);
});
});
</script>
<!-- FlowHunt integration -->
<script id="fh-chatbot-script-YOUR_CHATBOT_ID">
var currentScript = document.currentScript
|| document.getElementById('fh-chatbot-script-YOUR_CHATBOT_ID');
var script = document.createElement('script');
script.async = true;
script.src = 'https://app.flowhunt.io/api/chatbot/YOUR_CHATBOT_ID'
+ '?workspace_id=YOUR_WORKSPACE_ID&v=2';
script.onload = function () {
window.FHChatbot_YOUR_CHATBOT_ID.setConfig({
showChatButton: false,
headerTitle: 'AI Assistant',
maxWindowWidth: '600px',
flowVariables: {
source: 'website',
current_page_url: window.location.href,
},
urlSuffix: '?utm_source=chatbot',
});
window.FHChatbot_YOUR_CHATBOT_ID.init(function (chatbotManager) {
// Open / close from custom buttons
document.getElementById('open-chat-btn')
.addEventListener('click', function () {
chatbotManager.openChat();
});
document.getElementById('close-chat-btn')
.addEventListener('click', function () {
chatbotManager.closeChat();
});
// Keep the flow's context in sync with SPA navigation and
// optionally fire the Chat Hook trigger (if the flow has one wired).
// If the flow has no Chat Hook, the call is a silent 200 — the
// flow_variables still get merged, so the same line covers both
// "notify the flow" and "just update context".
window.addEventListener('hashchange', function () {
chatbotManager.sendHook('screen_changed', {
url: window.location.href,
}, {
flowVariables: { current_page_url: window.location.href },
});
});
});
};
if (currentScript && currentScript.parentNode) {
currentScript.parentNode.insertBefore(script, currentScript.nextSibling);
} else {
document.head.appendChild(script);
}
</script>
</body>
</html>

Learn how to build a powerful AI chatbot for your website in just 10 minutes using FlowHunt. This guide covers setup, knowledge base configuration, and deployme...

Learn whether FlowHunt chatbots can detect the web page URL from which a customer starts a chat, why this is not native, how to enable context passing via integ...

A comprehensive guide to configuring chatbot welcome messages and understanding the FlowHunt onboarding experience for new users, including registration notific...
Cookie Consent
We use cookies to enhance your browsing experience and analyze our traffic. See our privacy policy.