Зачем канал
Канал-источник — труба: сайт, CRM, почтовый шлюз, свой коннектор. В панели и в API аналитика режется по источникам: сколько пришло с виджета, сколько из магазина, сколько из почты или подключённого Telegram-аккаунта. Канал не равен пользователю: один источник может содержать тысячи независимых тредов.
Источник и тред
| Роль | Ключ | Зачем |
|---|---|---|
| Реестр / аналитика | custom:shop | Одна запись на источник. Счётчики суммируют все треды. |
| Лента, панель, ИИ, ответ | custom:shop:{uuid} | Диалог 1:1. Ответ оператора пишет в этот ключ. |
| Почта / Slack / Discord | email:support, тред email:support:{uuid} | Тот же принцип: источник + тред |
| Виджет на сайте | widget:{uuid} | Исключение: тред без третьей части. Реестр — widget:web |
Telegram и другие коннекторы
Telegram-бот и подключённый Telegram-аккаунт — разные поверхности. Бот может быть каналом проекта, а личные сообщения аккаунта попадают в специальный inbox-источник. В обоих случаях идентификатор пользователя и его аватар обрабатываются через авторизованный слой; не подставляйте Telegram file URL напрямую в публичный HTML.
Как писать
const SOURCE = "custom:shop";
await client.channels.create({
slug: "shop",
namespace: "custom",
display_name: "Магазин",
});
const accepted = await client.ingest.send({
channel: SOURCE,
body: text,
identity: { external_id: customer.id },
});
// accepted.channel — тред. Им же отвечайте:
await client.conversations.reply(accepted.channel!, { body: reply });Без identity.external_id и thread_id приём входящих пишет в общую ленту источника — как раньше. Для панели и ИИ всегда передавайте внешний id.
Ответ из панели уходит интегратору с ключом треда и external_id. В панели оператор пишет в том же композере, что и для виджета. Доставку человеку на custom-канале по-прежнему делаете вы: inbox.onAgent((m) => deliver(m.externalId, m.body)). Тело на WS — plaintext (L1 at-rest раскрывается на сервере). Если externalId пустой — не молчите, залогируйте кадр.