Files
aiform_prod/docs/SUPPORT_N8N_WEBHOOK.md

9.8 KiB
Raw Blame History

Поддержка: webhook n8n, диалог (треды), лимиты вложений

Функционал «Поддержка» реализован как диалог: треды и сообщения хранятся в БД. Таблицы с префиксом clpr_: clpr_support_threads, clpr_support_messages. Исходящие сообщения пользователя проксируются в n8n; входящие ответы оператора приходят в backend через webhook POST /api/v1/support/incoming (из n8n при ответе в CRM).

Подключение к PostgreSQL: креды берутся из .envPOSTGRES_HOST, POSTGRES_PORT, POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD.

Переменные окружения

В .env задаются:

Переменная Описание
N8N_SUPPORT_WEBHOOK URL webhook n8n (multipart). Обязателен.
SUPPORT_ATTACHMENTS_MAX_COUNT Макс. количество файлов (0 = без ограничений).
SUPPORT_ATTACHMENTS_MAX_SIZE_MB Макс. размер одного файла в МБ (0 = без ограничений).
SUPPORT_ATTACHMENTS_ALLOWED_TYPES Допустимые типы (пусто = любые).
SUPPORT_INCOMING_SECRET Секрет для POST /api/v1/support/incoming (заголовок X-Support-Incoming-Secret или query secret). Если задан — только n8n с этим секретом может слать ответы в тред.

Значение 0 или пустая строка для лимитов означает «без ограничений».

Формат запроса от backend к n8n

Backend отправляет на N8N_SUPPORT_WEBHOOK POST multipart/form-data:

  • Поля: message, subject, claim_id, source, unified_id, phone, email, session_id, timestamp, thread_id (UUID треда), ticket_id (если тред уже привязан к тикету в CRM).
  • Файлы: attachments[0], … или attachments.

Ответ n8n может содержать ticket_id — backend сохранит его в clpr_support_threads для последующих сообщений и для входящего webhook.

API backend

  • POST /api/v1/support — multipart: message, subject?, claim_id?, source, thread_id?, session_token (или channel+channel_user_id), файлы. Создаёт/находит тред по (unified_id, claim_id), записывает сообщение (user), проксирует в n8n. Ответ: { "success": true, "thread_id": "...", "message_id": "..." }.
  • GET /api/v1/support/threads — список всех тредов пользователя. В каждом элементе есть unread_count (число непрочитанных сообщений от поддержки). Ответ: { "threads": [{ "thread_id", "claim_id" | null, "source", "ticket_id", "created_at", "updated_at", "last_body", "last_at", "messages_count", "unread_count" }] }.
  • GET /api/v1/support/unread-count — суммарное число непрочитанных по всем тредам (для бейджа в баре). Ответ: { "unread_count": number }.
  • POST /api/v1/support/read — отметить тред как прочитанный (пользователь открыл чат). Query или body: thread_id или claim_id. Обновляет clpr_support_reads.
  • GET /api/v1/support/thread — query: claim_id?, session_token (или channel + channel_user_id). Возвращает один тред и сообщения: { "thread_id": "...", "messages": [...], "ticket_id": "..." }. Если треда нет — thread_id: null, messages: [].
  • POST /api/v1/support/incoming — для n8n: добавить сообщение от поддержки в тред. Тело JSON: { "thread_id" или "ticket_id", "body", "attachments?": [] }. Заголовок X-Support-Incoming-Secret или query secret должен совпадать с SUPPORT_INCOMING_SECRET (если задан). По ticket_id backend находит thread_id и вставляет сообщение с direction=support.
  • GET /api/v1/support/limits — лимиты вложений из env.
  • GET /api/v1/support/stream — SSE: один поток на пользователя (query session_token или channel + channel_user_id). Новые сообщения от поддержки приходят в реальном времени через Postgres NOTIFY (триггер на clpr_support_messages). События: connected, support_message (в теле — thread_id, message: id, direction, body, attachments, created_at).

Доставка в реальном времени (Postgres NOTIFY)

При INSERT в clpr_support_messages срабатывает триггер, который делает NOTIFY support_events с payload (unified_id, thread_id, сообщение). Backend при старте подписывается на канал support_events одним LISTEN-соединением и раскидывает события по реестру стримов (unified_id → очереди SSE).

Прочитано/непрочитано: таблица clpr_support_reads (unified_id, thread_id, last_read_at). Пользователь «прочитал» тред, когда открывает чат — фронт вызывает POST /read. Непрочитанные = сообщения от support с created_at > last_read_at. По этим данным можно в n8n/CRM строить сценарии напоминаний (push, повторная отправка), если пользователь долго не читает.

Миграции (таблицы с префиксом clpr_): 003 — треды и сообщения; 004 — триггер NOTIFY; 005_support_reads.sql — отметки прочтения. Применять к БД вручную. Креды Postgres — из .env:

# из корня aiform_prod, креды из .env
export $(grep -E '^POSTGRES_' .env | xargs)
psql -h "$POSTGRES_HOST" -p "$POSTGRES_PORT" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f backend/db/migrations/003_support_threads_messages.sql
psql -h "$POSTGRES_HOST" -p "$POSTGRES_PORT" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f backend/db/migrations/004_support_notify_trigger.sql
psql -h "$POSTGRES_HOST" -p "$POSTGRES_PORT" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f backend/db/migrations/005_support_reads.sql

Если в БД уже есть таблицы без префикса (support_threads, support_messages), их нужно переименовать в clpr_support_threads и clpr_support_messages перед применением 004, либо пересоздать схему (миграция 003 с префиксом создаёт таблицы с IF NOT EXISTS).

n8n

  1. Webhook приёма обращений — multipart, при первом сообщении создаёт тикет в CRM, в ответе возвращает ticket_id. При последующих (есть thread_id/ticket_id) — добавляет комментарий к тикету.
  2. Вызов нашего incoming — когда оператор ответил в CRM, workflow n8n должен вызвать POST https://.../api/v1/support/incoming с заголовком X-Support-Incoming-Secret: <SUPPORT_INCOMING_SECRET> и телом { "thread_id": "..." или "ticket_id": "...", "body": "текст ответа" }, чтобы сообщение появилось в чате мини-аппа.

Как тестировать SSE (ответы в реальном времени)

  1. В мини-аппе: зайти в поддержку (бар → «Поддержка» или страница /support), авторизоваться, отправить первое сообщение (или открыть уже существующий тред). Оставить чат открытым.

  2. Узнать thread_id: в DevTools → Network найти запрос GET .../api/v1/support/thread и в ответе скопировать thread_id, либо после отправки сообщения — ответ POST .../api/v1/support содержит thread_id.

  3. Имитация ответа поддержки: вызвать incoming (как будет делать n8n):

    # Подставить THREAD_ID и секрет из .env (SUPPORT_INCOMING_SECRET). Если секрет пустой — заголовок можно не передавать.
    curl -s -X POST 'https://miniapp.clientright.ru/api/v1/support/incoming' \
      -H 'Content-Type: application/json' \
      -H 'X-Support-Incoming-Secret: ВАШ_SUPPORT_INCOMING_SECRET' \
      -d '{"thread_id":"THREAD_ID","body":"Тестовый ответ от поддержки"}'
    
  4. Ожидание: в открытом чате в мини-аппе в течение 12 секунд должно появиться новое сообщение без перезагрузки и без повторного запроса (доставка по SSE). Если сообщение появляется только после обновления страницы — проверить, что фронт пересобран с SSE (docker compose build frontend && docker compose up -d frontend) и что в Network есть запрос к /api/v1/support/stream со статусом pending (длинное соединение).