На многих сайтах можно сделать автоматическую поддержку для пользователей. Проще всего использовать лёгкий виджет чата на чистом JavaScript, а всю умную часть (бота) вынести в n8n + OpenAI. Такой подход:
- не требует тяжёлых и сложных плагинов под WordPress;
- даёт полный контроль над тем, как бот отвечает;
- позволяет использовать один backend для нескольких сайтов.
Ниже — простой контракт обмена данными, код виджета и базовый/расширенный вариант воркфлоу в n8n.
- 1. Взаимодействие между чат-виджетом и n8n
- 1.1. Что отправляет виджет в n8n
- 1.2. Что должен вернуть n8n
- 2. Фронтенд: чат-виджет на JavaScript для WordPress
- 2.1. Создание JavaScript-сниппета в WordPress
- 2.2. Полный код JavaScript-виджета
- 3. Базовый воркфлоу в n8n: рабочий чат без истории
- 3.1. Структура воркфлоу
- 3.2. Настройка Webhook-ноды
- 3.3. Нода OpenAI Chat
- 3.4. Формирование ответа в Function-node
- 3.5. Нода Respond to Webhook
- 4. Расширение: история диалога и сессии
- 4.1. Идея архитектуры сессии
- 4.2. Function-нода для подготовки сессии
- 4.3. Чтение истории из Data Store
- 4.4. Вызов OpenAI с историей
- 4.5. Добавление ответа ассистента в историю
- 4.6. Сохранение истории и ответ виджету
- 5. Документация для клиента или партнёра
- 5.1. Что это за чат-виджет
- 5.2. Как подключить на WordPress
- 5.3. Как работает обмен с backend
- 5.4. Что можно делать с логами
- Заключение
1. Взаимодействие между чат-виджетом и n8n
Фронтенд-виджет общается с backend только через один HTTP-адрес (Webhook) в n8n. Это упрощает настройку и позволяет легко менять логику внутри воркфлоу.
1.1. Что отправляет виджет в n8n
Виджет делает запрос по HTTP API (метод POST) на Webhook в n8n и передаёт в JSON только самое главное: текст пользователя и базовый контекст.
POST https://your-n8n-domain.com/webhook/your-webhook-id
Content-Type: application/json
{
"message": "Текст пользователя",
"siteLabel": "example.com",
"pageUrl": "https://example.com/some-page/",
"userAgent": "Mozilla/5.0 (...)"
} message— текст сообщения пользователя;siteLabel— метка сайта (обычно домен или короткое имя проекта);pageUrl— точный URL страницы, где открыт виджет;userAgent— строка браузера (можно использовать для простой аналитики или для ключа сессии).
1.2. Что должен вернуть n8n
Ответ n8n — простой JSON с единственным полем reply. Виджет показывает это поле как сообщение бота.
{
"reply": "Ответ бота"
} Лучше сделать ответ максимально простым. Всё дополнительное (логи, аналитика, CRM и т.п.) удобнее обрабатывать внутри n8n, не усложняя формат ответа для виджета.
2. Фронтенд: чат-виджет на JavaScript для WordPress
Фронтенд полностью живёт в одном JavaScript-сниппете. В WordPress его удобно подключать через плагин для сниппетов.
2.1. Создание JavaScript-сниппета в WordPress
Простой порядок действий:
- Установить и включить плагин для сниппетов (тип: JavaScript-код на фронтенде).
- Создать новый сниппет типа JavaScript (не PHP и не HTML).
- Вставить в него код виджета целиком.
- Выбрать, где показывать виджет: «На всех страницах» или по своим условиям.
- Сохранить и включить сниппет.
Если на сайте включено кэширование и минификация скриптов, проверьте, что сниппет не ломается при объединении и сжатии. При необходимости исключите этот скрипт из минификации.
2.2. Полный код JavaScript-виджета
Подключитесь к админке WordPress, создайте JavaScript-сниппет и вставьте код ниже. Обязательно замените N8N_WEBHOOK_URL на свой реальный Webhook из n8n.
(function () {
// Адрес webhook n8n (замените на свой)
const N8N_WEBHOOK_URL = "https://your-n8n-domain.com/webhook/your-webhook-id";
function initChatWidget() {
// Защита от повторного запуска
if (document.getElementById("chatWidget") || document.getElementById("chatToggleBtn")) {
return;
}
// 1) Стили
const style = document.createElement("style");
style.textContent = `
.chat-toggle-btn {
position: fixed;
right: 20px;
bottom: 20px;
width: 56px;
height: 56px;
border-radius: 50%;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
background: #4d3bfe;
color: #fff;
box-shadow: 0 6px 20px rgba(0,0,0,0.2);
z-index: 9999;
}
.chat-widget {
position: fixed;
right: 20px;
bottom: 90px;
width: 320px;
max-height: 480px;
background: #ffffff;
border-radius: 16px;
box-shadow: 0 12px 30px rgba(0,0,0,0.15);
display: flex;
flex-direction: column;
overflow: hidden;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
z-index: 9999;
}
.chat-hidden {
display: none;
}
.chat-header {
padding: 12px 16px;
background: #4d3bfe;
color: #fff;
font-size: 14px;
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-header button {
border: none;
background: transparent;
color: #fff;
font-size: 18px;
cursor: pointer;
padding: 0;
line-height: 1;
}
.chat-messages {
padding: 10px 12px;
overflow-y: auto;
flex: 1;
font-size: 13px;
background: #f7f7fb;
}
.chat-message {
margin-bottom: 8px;
max-width: 90%;
padding: 8px 10px;
border-radius: 10px;
line-height: 1.4;
word-wrap: break-word;
}
.chat-message.user {
margin-left: auto;
background: #4d3bfe;
color: #fff;
border-bottom-right-radius: 2px;
}
.chat-message.bot {
margin-right: auto;
background: #ffffff;
color: #111827;
border-bottom-left-radius: 2px;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}
.chat-status {
font-size: 11px;
color: #6b7280;
padding: 4px 12px;
min-height: 16px;
}
.chat-input-area {
border-top: 1px solid #e5e7eb;
padding: 8px;
background: #fff;
display: flex;
gap: 6px;
}
.chat-input-area input[type="text"] {
flex: 1;
border-radius: 999px;
border: 1px solid #d1d5db;
padding: 8px 12px;
font-size: 13px;
outline: none;
}
.chat-input-area input[type="text"]:focus {
border-color: #4d3bfe;
box-shadow: 0 0 0 1px rgba(77,59,254,0.25);
}
.chat-input-area button {
border-radius: 999px;
border: none;
padding: 0 14px;
font-size: 13px;
cursor: pointer;
background: #4d3bfe;
color: #fff;
white-space: nowrap;
}
.chat-input-area button:disabled {
opacity: 0.6;
cursor: default;
}
`;
document.head.appendChild(style);
// 2) HTML виджета
const widgetHTML = `
<button class="chat-toggle-btn" id="chatToggleBtn" aria-label="Открыть чат">Чат</button>
<div class="chat-widget chat-hidden" id="chatWidget">
<div class="chat-header">
<span>Чат-помощник</span>
<button id="chatCloseBtn" aria-label="Закрыть чат">×</button>
</div>
<div class="chat-messages" id="chatMessages"></div>
<div class="chat-status" id="chatStatus"></div>
<form class="chat-input-area" id="chatForm">
<input
type="text"
id="chatInput"
placeholder="Напишите сообщение..."
autocomplete="off"
required
/>
<button type="submit" id="chatSendBtn">Отправить</button>
</form>
</div>
`;
document.body.insertAdjacentHTML("beforeend", widgetHTML);
// 3) Логика
const chatWidget = document.getElementById("chatWidget");
const chatToggleBtn = document.getElementById("chatToggleBtn");
const chatCloseBtn = document.getElementById("chatCloseBtn");
const chatMessages = document.getElementById("chatMessages");
const chatStatus = document.getElementById("chatStatus");
const chatForm = document.getElementById("chatForm");
const chatInput = document.getElementById("chatInput");
const chatSendBtn = document.getElementById("chatSendBtn");
function addMessage(text, sender = "bot") {
const msg = document.createElement("div");
msg.classList.add("chat-message", sender);
msg.textContent = text;
chatMessages.appendChild(msg);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
async function sendMessageToN8N(messageText) {
const payload = {
message: messageText,
siteLabel: window.location.hostname,
pageUrl: window.location.href,
userAgent: navigator.userAgent
};
try {
chatStatus.textContent = "Отправка...";
chatSendBtn.disabled = true;
chatInput.disabled = true;
const response = await fetch(N8N_WEBHOOK_URL, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error("Ошибка сети: " + response.status);
}
const data = await response.json();
if (data && typeof data.reply === "string") {
addMessage(data.reply, "bot");
chatStatus.textContent = "";
} else {
addMessage("Извините, сервер вернул непонятный ответ.", "bot");
chatStatus.textContent = "Некорректный формат ответа сервера.";
}
} catch (error) {
console.error("Ошибка при запросе к n8n:", error);
addMessage("Не удалось отправить сообщение. Попробуйте позже.", "bot");
chatStatus.textContent = "Ошибка отправки.";
} finally {
chatSendBtn.disabled = false;
chatInput.disabled = false;
chatInput.focus();
}
}
chatToggleBtn.addEventListener("click", () => {
chatWidget.classList.toggle("chat-hidden");
if (!chatWidget.classList.contains("chat-hidden")) {
chatInput.focus();
}
});
chatCloseBtn.addEventListener("click", () => {
chatWidget.classList.add("chat-hidden");
});
chatForm.addEventListener("submit", (event) => {
event.preventDefault();
const text = chatInput.value.trim();
if (!text) return;
addMessage(text, "user");
chatInput.value = "";
sendMessageToN8N(text);
});
addMessage("Здравствуйте! Задайте вопрос, и чат-помощник постарается помочь.", "bot");
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initChatWidget);
} else {
initChatWidget();
}
})(); Этот виджет не зависит от темы WordPress и работает там, где можно запустить пользовательский JavaScript на фронтенде.
3. Базовый воркфлоу в n8n: рабочий чат без истории
На стороне backend достаточно простого воркфлоу из четырёх шагов. Он уже даёт рабочий AI-чат, но без памяти о предыдущих сообщениях.
3.1. Структура воркфлоу
- Webhook — принимает запрос от виджета;
- OpenAI Chat (или HTTP Request к OpenAI API) — создаёт ответ;
- Function — приводит ответ к формату
{ reply: "..." }; - Respond to Webhook — отправляет JSON обратно виджету.
3.2. Настройка Webhook-ноды
Создайте новый воркфлоу и добавьте ноду Webhook:
- HTTP Method:
POST; - Response Mode:
Using "Respond to Webhook"; - Path: путь вида
/webhook/your-webhook-id(или свой).
Webhook обычно доступен из интернета. В продакшене важно ограничить доступ по IP, токену или использовать прокси с авторизацией. API-ключи OpenAI храните только в переменных окружения n8n.
3.3. Нода OpenAI Chat
Далее добавьте ноду OpenAI Chat (или HTTP Request к OpenAI API). Задача простая: взять текст пользователя из body.message и передать его в messages.
Пример JSON для поля messages (формат может чуть отличаться, но идея одна):
[
{
"role": "system",
"content": "Ты — помощник сайта. Отвечай коротко, по делу и простым техническим языком."
},
{
"role": "user",
"content": "={{ $json.body.message }}"
}
] 3.4. Формирование ответа в Function-node
После ноды OpenAI добавьте ноду Function. В простом варианте OpenAI возвращает структуру с choices[0].message.content. Нужно вынести этот текст в поле reply.
const answer = $json.choices[0].message.content;
return [
{
json: {
reply: answer
}
}
]; 3.5. Нода Respond to Webhook
В конце цепочки подключите ноду Respond to Webhook, настроенную в режиме JSON-ответа. В теле ответа укажите:
{
"reply": "={{ $json.reply }}"
} В результате виджет всегда получает предсказуемый JSON вида { "reply": "..." } и показывает это сообщение в чате.
4. Расширение: история диалога и сессии
Если нужен «умный» чат с памятью, удобно хранить историю сообщений в Data Store или внешней базе. Ключ сессии можно собрать из домена, userAgent и других простых идентификаторов.
4.1. Идея архитектуры сессии
- Определить ключ сессии (например,
CHAT:<siteLabel>:<userAgent>). - Прочитать историю сообщений по этому ключу из Data Store.
- Добавить новое сообщение пользователя к истории.
- Отправить всю историю (обрезанную по длине) в OpenAI.
- Добавить ответ ассистента в историю и сохранить её в Data Store.
- Вернуть только последнее сообщение ассистента в поле
reply.
4.2. Function-нода для подготовки сессии
Сразу после Webhook-ноды можно добавить Function-ноду, которая аккуратно разберёт вход и создаст ключ сессии.
const body = $json.body || {};
const message = body.message || "";
const siteLabel = body.siteLabel || "unknown";
const pageUrl = body.pageUrl || "";
const userAgent = body.userAgent || "";
// Простой вариант ключа сессии
const sessionKey = `CHAT:${siteLabel}:${userAgent}`;
return [
{
json: {
message,
siteLabel,
pageUrl,
userAgent,
sessionKey
}
}
]; 4.3. Чтение истории из Data Store
Далее добавьте ноду Data Store → Get:
- Key:
={{ $json.sessionKey }}; - Если данных нет — считайте, что результат это пустой массив
[].
После этого можно использовать ещё одну Function-ноду, чтобы собрать историю и подготовить массив messages для OpenAI.
const prev = $json.value || []; // value из Data Store
const { message, sessionKey, siteLabel, pageUrl, userAgent } = $items(0, 0).json;
// ограничиваем историю, чтобы она не разрасталась
const MAX_MESSAGES = 20;
const trimmed = prev.slice(-MAX_MESSAGES);
// добавляем новое сообщение пользователя
trimmed.push({
role: "user",
content: message
});
// готовим messages для OpenAI с system-промптом
const messages = [
{
role: "system",
content: "Ты — помощник сайта. Отвечай коротко, по делу и простым техническим языком."
},
...trimmed
];
return [
{
json: {
sessionKey,
siteLabel,
pageUrl,
userAgent,
history: trimmed,
messages
}
}
]; 4.4. Вызов OpenAI с историей
В ноде OpenAI Chat достаточно использовать поле messages из предыдущей Function-ноды, например:
={{ $json.messages }} Так модель видит не только последнее сообщение, но и короткую историю диалога.
4.5. Добавление ответа ассистента в историю
После ноды OpenAI добавьте ещё одну Function-ноду, которая добавляет ответ ассистента в историю и одновременно формирует финальный reply для виджета.
const history = $json.history || [];
const answer = $json.choices[0].message.content;
history.push({
role: "assistant",
content: answer
});
return [
{
json: {
sessionKey: $json.sessionKey,
history,
reply: answer
}
}
]; 4.6. Сохранение истории и ответ виджету
Сохраните историю через Data Store → Set:
- Key:
={{ $json.sessionKey }}; - Value:
={{ $json.history }}.
Далее используйте ноду Respond to Webhook с тем же JSON, что и в базовом варианте:
{
"reply": "={{ $json.reply }}"
} Обычно достаточно хранить 10–20 последних сообщений. Это экономит токены и ускоряет ответы, но у пользователя остаётся ощущение живого диалога.
5. Документация для клиента или партнёра
Ниже — пример простого описания сервиса, который можно отдать владельцу сайта в виде инструкции (например, в Notion или PDF).
5.1. Что это за чат-виджет
Это лёгкий виджет чата для поддержки пользователей, который подключается на сайт одной строкой кода и общается через n8n + OpenAI. Вся логика бота — на стороне n8n. На сайте только внешний JavaScript-файл.
5.2. Как подключить на WordPress
- Установите плагин для JavaScript-сниппетов.
- Создайте новый сниппет типа JavaScript.
- Вставьте предоставленный код виджета и укажите адрес Webhook.
- Включите сниппет и выберите, где показывать виджет (на всех страницах или только в нужных местах).
5.3. Как работает обмен с backend
- Пользователь открывает виджет и пишет сообщение.
- Виджет отправляет запрос
POSTв n8n:
{
"message": "Текст пользователя",
"siteLabel": "<домен вашего сайта>",
"pageUrl": "<полный URL страницы>",
"userAgent": "<браузер пользователя>"
} - n8n:
- получает сообщение через Webhook;
- при необходимости подгружает историю диалога;
- обращается к OpenAI за ответом;
- формирует ответ в виде JSON.
- Виджет показывает поле
replyкак ответ бота:
{
"reply": "Ответ бота"
} 5.4. Что можно делать с логами
Дополнительно доступны поля:
siteLabel— домен или идентификатор сайта, на котором стоит виджет;pageUrl— страница, на которой был задан вопрос;userAgent— тип устройства и браузера.
Эти данные можно использовать для:
- аналитики по страницам и типам трафика;
- улучшения ответов (например, отдельные промпты для разных разделов сайта);
- отправки обращений в CRM или helpdesk.
Заключение
Подход с лёгким JavaScript-виджетом и backend на n8n + OpenAI даёт простой в поддержке и расширяемый AI-чат для WordPress без тяжёлых плагинов. Контракт в виде простого JSON облегчает интеграцию, а хранение истории в Data Store позволяет добавить память и аналитику по мере развития проекта.









