CW Sync: Двусторонняя интеграция Chatwoot ↔ Telegram — Техническая документация

Статус: ✅ Работает в продакшене
Дата: 05.03.2026
Версия: v9 (финальная, стабильная)
n8n Version: 2.0.3
Инструкция оператора: chatwoot-operator-guide.md


Оглавление

  1. Общее описание
  2. Полная архитектура
  3. Инфраструктура
  4. Маршрут 1: NextBot → Chatwoot
  5. Маршрут 2: Chatwoot → Telegram + NextBot
  6. Анти-петля (Anti-Loop)
  7. Перехват команд оператора
  8. Обогащение контактов
  9. Supabase таблицы
  10. Chatwoot API
  11. Решённые проблемы и gotchas
  12. Тестирование
  13. Мониторинг

Общее описание

Система обеспечивает полностью двустороннюю синхронизацию между Telegram-ботом (через NextBot AI-агент) и CRM Chatwoot. Операторы видят все диалоги в Chatwoot и могут вмешиваться в любой момент.

Ключевые возможности


Полная архитектура

┌──────────────────────────────────────────────────────────────────────────┐
│                                                                          │
│   МАРШРУТ 1: Telegram → Chatwoot (Workflow bCKmQFvILtXzI3Sa)           │
│                                                                          │
│   Клиент (Telegram)                                                      │
│       ↓                                                                  │
│   NextBot (AI Agent)                                                     │
│       ↓ webhook: forwarded_output                                        │
│   n8n: NextBot Webhook                                                   │
│       ↓                                                                  │
│   n8n: Process Message (Code)                                            │
│       ├── Supabase: channel_mapping (lookup/create)                      │
│       ├── Telegram Bot API: getChat (обогащение контакта)                │
│       └── Chatwoot API: создание контакта + разговора (если новый)       │
│       ↓                                                                  │
│   n8n: IF: Should Send                                                   │
│       ↓ (skip=false)                                                     │
│   n8n: CW Action → Chatwoot API: отправка сообщения                     │
│       ↓              ⚡ content_attributes: { source: "n8n_sync" }       │
│   n8n: Log to Supabase → sync_message_log                                │
│                                                                          │
│ ──────────────────────────────────────────────────────────────────────── │
│                                                                          │
│   МАРШРУТ 2: Chatwoot → Telegram (Workflow EN7OWW2XHHt07oDU)           │
│                                                                          │
│   Оператор (Chatwoot UI)                                                 │
│       ↓ callback webhook                                                 │
│   n8n: Chatwoot Callback                                                 │
│       ↓                                                                  │
│   n8n: Filter & Lookup (Code)                                            │
│       ├── SKIP если event ≠ message_created                              │
│       ├── SKIP если message_type ≠ outgoing                              │
│       ├── SKIP если sender.type = contact                                │
│       ├── ⚡ SKIP если content_attributes.source = "n8n_sync" (ANTI-LOOP)│
│       └── Детект команд (is_command)                                     │
│       ↓                                                                  │
│   n8n: Is Command? (IF)                                                  │
│       ├── TRUE → NextBot Only (команда) → Log Result                     │
│       │          клиент НЕ видит!                                        │
│       └── FALSE → Send to Telegram → Notify NextBot → Log Result        │
│                   клиент ВИДИТ, AI засыпает                              │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

Как это работает пошагово (полный цикл)

Клиент пишет → AI отвечает

  1. Клиент отправляет сообщение боту в Telegram
  2. NextBot принимает сообщение, AI-агент формирует ответ
  3. NextBot отвечает клиенту в Telegram (напрямую)
  4. NextBot отправляет webhook forwarded_output на n8n с событиями:
    • client_message — сообщение клиента
    • agent_message — ответ AI-агента
  5. n8n Process Message:
    • Ищет маппинг в Supabase (dialog_id → conversation_id)
    • Если маппинга нет — создаёт контакт + разговор в Chatwoot
    • Определяет тип сообщения (incoming/outgoing)
  6. n8n CW Action отправляет сообщение в Chatwoot API с маркером content_attributes: { source: "n8n_sync" }
  7. Chatwoot callback вызывает Workflow #2
  8. ANTI-LOOP: Filter & Lookup видит source: "n8n_sync"return [] → workflow останавливается
  9. ✅ Результат: сообщение в Chatwoot, без дубликатов, AI продолжает работать

Оператор отвечает из Chatwoot

  1. Оператор набирает сообщение в Chatwoot UI и нажимает "Отправить"
  2. Chatwoot callback вызывает Workflow #2
  3. Filter & Lookup проверяет:
    • event = message_created
    • message_type = outgoing
    • sender.type ≠ contact
    • content_attributes.source ≠ n8n_sync ✅ (оператор, не наш бот)
  4. Is Command? → FALSE (обычное сообщение)
  5. Send to Telegram → отправляет через Telegram Bot API sendMessage
  6. Notify NextBot → отправляет message_type: "output" → AI засыпает
  7. ✅ Результат: клиент видит ответ оператора, AI на паузе

Оператор отправляет команду

  1. Оператор набирает "бот продолжай" в Chatwoot UI
  2. Filter & Lookup определяет is_command: true
  3. Is Command? → TRUE
  4. NextBot Only → отправляет команду в NextBot webhook
  5. ✅ Результат: AI просыпается, клиент НЕ видит команду

Инфраструктура

Компонент URL / ID Описание
Chatwoot https://chatcrm.eastpay.online CRM для операторов
n8n https://n8n.flowzzy.com Автоматизация workflow
Supabase https://cvzsgjksswowqgfxvrsb.supabase.co База данных (маппинг + логи)
NextBot https://app.nextbot.kz AI-агент платформа
Telegram Bot 8385176510:AAE... Бот EastPay ChatAgent
Chatwoot Account 3 EastPay
Chatwoot Inbox 3 EastPay Telegram AI (тип: API)

n8n Workflows

Workflow ID Направление Нод Webhook URL
CW Sync: NextBot → Chatwoot bCKmQFvILtXzI3Sa Telegram → Chatwoot 5 /webhook/nextbot-message-sync
CW Sync: Chatwoot → NextBot (callback) EN7OWW2XHHt07oDU Chatwoot → Telegram 7 /webhook/chatwoot-callback

Маршрут 1: NextBot → Chatwoot (Workflow bCKmQFvILtXzI3Sa)

Структура нод

NextBot Webhook → Process Message → IF: Should Send → CW Action → Log to Supabase

1. NextBot Webhook

Параметр Значение
Node ID 4ef9415f-53bc-4dcd-9abc-d64de5194cc2
Type n8n-nodes-base.webhook
Method POST
Path nextbot-message-sync

2. Process Message (Code Node)

Параметр Значение
Node ID 3c887340-ae54-481e-9960-20904fad3077
Type n8n-nodes-base.code
typeVersion 2
Language JavaScript
Version v9 (axios + contact enrichment + anti-loop marker)

Ключевые константы в коде:

const SB_URL = 'https://cvzsgjksswowqgfxvrsb.supabase.co';
const SB_KEY = 'eyJhbGci...'; // service_role key
const CW = 'https://chatcrm.eastpay.online';
const CW_TOKEN = 'FksZYfbqjb9RXmiEgAQqQ6Ev';
const CW_ACC = 3;   // Chatwoot Account ID
const CW_INBOX = 3; // Chatwoot Inbox ID
const TG_BOT_TOKEN = '8385176510:AAE...'; // Telegram Bot Token

Обрабатываемые события NextBot:

Событие Chatwoot message_type private? Описание
client_message incoming false Сообщение клиента
agent_message outgoing false Ответ AI-агента
manager_message outgoing true Комментарий менеджера (из NextBot)
dialog_start incoming false Начало диалога
send_error outgoing true Ошибка отправки

Логика создания контакта:

  1. Поиск маппинга в Supabase (channel_mapping)
  2. Если найден → используем существующий conversation_id
  3. Если НЕ найден:
    • Вызов Telegram Bot API getChat для обогащения
    • Создание контакта в Chatwoot (с custom_attributes)
    • Fallback: поиск существующего через /contacts/search
    • Обновление контакта через PUT /contacts/{id}
    • Создание разговора
    • Сохранение маппинга в Supabase

3. IF: Should Send

Пропускает только сообщения с skip: false (отфильтровывает невалидные payload).

4. CW Action (HTTP Request)

Параметр Значение
Method POST
URL https://chatcrm.eastpay.online/api/v1/accounts/3/conversations/{id}/messages
Body JSON (JSON.stringify)
Retry 3 попытки, 2 сек интервал

Тело запроса:

JSON.stringify({
  content: $json.text,
  message_type: $json.message_type,
  private: $json.is_private,
  content_attributes: { source: "n8n_sync" }  // ⚡ ANTI-LOOP MARKER
})

⚠️ Критически важно: content_attributes: { source: "n8n_sync" } — маркер, предотвращающий петлю обратной связи. Без него Workflow #2 воспримет ответ AI как сообщение оператора!

5. Log to Supabase

Логирует каждое отправленное сообщение в sync_message_log.


Маршрут 2: Chatwoot → Telegram + NextBot (Workflow EN7OWW2XHHt07oDU)

Структура нод

Chatwoot Callback → Filter & Lookup → Is Command?
                                        ├── TRUE  → NextBot Only (command) → Log Result
                                        └── FALSE → Send to Telegram → Notify NextBot → Log Result

1. Chatwoot Callback (Webhook)

Параметр Значение
Node ID webhook-cw-callback
Path chatwoot-callback
Webhook URL (настроен в Chatwoot) https://n8n.flowzzy.com/webhook/chatwoot-callback

2. Filter & Lookup (Code)

Критическая нода — фильтрует входящие webhook-и Chatwoot.

Порядок проверок:

// 1. Только event = message_created
if (body.event !== 'message_created') return [];

// 2. Только outgoing (ответы агентов/операторов)
if (msgType !== 'outgoing' && msgType !== 1) return [];

// 3. Пропускаем private-сообщения (заметки)
if (body.private === true) return [];

// 4. Пропускаем сообщения от контактов
if (body.sender?.type === 'contact') return [];

// 5. ⚡ ANTI-LOOP: Пропускаем сообщения нашего же workflow
if (contentAttrs.source === 'n8n_sync') return [];

// 6. Проверяем наличие telegram_chat_id в custom_attributes
// 7. Детектируем команды

Извлечение ID из Chatwoot payload:

const meta = body.conversation?.meta?.sender?.custom_attributes || {};
const chatId = meta.telegram_chat_id;      // для Telegram sendMessage
const dialogId = meta.nextbot_dialog_id;   // для NextBot webhook

3. Is Command? (IF)

Проверяет is_command === true (boolean).

4. Send to Telegram (HTTP Request)

// URL: https://api.telegram.org/bot{TOKEN}/sendMessage
JSON.stringify({
  chat_id: $json.telegram_chat_id,
  text: $json.content
})

5. Notify NextBot (HTTP Request)

// URL: https://app.nextbot.kz/api/webhooks/v1/{project_id}/{token}
JSON.stringify({
  dialog_id: dialogId,
  text: content,
  message_type: "output"  // ⚡ Ставит AI на паузу
})

6. NextBot Only — command (HTTP Request)

Аналогично Notify NextBot, но вызывается только для команд (AI не засыпает от /start, а просыпается).

7. Log Result

Логирует каждую операцию в sync_message_log. continueOnFail: true — не блокирует workflow при ошибке логирования.


Анти-петля (Anti-Loop)

Проблема

Без защиты возникает feedback loop (петля обратной связи):

❌ ПЕТЛЯ:
1. AI отвечает → Workflow #1 отправляет ответ в Chatwoot (outgoing)
2. Chatwoot callback → Workflow #2 ДУМАЕТ что это оператор
3. Workflow #2 → отправляет в Telegram (ДУБЛЬ!) + NextBot (AI ЗАСЫПАЕТ!)
4. Клиент видит 2 одинаковых ответа, AI на паузе без причины

Решение: Маркер content_attributes

✅ РЕШЕНИЕ:
1. Workflow #1 отправляет ответ с content_attributes: { source: "n8n_sync" }
2. Chatwoot callback → Workflow #2 видит source="n8n_sync"
3. Filter & Lookup → return [] (ПРОПУСК)
4. Workflow останавливается — никаких дубликатов и паузы!

Workflow #1 (CW Action):

{
  "content": "ответ AI",
  "message_type": "outgoing",
  "content_attributes": { "source": "n8n_sync" }
}

Workflow #2 (Filter & Lookup):

const contentAttrs = body.content_attributes || {};
if (contentAttrs.source === 'n8n_sync') return []; // ← SKIP!

[!CAUTION] Если убрать маркер из Workflow #1, система сломается — появятся дубликаты и AI будет засыпать при каждом своём ответе. НИКОГДА не удаляйте content_attributes: { source: "n8n_sync" } из CW Action!


Перехват команд оператора

Проблема

Когда оператор пишет "бот продолжай", это управляющая команда для NextBot, а не сообщение клиенту. Клиент не должен его видеть.

Решение: IF-ветка в callback workflow

Команда Действие Клиент видит?
продолжай Возобновить AI ❌ Нет
продолжи Возобновить AI ❌ Нет
бот продолжай Возобновить AI ❌ Нет
/start Возобновить AI ❌ Нет
стоп Остановить AI ❌ Нет
stop Остановить AI ❌ Нет
/stop Остановить AI ❌ Нет
менеджер ответит Остановить AI ❌ Нет
передаю коллеге Остановить AI ❌ Нет

Добавление новых команд

В ноде Filter & Lookup workflow EN7OWW2XHHt07oDU найдите массив:

const commandPhrases = [
  '/start', '/stop', 'стоп', 'stop',
  'продолжай', 'продолжи', 'бот продолжай',
  'менеджер ответит', 'передаю коллеге'
];

Добавьте новую команду (lowercase) и опубликуйте workflow.


Обогащение контактов

Источник: Telegram Bot API getChat

При первом появлении нового клиента, Process Message вызывает:

GET https://api.telegram.org/bot{TOKEN}/getChat?chat_id={telegram_chat_id}

Данные для обогащения

Поле Telegram Поле Chatwoot Пример
first_name + last_name name Sergei Modiazhenov
username custom_attributes.telegram_username @modyazhenov
— (вычисляемое) custom_attributes.telegram_link https://t.me/modyazhenov
bio custom_attributes.telegram_bio StaffAI — AI для бизнеса
chat_id custom_attributes.telegram_chat_id 69691085
dialog_id (NextBot) custom_attributes.nextbot_dialog_id 11523024

Custom Attribute Definitions

[!IMPORTANT] Custom attributes НЕ отображаются в Chatwoot UI без предварительного определения. Определения создаются ОДИН РАЗ через API: POST /api/v1/accounts/3/custom_attribute_definitions

Созданные определения:

Key Display Name Тип
telegram_chat_id Telegram Chat ID number
nextbot_dialog_id NextBot Dialog ID number
telegram_username Telegram Username text
telegram_link Telegram Link link
telegram_bio Telegram Bio text
source Source text
messenger Messenger text

Supabase таблицы

channel_mapping

Связь NextBot dialog_id ↔ Chatwoot conversation_id.

Колонка Тип Описание
id uuid PK
telegram_chat_id bigint Telegram chat ID
nextbot_dialog_id integer NextBot dialog ID
nextbot_project_id text 276a96dd-9dea-41f6-8828-3666a84d85c1
chatwoot_contact_id integer Chatwoot Contact ID
chatwoot_conversation_id integer Chatwoot Conversation ID
chatwoot_source_id text tg_{chat_id}_{dialog_id}
user_name text Имя пользователя
telegram_username text @username
is_active boolean Активен ли маппинг
created_at timestamp Дата создания

sync_message_log

Лог всех синхронизированных сообщений.

Колонка Тип Описание
id uuid PK
message_id text Уникальный ID сообщения
source text nextbot или chatwoot_agent
direction text nb_to_cw или chatwoot_to_telegram
content_preview text Первые 100-200 символов текста
message_type text incoming / outgoing / command
status text sent / success
created_at timestamp Дата создания

Chatwoot API

Базовые параметры

Параметр Значение
Base URL https://chatcrm.eastpay.online
Account ID 3
API Token FksZYfbqjb9RXmiEgAQqQ6Ev
Inbox ID 3 (тип: API)

Используемые endpoints

Endpoint Метод Назначение
/api/v1/accounts/3/contacts POST Создание контакта
/api/v1/accounts/3/contacts/{id} PUT Обновление контакта
/api/v1/accounts/3/contacts/search?q= GET Поиск контакта
/api/v1/accounts/3/conversations POST Создание разговора
/api/v1/accounts/3/conversations/{id}/messages POST Отправка сообщения
/api/v1/accounts/3/custom_attribute_definitions POST Определение custom attribute

NextBot Webhook API

Параметр Значение
URL https://app.nextbot.kz/api/webhooks/v1/{project_id}/{token}
Project ID 276a96dd-9dea-41f6-8828-3666a84d85c1
Token 41a58518d5a44d66bb656df141ad0963

Формат запроса к NextBot:

{
  "dialog_id": 11436784,
  "text": "текст сообщения",
  "message_type": "output"
}

message_type: "output" — ключевой параметр, который ставит AI на паузу и сохраняет контекст диалога.

Telegram Bot API

Параметр Значение
Bot Token 8385176510:AAEF6IjfCVH1e75pVkcy751Kbs2RgdDpxwk
sendMessage POST https://api.telegram.org/bot{TOKEN}/sendMessage
getChat GET https://api.telegram.org/bot{TOKEN}/getChat?chat_id={id}

Решённые проблемы и gotchas

🔴 Проблема 1: $helpers is not defined

Причина: n8n task runner sandbox не предоставляет $helpers в Code node. Решение: Используем const axios = require('axios') (с NODE_FUNCTION_ALLOW_EXTERNAL=axios в docker-compose).

🔴 Проблема 2: Task Runner memory crash (fetch/axios)

Причина: В callback workflow код с axios в Code node вызывал crash task runner. Решение: Весь HTTP взаимодействие в callback workflow перенесено в нативные HTTP Request ноды.

🔴 Проблема 3: message_type — строка или число?

Причина: Chatwoot callback иногда присылает message_type: "outgoing" (строка), иногда 1 (число). Решение: Проверяем оба: if (msgType !== 'outgoing' && msgType !== 1) return [];

🔴 Проблема 4: Неверные столбцы sync_message_log

Причина: JSON body содержал chatwoot_conversation_id, которого нет в таблице. Решение: Исправлен payload на существующие столбцы.

🔴 Проблема 5: Команды видны клиенту

Причина: Все outgoing-сообщения отправлялись в Telegram, включая "бот продолжай". Решение: IF-нода Is Command? с массивом commandPhrases + отдельная ветка NextBot Only.

🔴 Проблема 6: Контакты без обогащения

Причина: Контакт создавался только с user_name из NextBot (часто неполное имя). Решение: Telegram Bot API getChatfirst_name, last_name, username, bio.

🔴 Проблема 7: Custom attributes не видны в Chatwoot UI

Причина: Chatwoot требует предварительного определения custom attribute через API настроек. Решение: Созданы 7 custom_attribute_definitions через POST API.

🔴 Проблема 8: Feedback Loop (КРИТИЧЕСКАЯ)

Причина: AI отвечает → Workflow #1 → Chatwoot (outgoing) → Callback → Workflow #2 думает оператор → дубль в Telegram + AI на паузе! Решение: Маркер content_attributes: { source: "n8n_sync" } в Workflow #1, проверка if (contentAttrs.source === 'n8n_sync') return [] в Workflow #2.

🟡 Gotcha: Порядок publicации

При обновлении workflow через API, они сохраняются, но НЕ публикуются автоматически. Нужно:

  1. Открыть workflow в n8n UI
  2. Нажать "Publish" / "Опубликовать" (или активировать toggle)

🟡 Gotcha: JSON.stringify для body

Используем JSON.stringify() для body в HTTP Request нодах (не keypair). Это решает проблему с:

🟡 Gotcha: continueOnFail

Ноды логирования (Log to Supabase, Log Result) имеют continueOnFail: true. Если Supabase логирование упадёт — основной workflow не сломается.


Тестирование

Тест 1: Новый клиент из Telegram

  1. Написать боту с нового Telegram-аккаунта
  2. Проверить в Chatwoot: появился контакт с полным именем и custom attributes
  3. Проверить: сообщение отображается как incoming
  4. Проверить: ответ AI отображается как outgoing
  5. Проверить: НЕТ дубликатов сообщений AI в Telegram

Тест 2: Ответ оператора

  1. Написать сообщение клиенту из Chatwoot
  2. Проверить: клиент получил в Telegram
  3. Проверить: AI поставился на паузу (в NextBot → "Dialog paused")

Тест 3: Команда оператора

  1. Написать "бот продолжай" из Chatwoot
  2. Проверить: клиент НЕ получил сообщение в Telegram
  3. Проверить: AI возобновился (в NextBot → "Dialog resumed")

Тест 4: Отсутствие петли

  1. Дождаться ответа AI
  2. Проверить в n8n исполнения callback workflow:
    • Filter & Lookup: output=0 (заблокировано маркером)
    • Duration < 50ms (workflow мгновенно остановился)

Быстрая проверка через curl

# Отправить сообщение от оператора
curl -X POST \
  "https://chatcrm.eastpay.online/api/v1/accounts/3/conversations/3/messages" \
  -H "api_access_token: FksZYfbqjb9RXmiEgAQqQ6Ev" \
  -H "Content-Type: application/json" \
  -d '{"content": "тест", "message_type": "outgoing"}'

Мониторинг

n8n Executions

Ключевые метрики

Метрика Нормально Проблема
Callback duration для AI-ответов < 50ms > 1s (anti-loop не работает!)
Filter & Lookup output для AI-ответов 0 1 (петля!)
Дубликаты в Telegram 0 > 0 (петля!)
Паузы AI без вмешательства 0 > 0 (петля!)

Supabase запросы для мониторинга

-- Последние 20 синхронизированных сообщений
SELECT * FROM sync_message_log ORDER BY created_at DESC LIMIT 20;

-- Активные маппинги
SELECT * FROM channel_mapping WHERE is_active = true;

-- Кол-во сообщений за час
SELECT COUNT(*) FROM sync_message_log
WHERE created_at > NOW() - INTERVAL '1 hour';

Связанные документы

Документ Описание
chatwoot-operator-guide.md Инструкция для операторов
chatwoot-integration-plan.md Исходный план интеграции
nextbot-chatwoot-scenarios.md Сценарии взаимодействия

Последнее обновление: 05.03.2026, 19:57 UTC+7 — v9, финальная стабильная версия