🏗️ СПРИНТ: NextBot ↔ Chatwoot Потоковая Синхронизация
Проект: EastPay v2 Pro
Приоритет: 🔴 CRITICAL — Production-grade, финансовые сделки
Дата старта: 2026-03-05
Статус: 🔄 В работе — Phase 1 выполняется
⚠️ ИЗОЛЯЦИЯ ПРОЕКТА
NextBot аккаунт содержит НЕСКОЛЬКО проектов!
Этот спринт работает ТОЛЬКО с проектом:
- Название:
EastPay- Project ID (agent_hash):
276a96dd-9dea-41f6-8828-3666a84d85c1- Домен:
app.nextbot.kzВсе dialog_id, сценарии и вебхуки принадлежат ТОЛЬКО этому проекту.
Никогда не подставляйте ID из других проектов — утечка данных клиентов!
📋 Executive Summary
Production-grade двунаправленная синхронизация между NextBot (ИИ-агент) и Chatwoot (операторский интерфейс) через n8n. Система обслуживает финансовые сделки — каждое потерянное сообщение = потерянный клиент = потерянные деньги.
Принципы проектирования
| Принцип | Реализация |
|---|---|
| Zero message loss | Каждое сообщение записывается в Supabase перед обработкой |
| Idempotency | Уникальный message_id предотвращает дублирование |
| Graceful degradation | При сбое одной системы остальные продолжают работать |
| Loop prevention | 5 уровней защиты от зацикливания |
| Audit trail | Полный лог всех операций |
| Project isolation | Все данные привязаны к NextBot Project ID 276a96dd |
🎯 Прогресс спринта
| Фаза | Задача | Статус |
|---|---|---|
| 1 | T1: Supabase — таблицы channel_mapping + sync_message_log |
✅ |
| 1 | T2: Chatwoot — API Inbox | ✅ |
| 1 | T3: Chatwoot — Global Webhook | ⬜ |
| 1 | T4: Smoke Test всех API | ✅ |
| 2 | T5: n8n Workflow 1 «NextBot → Chatwoot» | ✅ (v4 published) |
| 2 | T6: NextBot — сценарии синхронизации (1-4) | ✅ (все 4 теста 200 OK) |
| 2 | T7: Integration Test NB → CW | ⬜ |
| 3 | T8: n8n Workflow 2 «Chatwoot → NextBot» | ✅ (TG removed, NextBot only) |
| 3 | T9: NextBot — настройки оператора | ⬜ |
| 3 | T10: Integration Test CW → NB | ⬜ |
| 4 | T11: E2E Full Cycle Test | ⬜ |
| 4 | T12: Production Checklist | ⬜ |
Легенда: ⬜ Todo · 🔄 В работе · ✅ Готово · ❌ Заблокировано
Phase 1: Foundation (Infrastructure)
T1: Supabase — Создание таблиц
Приоритет: 🔴 Blocker | Зависимости: нет | Статус: ✅ Выполнено
Ядро всей синхронизации. Без маппинга ID между системами ни один поток не работает.
Таблица channel_mapping:
CREATE TABLE public.channel_mapping (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
-- NextBot Project Isolation
-- ⚠️ CRITICAL: Ensures we only process EastPay project data
nextbot_project_id TEXT NOT NULL DEFAULT '276a96dd-9dea-41f6-8828-3666a84d85c1',
-- Telegram
telegram_chat_id BIGINT NOT NULL,
-- NextBot
nextbot_dialog_id BIGINT NOT NULL,
-- Chatwoot
chatwoot_contact_id INTEGER,
chatwoot_conversation_id INTEGER,
chatwoot_source_id TEXT,
-- Client metadata (cache)
user_name TEXT,
user_phone TEXT,
telegram_username TEXT,
-- State management
is_active BOOLEAN DEFAULT true,
operator_active BOOLEAN DEFAULT false,
last_synced_at TIMESTAMPTZ DEFAULT now(),
-- Timestamps
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
-- Constraint: only EastPay project
CONSTRAINT chk_eastpay_project CHECK (
nextbot_project_id = '276a96dd-9dea-41f6-8828-3666a84d85c1'
)
);
CREATE UNIQUE INDEX idx_cm_nextbot_dialog
ON public.channel_mapping(nextbot_dialog_id);
CREATE INDEX idx_cm_telegram_chat
ON public.channel_mapping(telegram_chat_id);
CREATE INDEX idx_cm_cw_conversation
ON public.channel_mapping(chatwoot_conversation_id)
WHERE chatwoot_conversation_id IS NOT NULL;
CREATE INDEX idx_cm_active
ON public.channel_mapping(is_active)
WHERE is_active = true;
CREATE OR REPLACE FUNCTION update_channel_mapping_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_channel_mapping_updated
BEFORE UPDATE ON public.channel_mapping
FOR EACH ROW
EXECUTE FUNCTION update_channel_mapping_timestamp();
ALTER TABLE public.channel_mapping ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Service role full access"
ON public.channel_mapping FOR ALL
USING (true) WITH CHECK (true);
Таблица sync_message_log (идемпотентность + аудит):
CREATE TABLE public.sync_message_log (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
message_id TEXT NOT NULL,
source TEXT NOT NULL, -- 'nextbot', 'chatwoot', 'telegram'
direction TEXT NOT NULL, -- 'nb_to_cw', 'cw_to_nb', 'cw_to_tg'
channel_mapping_id UUID REFERENCES public.channel_mapping(id),
content_preview TEXT,
message_type TEXT,
status TEXT DEFAULT 'sent',
error_message TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE UNIQUE INDEX idx_sml_message_unique
ON public.sync_message_log(message_id, direction);
CREATE INDEX idx_sml_created_at
ON public.sync_message_log(created_at);
ALTER TABLE public.sync_message_log ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Service role full access"
ON public.sync_message_log FOR ALL
USING (true) WITH CHECK (true);
Чеклист:
- Таблица
channel_mappingсоздана - Таблица
sync_message_logсоздана - Индексы на месте
- Триггер
updated_atработает - INSERT/SELECT через service_role key работает
- CHECK CONSTRAINT
chk_eastpay_project— чужой project_id отклоняется
T2: Chatwoot — Создать API Inbox
Приоритет: 🔴 Blocker | Зависимости: нет | Статус: ✅ Выполнено
curl -X POST "https://chatcrm.eastpay.online/api/v1/accounts/3/inboxes" \
-H "api_access_token: FksZYfbqjb9RXmiEgAQqQ6Ev" \
-H "Content-Type: application/json" \
-d '{
"name": "EastPay Telegram AI",
"channel": {
"type": "api",
"webhook_url": "https://n8n.flowzzy.com/webhook/chatwoot-operator-reply"
}
}'
Чеклист:
- API Inbox создан: ID=3, Type=Channel::Api, Name="EastPay Telegram AI"
-
CHATWOOT_INBOX_ID=3записан в.env -
CHATWOOT_HMAC_TOKENиCHATWOOT_INBOX_IDENTIFIERзаписаны в.env - Менеджеры добавлены в inbox (Collaborators) — через CW UI
T3: Chatwoot — Настроить Global Webhook
Приоритет: 🔴 Blocker | Зависимости: T2 | Статус: ⬜
В Chatwoot UI: Settings → Integrations → Webhooks → Add new webhook
| Поле | Значение |
|---|---|
| URL | https://n8n.flowzzy.com/webhook/chatwoot-events |
| Name | n8n Sync Bridge |
| message_created | ✅ Обязательно |
| conversation_status_changed | ✅ Обязательно |
Чеклист:
- Webhook создан в Chatwoot
- URL корректный
- Выбраны правильные события (
message_created,conversation_status_changed)
T4: Smoke Test — проверка фундамента
Приоритет: 🟡 Required | Зависимости: T1, T2, T3 | Статус: ✅ Выполнено (частично, кроме T3)
Чеклист:
- Supabase: INSERT + SELECT + UPDATE trigger — всё работает
- Chatwoot API: inbox list подтверждает ID=3 Channel::Api
- NextBot Webhook: будет протестирован при создании Workflow 1
- Telegram Bot API:
getMe→east_pay_bot(EastPay ChatAgent) - Тестовые данные удалены (smoke test row deleted)
Phase 2: NextBot → Chatwoot (Forward Sync)
T5: n8n — Workflow 1 «NB→CW Message Sync»
Приоритет: 🔴 Critical | Зависимости: T4 | Статус: ✅ Выполнено (n8n ID: qWlkQJiTqU1IzJQx)
Главный workflow. Без него менеджеры не видят диалоги.
Архитектура:
[Webhook Trigger] → [Switch by event] → [Supabase: Find mapping]
│
┌────────┴────────┐
Found Not Found
│ │
│ [CW: Search/Create Contact]
│ │
│ [CW: Create Conversation]
│ │
│ [Supabase: Save mapping]
│ │
├─────────────────┘
│
[Idempotency Check]
│
[CW: Send Message]
│
[Log to Supabase]
│
[Done ✅]
Спецификация нод:
| # | Нода | Type | Ключевые параметры |
|---|---|---|---|
| 1 | Webhook Trigger | webhook | Path: nextbot-message-sync, POST, Response: 200 |
| 2 | Switch: Event | switch | Field: event → client_message / agent_message |
| 3 | Supabase: Find | supabase | channel_mapping WHERE nextbot_dialog_id = dialog_id |
| 4 | IF: Exists | if | Check mapping found |
| 5a | CW: Search Contact | httpRequest | GET /contacts/search?q=tg_{{user_id}} |
| 5b | CW: Create Contact | httpRequest | POST /contacts (inbox_id, identifier, name, phone) |
| 6 | CW: Create Conversation | httpRequest | POST /conversations (с source_id!) |
| 7 | Supabase: Save Mapping | supabase | INSERT channel_mapping |
| 8 | Idempotency Check | supabase | SELECT sync_message_log WHERE message_id + direction |
| 9 | CW: Send Message | httpRequest | POST /conversations/{id}/messages |
| 10 | Log | supabase | INSERT sync_message_log |
Payload от NextBot (webhook из сценария):
{
"event": "client_message",
"dialog_id": 10177062,
"text": "Хочу обменять 5000 USDT",
"user_id": "tg_123456789",
"user_name": "Иван Петров",
"user_phone": "+79001234567",
"telegram_username": "ivan_petrov",
"timestamp": "2026-03-05T14:05:00Z",
"message_id": "uuid-from-nextbot"
}
Маппинг event → CW message_type:
| NextBot event | CW message_type | CW private |
|---|---|---|
client_message |
incoming (0) |
false |
agent_message |
outgoing (1) |
false |
function_executed |
outgoing (1) |
true |
Чеклист:
- Workflow создан в n8n (ID: qWlkQJiTqU1IzJQx, 13 нод)
- Webhook URL активен: https://n8n.flowzzy.com/webhook/nextbot-message-sync
- Первый запрос → Contact + Conversation + Message в CW
- Повторный запрос от того же клиента → существующий маппинг
- Сообщения видны в Chatwoot UI (incoming/outgoing)
-
sync_message_logзаписи создаются - Дубли не проходят (idempotency)
- Ошибки логируются, workflow не падает
- Workflow активирован и опубликован
T6: NextBot — Сценарии синхронизации (5 штук)
Приоритет: 🔴 Critical | Зависимости: T5 | Статус: ⬜
⚠️ Все действия СТРОГО в проекте «EastPay» (ID:
276a96dd-9dea-41f6-8828-3666a84d85c1)
📖 Полная инструкция:docs/ai-agent/nextbot-chatwoot-scenarios.md
Где: NextBot → (проект EastPay) → Scenarios → Create
Все сценарии используют действие Custom API → POST → https://n8n.flowzzy.com/webhook/nextbot-message-sync
| # | Сценарий | Триггер (Condition) | Event в JSON | Обязательный |
|---|---|---|---|---|
| 1 | CW Sync: Старт диалога |
Dialog start | dialog_start |
✅ да |
| 2 | CW Sync: Входящее от клиента |
New client message | client_message |
✅ да |
| 3 | CW Sync: Ответ агента |
New agent message | agent_message |
✅ да |
| 4 | CW Sync: Ошибка отправки |
Message send error | send_error |
✅ да |
| 5 | CW Sync: Менеджер в NextBot |
Manager message | manager_message |
⚠️ опц. |
Сценарий 5 опционален — может вызвать петлю. Создавать только после тестирования 1-4.
Пример JSON Body для Custom API (Сценарий 2):
{
"event": "client_message",
"dialog_id": "{{dialog_id}}",
"text": "{{message_text}}",
"user_id": "tg_{{user_id}}",
"user_name": "{{user_name}}",
"user_phone": "{{user_phone}}",
"telegram_username": "{{telegram_username}}",
"timestamp": "{{current_datetime}}",
"message_id": "client_{{dialog_id}}_{{timestamp}}"
}
⚠️ Имена переменных (
{{dialog_id}},{{message_text}}и т.д.) — проверьте в NextBot UI (автокомплит в поле Body).
Чеклист:
- Сценарий 1 (Dialog start) создан в NextBot
- Сценарий 2 (New client message) создан в NextBot
- Сценарий 3 (New agent message) создан в NextBot
- Сценарий 4 (Message send error) создан в NextBot
- (Опц.) Сценарий 5 (Manager message) создан
- Тест: клиент пишет боту → execution в n8n появляется
- Тест: бот отвечает → execution в n8n появляется
- Payload содержит все необходимые поля
- Нет дублей событий (один сценарий = один хук)
T7: Integration Test — NextBot → Chatwoot
Приоритет: 🟡 Required | Зависимости: T5, T6 | Статус: ⬜
- Новый клиент пишет в TG → Contact + Conversation в CW, incoming message
- ИИ отвечает → outgoing message в CW
- Повторное сообщение → та же Conversation (не новая)
-
channel_mappingв Supabase — запись создана, все поля ОК -
sync_message_log— записи без дублей
Phase 3: Chatwoot → NextBot (Reverse Sync)
T8: n8n — Workflow 2 «CW→NB Operator Reply»
Приоритет: 🔴 Critical | Зависимости: T7 | Статус: ⬜
⚠️ АРХИТЕКТУРНОЕ ПРАВИЛО: НИКОГДА не слать в Telegram напрямую!
Все сообщения клиенту ОБЯЗАНЫ идти через NextBot webhook (forwarded_output).
Иначе AI-агент не видит историю, автозасыпание не работает, контекст теряется.
Архитектура:
[Webhook Trigger] → [Switch: event type]
│
┌───────────┴───────────┐
message_created conversation_status_changed
│ │
[FILTER: sender.type [IF: resolved → deactivate]
=== "user" && [IF: open → reactivate]
!private &&
message_type === 1]
│
┌─────┴─────┐
PASS REJECT → STOP
│
[Supabase: Get mapping]
│
┌─────┴─────┐
│ │
Found Not Found → Log Error
│
├── [NextBot Webhook: forwarded_output] ── клиент получает в TG
│ ↑ NextBot сам отправит через бот, запишет в историю,
│ AI увидит контекст, автозасыпание сработает
│
└── [Log to sync_message_log]
⚠️ ФИЛЬТР — самая важная защита от петель!
sender.type === "user"→ только от реальных менеджеровprivate === false→ не пересылать private notesmessage_type === 1→ только outgoing (ответы менеджера)Ошибка = бесконечная петля = DDoS на все системы!
🔑 Почему ТОЛЬКО через NextBot, а не через Telegram API:
- NextBot записывает сообщение в историю → AI-агент видит полный контекст
- Автозасыпание AI-агента срабатывает (он знает, что менеджер пишет)
- Сценарии и функции NextBot продолжают работать корректно
- Один токен бота — один хозяин (NextBot), мы не вмешиваемся
Спецификация нод:
| # | Нода | Ключевые параметры |
|---|---|---|
| 1 | Webhook Trigger | Path: chatwoot-events, POST |
| 2 | Switch: Event | event: message_created → A, conversation_status_changed → B |
| 3 | FILTER | sender.type === "user" AND private === false AND message_type === 1 |
| 4 | Supabase: Get Mapping | WHERE chatwoot_conversation_id = conversation.id |
| 5 | NextBot: forwarded_output | POST webhook_url, body: {dialog_id, text, message_type: "forwarded_output"} |
| 6 | Log | INSERT sync_message_log |
Flow B — Conversation Status:
| CW Status | Действие |
|---|---|
resolved |
UPDATE channel_mapping SET is_active = false |
open (reopen) |
UPDATE channel_mapping SET is_active = true |
Чеклист:
- Workflow создан в n8n
- Менеджер пишет в CW → клиент получает в TG
- NextBot получает notification (контекст обновлён)
- Сообщения от бота НЕ пересылаются (фильтр ОК)
- Private notes НЕ пересылаются
- Resolve → маппинг деактивирован
- Workflow активирован
T9: NextBot — Настройки оператора + Обратный поток
Приоритет: 🟡 Required | Зависимости: T7 | Статус: ⬜
⚠️ Проект EastPay (ID:
276a96dd-9dea-41f6-8828-3666a84d85c1)
📖 Полная инструкция:docs/ai-agent/nextbot-chatwoot-scenarios.md→ раздел «Настройки оператора»
Часть A: Настройки Agent Settings
Где: NextBot → Проект EastPay → Agent Settings → Управление активностью
| Настройка | Значение | Почему |
|---|---|---|
| Автоматическое засыпание | ✅ Вкл | ИИ замолкает при вмешательстве менеджера |
| Игнор первого сообщения менеджера | ❌ Выкл | Каждое сообщение = интенция взять контроль |
| Авто-возобновление | 30 минут | Менеджер ушёл → ИИ продолжит |
| Буфер сообщений | 10 сек | Собирает короткие реплики клиента в один запрос |
Часть B: Обратный поток (Chatwoot → NextBot)
Когда менеджер пишет из Chatwoot, n8n WF-2 отправляет сообщение в NextBot через webhook:
POST https://app.nextbot.kz/api/webhooks/v1/276a96dd-.../41a585...
{
"dialog_id": <из channel_mapping>,
"text": "<текст менеджера>",
"message_type": "forwarded_output"
}
forwarded_output= сообщение доставляется клиенту в Telegramnotification= только добавляется в контекст (клиент не видит)input= имитация сообщения от клиента (не видно клиенту)
Чеклист:
- Автозасыпание включено
- Авто-возобновление: 30 мин
- Тест: менеджер пишет в CW → ИИ засыпает
- Тест: менеджер молчит 30 мин → ИИ просыпается
- NextBot webhook от CW → клиент получает в TG
T10: Integration Test — Chatwoot → NextBot + TG
Приоритет: 🟡 Required | Зависимости: T8, T9 | Статус: ⬜
- Менеджер пишет в CW → клиент получает в TG
- После ответа менеджера → ИИ НЕ отвечает (спит)
- Менеджер молчит 30 мин → ИИ просыпается
- Менеджер Resolve →
channel_mapping.is_active = false -
sync_message_log→ directioncw_to_tg - Private note → НЕ отправляется клиенту
Phase 4: Hardening & Production
T11: E2E Full Cycle Test
Приоритет: 🔴 Critical | Зависимости: T10 | Статус: ⬜
Полный сценарий шаг за шагом:
- 1. 👤 Клиент → TG: «Хочу обменять USDT» → ✅ incoming в CW
- 2. 🤖 ИИ отвечает: «Какой объём?» → ✅ outgoing в CW
- 3. 👤 Клиент: «5000 USDT в THB» → ✅ incoming в CW
- 4. 🤖 ИИ вызывает функцию → ✅ private note (опц.)
- 5. 🤖 ИИ: «Курс 34.5, итого 172,500 THB» → ✅ outgoing в CW
- 6. 👨💼 Менеджер в CW: «Подтверждаю курс» → ✅ клиент в TG + ИИ спит
- 7. 👤 Клиент: «Когда подъехать?» → ✅ incoming в CW, ИИ молчит
- 8. 👨💼 Менеджер: «Офис на Сукумвит» → ✅ клиент в TG
- 9. 👨💼 Resolve → ✅ маппинг деактивирован
- 10. Клиент снова пишет → ✅ ИИ проснулся, новая/reopen conversation
Нагрузочный тест:
- 10 сообщений за 10 секунд → все дошли без дублей
-
sync_message_log— ровно 10 записей
T12: Production Checklist
Приоритет: 🔴 Critical | Зависимости: T11 | Статус: ⬜
- Все n8n workflows ACTIVE
- n8n Error Workflow настроен (уведомление в TG при ошибке)
- Retry policy: 3 попытки, backoff 1→2→4 сек
-
.envобновлён сCHATWOOT_INBOX_ID -
channel_mapping— тестовые данные удалены -
sync_message_log— тестовые записи удалены - NextBot сценарии активны (проект EastPay!)
- NextBot оператор-контроль настроен
- Chatwoot менеджеры в API Inbox
- Документация обновлена
📊 Risk Register
| Риск | Влияние | Митигация |
|---|---|---|
| Петля сообщений | 🔴 | 5 фильтров + message_type: notification |
| Потеря сообщений при сбое n8n | 🔴 | Retry 3x + Error Workflow + sync_message_log |
| Чужие dialog_id из другого проекта | 🔴 | CHECK CONSTRAINT в БД + nextbot_project_id |
| Chatwoot API rate limit | 🟡 | Wait node + экспоненциальный backoff |
| NextBot webhook медленный | 🟢 | Async отправка, timeout 10 сек |
| Клиент удалил chat | 🟡 | Try-catch на sendMessage |
| Несколько менеджеров одновременно | 🟢 | CW Conversation Assignee |
🔐 Учётные данные
| Параметр | Значение | Статус |
|---|---|---|
CHATWOOT_BASE_URL |
https://chatcrm.eastpay.online |
✅ |
CHATWOOT_ACCOUNT_ID |
3 |
✅ |
CHATWOOT_API_TOKEN |
FksZY... (в .env) |
✅ |
CHATWOOT_INBOX_ID |
3 |
✅ |
NEXTBOT_PROJECT_NAME |
EastPay |
✅ |
NEXTBOT_PROJECT_ID |
276a96dd-9dea-41f6-8828-3666a84d85c1 |
✅ |
NEXTBOT_WEBHOOK_URL |
https://app.nextbot.kz/api/webhooks/v1/276a96dd.../41a585... |
✅ |
TELEGRAM_BOT_TOKEN |
8385176510:AAEF6... (в .env) |
✅ |
N8N_WEBHOOK_BASE |
https://n8n.flowzzy.com |
✅ |
SUPABASE_URL |
https://cvzsgjksswowqgfxvrsb.supabase.co |
✅ |
Критический путь: T1 → T4 → T5 → T7 → T8 → T10 → T11 → T12
Параллельно: T1+T2 | T5+T6 | T8+T9
NextBot Project: EastPay (ID:276a96dd-9dea-41f6-8828-3666a84d85c1)
Обновлено: 2026-03-05