Разговоры, сообщения и вложения

Список разговоров, карточка и закрытие; переписка, ответ посетителю с файлом и разметкой, идемпотентность; вложения по ключу и подписанные ссылки.

Список разговоров #

GET /api/v1/conversations conversations:read
ПолеТипОбяз.Описание
statusstringнетopen, pending, resolved или closed
sinceRFC 3339неттолько изменённые после этого времени
cursorRFC 3339нетпродолжение предыдущей страницы
limitintнет1…100, по умолчанию 25
Пример вызова
curl -s "https://api.widgetchat.ru/api/v1/conversations?status=open&since=2026-08-22T00:00:00Z" -H "Authorization: Bearer wck_…"
Ответ
{"data": [ /* объекты conversation */ ], "nextCursor": "…", "hasMore": true}

Один разговор #

GET /api/v1/conversations/{id} conversations:read

Номер разговора имеет вид UUID. Разговор чужого сайта отвечает 404, а не 403: существование чужих номеров тоже сведения.

Объект conversation #

ПолеТипОбяз.Описание
iduuidданомер разговора
statusstringдаopen, pending, resolved, closed
channelstringдавсегда app: обращение приходит из виджета
visitor.idstringдапостоянный номер посетителя
visitor.namestringнетимя, если сайт его передал
visitor.emailstringнетпочта, если сайт её передал
visitor.organizationstringнеторганизация из подписанного токена
visitor.accountIdstringнетваш номер клиента из подписанного токена
visitor.rolestringнетроль посетителя из подписанного токена
sourcestringнетоткуда заведено обращение: widget у окна чата на сайте. Адрес страницы, с которой написал посетитель, виджет не передаёт
createdAtRFC 3339дакогда заведено
lastMessageAtRFC 3339|nullнетвремя последнего сообщения
lastReadAtRFC 3339|nullнетдо какого момента посетитель прочитал

В visitor попадают только сведения, которые сайт сам передал подписанным токеном. Служебные пометки разговора (номер темы Telegram, чат открытой линии) наружу не отдаются.

Закрыть разговор #

POST /api/v1/conversations/{id}/close conversations:write

Закрытый разговор перестаёт принимать ответы: следующее сообщение посетителя заведёт новое обращение. Повторное закрытие ошибкой не считается и события не порождает.

Ответ
{"data": {"id": "6c82f8e2-…", "status": "closed"}}

Переписка разговора #

GET /api/v1/conversations/{id}/messages conversations:read
ПолеТипОбяз.Описание
cursorRFC 3339нетпродолжение предыдущей страницы
limitintнет1…100, по умолчанию 25
Ответ
{"data": [{
  "id": "7735af47-2e36-45fd-9543-83f67b6d7eef",
  "direction": "outgoing",
  "senderType": "admin",
  "content": "Заказ уже в пути",
  "entities": [{"type": "bold", "offset": 0, "length": 5}],
  "messageType": "text",
  "origin": "api",
  "createdAt": "2026-08-22T09:15:04.281Z",
  "editedAt": null,
  "attachments": []
}], "hasMore": false}

Объект message #

ПолеТипОбяз.Описание
iduuidданомер сообщения
directionstringдаincoming пишет посетитель, outgoing пишет поддержка
senderTypestringдаcustomer, admin, system
contentstringдатекст
entities[]objectнетразметка текста отрезками, см. раздел "Форматирование текста"; пустой список у обычного текста
messageTypestringдаtext, image, document, audio, video
originstringдаоткуда написано: widget, telegram, api, bitrix24, amocrm, retailcrm, unknown
createdAtRFC 3339дакогда записано
editedAtRFC 3339|nullнеткогда исправлено
attachments[]objectнетвложения, см. раздел "Вложения" ниже

🔴 origin нужен, чтобы не зациклиться: получив событие о собственном ответе, ваша система не должна отправить его второй раз.

Ответить посетителю #

POST /api/v1/conversations/{id}/messages conversations:write
ПолеТипОбяз.Описание
contentstringнеттекст до 4096 символов; обязателен, если нет file
entities[]objectнетразметка текста: жирный, наклонный, подчёркнутый, зачёркнутый, моноширинный, цитата и ссылка с подписью (см. раздел ниже)
file.urlstringнетссылка на файл: мы скачаем его сами, до 50 МБ
file.namestringнетимя файла для переписки
file.typestringнетimage, document, audio, video
file_url, file_name, file_typestringнетто же самое плоскими полями: для конструкторов связок, которые не умеют вложенные объекты
Запрос
POST /api/v1/conversations/6c82f8e2-…/messages
Idempotency-Key: reply-4417
Content-Type: application/json

{"content": "Заказ уже в пути",
 "file": {"url": "https://example.ru/act.pdf", "name": "Акт.pdf", "type": "document"}}
Ответ
201 {"data": {"id": "7735af47-…", "content": "Заказ уже в пути",
                "createdAt": "2026-08-22T09:15:04.281Z", "direction": "outgoing", "status": "queued"}}
200 {"data": {"id": "7735af47-…", "duplicate": true}}

Файл забирается до записи сообщения: недоступная ссылка не оставит в переписке пустой ответ. Запросы к вашему адресу идут мимо прокси и не во внутреннюю сеть. Вложение можно прислать и плоскими полями file_url, file_name, file_type, так удобнее конструкторам связок; вложенный объект file главнее. Имя или тип файла без ссылки дают отказ validation_error, а не молчаливую отправку одного текста: иначе связка считала бы, что вложение ушло.

Форматирование текста #

В окне чата текст показывается размеченным: жирный, наклонный, подчёркнутый, зачёркнутый, моноширинный, цитата и ссылка, спрятанная под короткой подписью вместо длинного адреса.

Оператору для этого ничего изучать не нужно: он форматирует ответ обычными средствами Telegram (выделить текст → "Жирный", "Ссылка" и так далее), а посетитель видит то же самое. Через API разметка задаётся полем entities.

Разметка передаётся отрезками, а не тегами: HTML в переписке мы не принимаем и не отдаём. Отрезок задаётся так: с такого-то знака столько-то знаков такой-то вид. Смещения считаются в единицах UTF-16, так же как их считают Telegram Bot API и строки в браузере: эмодзи занимает два знака, буква кириллицы один.

ПолеТипОбяз.Описание
typestringдаbold, italic, underline, strikethrough, code, pre, blockquote, text_link
offsetintданачало отрезка от начала текста, в единицах UTF-16
lengthintдадлина отрезка в тех же единицах
urlstringнетадрес для type: text_link; допустимы схемы http, https, mailto, tel
languagestringнетязык блока кода для type: pre
Запрос
POST /api/v1/conversations/6c82f8e2-…/messages
Content-Type: application/json

{"content": "Заказ уже в пути, отследить можно тут",
 "entities": [
   {"type": "bold", "offset": 0, "length": 5},
   {"type": "text_link", "offset": 30, "length": 3, "url": "https://example.ru/track/4417"}
 ]}

Отрезки, которые нельзя показать, отбрасываются, а текст остаётся целым: неизвестный тип, выход за пределы текста и ссылка с чужой схемой (javascript:, data:) молча превращаются в обычный текст. Пересекаться отрезки не должны: вкладывайте их друг в друга, как это делает Telegram: ссылка внутри жирного, а не наполовину.

Idempotency-Key #

Заголовок делает повтор безопасным: связь может оборваться после того, как запрос уже приняли. Повторная отправка с тем же ключом вернёт 200 и duplicate: true вместо второго сообщения.

Допустимы до 128 символов из A-Z a-z 0-9 . _ : - Ключ действует в пределах вашего ключа доступа: два разных ключа доступа с одинаковым Idempotency-Key друг другу не мешают.

Что означает status: queued #

Ответ принят в доставку и уйдёт в окно посетителя той же очередью, что и ответ оператора из Telegram. Отдельного признака "доставлено" у API нет: событие message.created придёт вам на общих основаниях, а видимость ответа посетителем отражает поле lastReadAt разговора.

Вложения #

ПолеТипОбяз.Описание
iduuidданомер файла
typestringдаimage, document, audio, video
namestringнетимя файла
sizeintнетразмер в байтах
mimeTypestringнеттип содержимого
urlstringнетподписанная ссылка со сроком
urlExpiresAtRFC 3339неткогда ссылка перестанет работать

Подписанная ссылка #

GET /media/signed/{id}?exp=…&sig=…

Ссылка не требует ключа: её открывает чужой сервер, у которого нашего входа нет. Её защищают подпись и срок в час. Раздавать переписку по угадываемому адресу нельзя, поэтому открытых ссылок на вложения не существует.

Скачать по ключу #

GET /api/v1/media/{id} media:read

Отдаёт сам файл с его типом содержимого. Годится, когда подписанная ссылка просрочилась или когда файл забирает ваш серверный код.

Пример вызова
curl -s -o act.pdf https://api.widgetchat.ru/api/v1/media/9f2c… -H "Authorization: Bearer wck_…"