Актуальность: 15 октября 2026 года. Описана реализованная версия, а не план развития.
Для настройки без программирования используйте пользовательское руководство.
Содержание
- Два направления запросов
- Быстрый старт API
- Объект endpoint
- Методы API
- Объект доставки
- События и исходящий JSON
- Настройка полей
- Заголовки исходящего запроса
- Проверка подписи
- Повторы и гарантии
- Ошибки API
- Отличия MCP
Два направления запросов
| Направление | Кто вызывает | Адрес | Назначение |
|---|---|---|---|
| Локальный API управления | Ваш локальный скрипт → Deskgram | http://127.0.0.1:5000/api/webhooks | Создать endpoint, изменить настройки, посмотреть журнал |
| Исходящий вебхук | Deskgram → ваш получатель | URL из настроек endpoint | Передать событие POST-запросом с JSON |
127.0.0.1:5000 — пример адреса локального backend. Используйте адрес и порт своей установки. Это не API облачного сервера лицензий и не MCP-сервер.
Граница доступа: у описанных REST-маршрутов нет отдельной проверки Bearer-токена. Они предназначены для доверенного локального окружения. Не открывайте порт backend в интернет ради вебхуков. Исходящая доставка не требует входящего доступа к компьютеру с Deskgram.
Локальные ответы возвращаются в JSON. Для POST/PUT с настройками передавайте Content-Type: application/json. Если перед вашим backend установлен собственный шлюз авторизации, его правила настраиваются отдельно.
Быстрый старт API
Ниже команды curl для shell с поддержкой одинарных кавычек; в PowerShell используйте curl.exe и учтите правила передачи JSON вашей версии оболочки. Все адреса, ID и секреты в примерах вымышлены.
- Создайте выключенный endpoint, чтобы сначала проверить настройки:
curl -X POST 'http://127.0.0.1:5000/api/webhooks/endpoints' \ -H 'Content-Type: application/json' \ --data '{"name":"CRM: рассылка","url":"https://receiver.example.com/deskgram","secret":"YOUR_SHARED_SECRET","events":["message.sent","message.failed","lead.reply_received"],"module_filter":["spam_pm"],"payload_template":"deskgram","enabled":false}'- Из ответа возьмите
endpoint.id. Для условного ID12отправьте тест:
curl -X POST 'http://127.0.0.1:5000/api/webhooks/endpoints/12/test'- Проверьте
success,delivery.responsestatuscode,delivery.response_bodyи фактический прием запроса. Тест не содержит реального лида и может быть отклонен бизнес-валидацией CRM. - Включите endpoint в интерфейсе или передайте полный объект настроек через PUT с
enabled: true. Не отправляйте толькоenabled: PUT не является частичным обновлением. - Выполните соответствующее реальное действие и проверьте журнал:
curl 'http://127.0.0.1:5000/api/webhooks/deliveries?endpoint_id=12&limit=20'Объект endpoint
Поля запроса создания и обновления
Одна модель используется в POST и PUT.
| Поле | Тип | По умолчанию | Правила |
|---|---|---|---|
name | string | Обязательное | 1–120 символов до удаления крайних пробелов; используйте непустое имя |
url | string | Обязательное | 8–2000 символов, начинается с http:// или https://; рекомендуется HTTPS |
method | string | POST | Поддерживается только POST |
secret | string или null | null при создании | Общий секрет; крайние пробелы удаляются. При PUT отсутствие поля сохраняет секрет, null или пустая строка очищает |
headers | object со строковыми значениями | {} | Дополнительные заголовки получателя; не ключи доступа к Deskgram |
events | array of string | ["task.completed"] | Шесть событий из каталога или "*"; пустой список заменяется на task.completed |
module_filter | array of string | [] | Пустой список означает все модули; например ["spam_pm"] |
payload_template | string | deskgram | deskgram, shortresult, crmlead, custom |
payload_mapping | object | {} | Настройка выходных полей; формат ниже |
enabled | boolean | true | Разрешает автоматическую отправку выбранных событий |
timeout_seconds | number | 15 | От 3 до 60 секунд |
Для обычного endpoint timeout применяется к сетевым операциям HTTP-клиента, а не является строгим пределом времени всего API-вызова. Тест и повтор ждут завершения доставки, включая автоматические попытки и паузы.
Имена модулей нормализуются в нижний регистр, - заменяется на _; дубликаты событий и модулей удаляются. "*" означает все поступающие в этот механизм события, но не включает отсутствующие интеграции модулей.
Поля ответа
Endpoint содержит все перечисленные настройки, а также id (integer), hassecret (boolean), createdat и updated_at (дата/время UTC). Поле secret всегда возвращается как null, даже если секрет сохранен.
Важно: значения в headers локальный REST API возвращает без маскирования. Ответ может содержать токены получателя; не публикуйте его в логах или обращениях в поддержку.
Пример ответа создания:
{ "success": true, "endpoint": { "id": 12, "name": "CRM: рассылка", "url": "https://receiver.example.com/deskgram", "method": "POST", "secret": null, "has_secret": true, "headers": {}, "events": ["message.sent", "message.failed", "lead.reply_received"], "module_filter": ["spam_pm"], "payload_template": "deskgram", "payload_mapping": {}, "enabled": false, "timeout_seconds": 15.0, "created_at": "2026-10-15T10:00:00Z", "updated_at": "2026-10-15T10:00:00Z" }}Методы API
Все пути ниже включают /api. Для успешного выполнения используется HTTP 200, в том числе при создании и удалении. endpointid и deliveryid — целочисленные ID соответствующих записей.
GET /api/webhooks/endpoints
Список endpoint-ов от новых к старым по времени создания. Параметров пагинации нет.
Ответ: {"endpoints": [...]}, каждый элемент — объект endpoint. Пустой список: {"endpoints": []}.
POST /api/webhooks/endpoints
Создание endpoint. Тело — модель настроек из раздела выше. Ответ: {"success": true, "endpoint": {...}}.
При пропуске enabled endpoint сразу включен. Для безопасной первичной настройки явно передайте false.
PUT /api/webhooks/endpoints/{endpoint_id}
Обновление endpoint. Требует name, url и остальные актуальные настройки, которые нужно сохранить. Отсутствующие поля сбрасываются к значениям по умолчанию, кроме secret.
Не пересылайте объект ответа целиком без обработки: его secret: null при PUT удалит сохраненный секрет. Для сохранения секрета исключите ключ secret из запроса.
Пример включения существующего endpoint с сохранением секрета:
{ "name": "CRM: рассылка", "url": "https://receiver.example.com/deskgram", "method": "POST", "headers": {}, "events": ["message.sent", "message.failed", "lead.reply_received"], "module_filter": ["spam_pm"], "payload_template": "deskgram", "payload_mapping": {}, "enabled": true, "timeout_seconds": 15}Ответ: {"success": true, "endpoint": {...}}. Несуществующий endpoint: HTTP 404.
DELETE /api/webhooks/endpoints/{endpoint_id}
Удаление endpoint. Тело не требуется. Ответ: {"success": true, "endpoint_id": 12}. Несуществующий endpoint: HTTP 404.
История доставок остается. Удаление не отменяет уже начавшуюся отправку; повтор истории после удаления endpoint невозможен.
POST /api/webhooks/endpoints/{endpoint_id}/test
Отправка тестового события только выбранному endpoint. Тело не требуется; передать произвольный тестовый payload через этот метод нельзя.
Тест работает также для выключенного endpoint, независимо от подписки на события и фильтра модулей. Применяет текущий mapping. Возвращает результат после отправки и автоматических повторов.
Пример ответа при отказе получателя с HTTP 403, для стандартного формата:
{ "success": false, "delivered": 0, "delivery_id": 45, "status": "error", "delivery": { "id": 45, "endpoint_id": 12, "event_type": "webhook.test", "task_id": null, "status": "error", "attempts": 3, "response_status_code": 403, "response_body": "Forbidden", "error": "HTTP 403", "created_at": "2026-10-15T10:01:00Z", "updated_at": "2026-10-15T10:01:07Z", "delivered_at": null, "request_payload": { "schema_version": "1.0", "event": "webhook.test", "occurred_at": "2026-10-15T10:01:00Z", "source": "deskgram", "test": true, "endpoint": {"id": 12, "name": "CRM: рассылка"} }, "source_payload": { "schema_version": "1.0", "event": "webhook.test", "occurred_at": "2026-10-15T10:01:00Z", "source": "deskgram", "test": true, "endpoint": {"id": 12, "name": "CRM: рассылка"} } }}HTTP-статус локального API в этом случае — 200. Код 403 относится к получателю и находится внутри delivery. При успехе: success: true, delivered: 1, status: "success".
Если доставка не создана, delivery_id и delivery могут быть null, а success — false. Несуществующий endpoint дает HTTP 404 локального API.
GET /api/webhooks/deliveries
Журнал от новых к старым по времени создания.
| Query-параметр | Тип | По умолчанию | Значение |
|---|---|---|---|
endpoint_id | integer | Не задан | ID endpoint |
event_type | string | Не задан | Точное имя события, например message.failed |
status | string | Не задан | pending, retrying, success или error |
limit | integer | 100 | От 1 до 500 записей |
Ответ: {"deliveries": [...]}. Каждый элемент — объект доставки без requestpayload и sourcepayload. offset, курсор и общее количество записей этим методом не предоставляются.
GET /api/webhooks/deliveries/{delivery_id}
Детали доставки, включая requestpayload и sourcepayload.
Ответ: {"delivery": {...}}. Несуществующая доставка: HTTP 404.
POST /api/webhooks/deliveries/{delivery_id}/retry
Ручной повтор доставки в статусе error. Тело не требуется. Создает новую запись и ждет завершения ее попыток. Явный повтор разрешен также для выключенного endpoint.
Ответ содержит success (boolean), originaldeliveryid (integer), status (string), delivery (полный объект новой доставки).
У новой доставки другие id и X-Deskgram-Delivery-Id; исходная запись не заменяется. Неуспех получателя возвращается в HTTP 200 с success: false.
HTTP 400: исходная доставка не в статусе error либо ее ручной повтор уже выполняется в этом процессе. HTTP 404: не найдена доставка или ее endpoint.
GET /api/webhooks/events
Каталог шести поддерживаемых событий с подписями для интерфейса:
{ "events": [ {"event": "task.completed", "label": "Задача завершена"}, {"event": "task.failed", "label": "Задача завершилась ошибкой"}, {"event": "task.cancelled", "label": "Задача остановлена"}, {"event": "lead.reply_received", "label": "Получен ответ лида"}, {"event": "message.sent", "label": "Сообщение отправлено"}, {"event": "message.failed", "label": "Ошибка отправки сообщения"} ]}webhook.test отправляется отдельным методом тестирования и не включен в каталог подписок. "*" принимается в настройках, но также не возвращается каталогом.
Объект доставки
| Поле | Тип | Описание |
|---|---|---|
id | integer | ID доставки в локальной базе |
endpoint_id | integer | ID endpoint, в том числе ранее удаленного |
event_type | string | Событие независимо от выходного mapping |
task_id | string или null | ID задачи, если доступен |
status | string | pending, retrying, success или error |
attempts | integer | Число сделанных автоматических попыток |
responsestatuscode | integer или null | Последний HTTP-код; при сетевой ошибке может быть null |
response_body | string или null | Ответ получателя, возможно усеченный |
error | string или null | Диагностическое сообщение, не поле бизнес-ошибки CRM |
createdat, updatedat | string | UTC-даты в ISO 8601 с Z |
delivered_at | string или null | Время успешной доставки |
request_payload | object | JSON после mapping; только в деталях/test/retry |
source_payload | object | Исходное событие до mapping; у старых записей может быть {}; только в деталях/test/retry |
История не является полным HTTP-трассировщиком: заголовки отправленного запроса и все промежуточные ответы отдельных попыток не сохраняются в этом объекте.
События и исходящий JSON
Примеры ниже относятся к payload_template: "deskgram". При mapping внешняя структура меняется; X-Deskgram-Event по-прежнему содержит имя события.
Финальные события задач
| Событие | Значение |
|---|---|
task.completed | Завершение выполнения задачи; не гарантия успешности каждого действия |
task.failed | Финальный статус ошибки задачи |
task.cancelled | Остановка или отмена задачи |
{ "schema_version": "1.0", "event": "task.completed", "occurred_at": "2026-10-15T10:05:00Z", "source": "deskgram", "task": { "task_id": "12345", "task_name": "Тестовая рассылка", "module_name": "spam_pm", "status": "completed", "created_at": "2026-10-15T10:04:00Z", "completed_at": "2026-10-15T10:05:00Z", "accounts_count": 1, "params": {}, "statistics": {}, "result": {} }}params, statistics, result зависят от модуля, общего набора счетчиков для всех задач нет. taskname, modulename, created_at могут быть null. У failed/cancelled меняются событие, статус и доступные результаты, но не семейство структуры.
Живые события рассылки
Сейчас message.sent, message.failed, lead.replyreceived подключены к spampm. Фильтр другого модуля не создает новые источники событий.
Пример отправленного сообщения:
{ "schema_version": "1.0", "event": "message.sent", "occurred_at": "2026-10-15T10:04:20Z", "source": "deskgram", "module": "spam_pm", "task": { "task_id": "12345", "campaign_id": "spam_pm:12345", "module_name": "spam_pm" }, "account": {"id": "987654321"}, "lead": { "telegram_user_id": "123456789", "username": "example_client", "raw_identifier": "@example_client" }, "message": { "message_id": 1001, "message_text": "Здравствуйте!", "raw_identifier": "@example_client", "text": "Здравствуйте!", "status": "sent" }}Состав lead и message зависит от доступных данных. Могут отсутствовать телефон, username, имя, ID или текст. Для mapping текста используйте message.text. Не требуйте message_text: это дополнительное поле конкретного пути отправки, не универсальное поле всех событий.
При message.failed в message передается доступная ошибка error и статус; ID успешно отправленного сообщения может отсутствовать. Пропуски и ошибки разрешения получателя не всегда создают это событие.
При lead.replyreceived в message передаются доступные messageid, text, media_type, datetime, timestamp и status: "received". Событие появляется только когда активный обработчик действительно получил ответ; вебхук не включает круглосуточное прослушивание сам по себе.
message.sent не подтверждает прочтение сообщения адресатом. ID рекомендуется обрабатывать как непрозрачные значения; ID задачи, аккаунта и Telegram-пользователя в этом контракте представлены строками, отдельный message_id может быть числом.
Тестовое событие
Исходное событие содержит schemaversion, event: "webhook.test", occurredat, source, test: true, endpoint: {id, name}. Пример полностью приведен в ответе метода test.
Тест не содержит task, lead, account, message. Mapping, состоящий только из источников лида, даст {}.
Настройка полей
deskgram отправляет полное исходное событие и не применяет payload_mapping. Для ограничения передаваемых данных выберите другой формат.
Для shortresult, crmlead, custom рекомендуемый явный формат mapping:
{ "fields": [ {"target": "event", "source": "event", "enabled": true}, {"target": "contact.telegram_id", "source": "lead.telegram_user_id", "enabled": true}, {"target": "contact.username", "source": "lead.username", "enabled": true}, {"target": "comment", "source": "message.text", "enabled": true}, {"target": "status", "source": ["message.status", "task.status"], "enabled": true} ]}Весь этот объект передается в payload_mapping. target — путь поля у получателя, source — путь в исходном событии Deskgram. Это не значения-константы и не выражения.
Для примера message.sent выше результат:
{ "event": "message.sent", "contact": {"telegram_id": "123456789", "username": "example_client"}, "comment": "Здравствуйте!", "status": "sent"}Правила:
- Точка в
targetсоздает вложенные объекты; не задавайте одновременно конфликтующие путиcontactиcontact.username. sourceчитает ключи вложенных объектов через точку. Индексы массивов и шаблоны{{...}}не поддерживаются.- Недоступное значение или null не добавляется. Значения 0, false и пустая строка одиночного источника сохраняются с исходным JSON-типом.
- Список источников берет первое значение, отличное от null, пустой строки, пустого списка и пустого объекта. Также поддерживается строка
"message.status|task.status". enabled: falseисключает поле; отсутствие enabled означает включение.{"fields": []}или все выключенные поля дают{}без подстановки полного payload.- При
payloadmapping: {}форматы shortresult/crm_lead используют встроенные поля, а custom дает{}. Явные fields позволяют точно зафиксировать состав.
Встроенный shortresult включает доступные event, occurredat, taskid, module, status, lead, message, error. Встроенный crmlead — event, occurredat, taskid, accountid, telegramuserid, username, phone, firstname, lastname, rawidentifier, messageid, messagetext, messagestatus, messageerror.
Интерфейс может сохранять собственный редактируемый список fields для выбранного пресета. Фактический контракт конкретного endpoint задают его сохраненные настройки, а не только название пресета.
Заголовки исходящего запроса
| Заголовок | Значение |
|---|---|
Content-Type | application/json |
User-Agent | Deskgram-Webhooks/1.0 |
X-Deskgram-Delivery-Id | Строковое представление ID записи доставки |
X-Deskgram-Event | Имя события, например message.sent |
x-secret | Общий секрет, если настроен |
X-Deskgram-Webhook-Secret | Тот же общий секрет, если настроен |
X-Deskgram-Signature | Подпись sha256=, если настроен секрет |
Секрет не только используется для HMAC, но и передается получателю в двух заголовках. Используйте HTTPS и не записывайте эти заголовки в открытые логи.
Системные Content-Type, User-Agent, Delivery-Id, Event, а при настроенном секрете также три секретных заголовка нельзя переопределить через headers независимо от регистра. Другие заголовки, например Authorization принимающей системы, добавляются отдельно.
Проверка подписи
Алгоритм: HMAC-SHA256(secretutf8, rawrequest_body). Заголовок содержит sha256= и шестнадцатеричный digest в нижнем регистре.
Подписываются точно те байты, которые отправляются. JSON кодируется UTF-8, без ASCII-экранирования кириллицы, с сортировкой ключей и без пробелов между элементами. На стороне получателя используйте исходное тело HTTP, не сериализуйте разобранный JSON повторно.
Проверка на Python, только стандартная библиотека:
import hashlibimport hmacdef verify_deskgram_signature(raw_body: bytes, signature: str, secret: str) -> bool: if not secret or not signature: return False expected = "sha256=" + hmac.new( secret.encode("utf-8"), raw_body, hashlib.sha256 ).hexdigest() try: return hmac.compare_digest(expected, signature) except TypeError: return FalseПередайте сюда сырые байты body, заголовок X-Deskgram-Signature и сохраненный на стороне получателя секрет. При отказе не выполняйте бизнес-действие. Сам секрет загружайте из защищенной конфигурации, а не из присланного x-secret.
Подпись не дает защиту от повтора ранее подписанного тела: проверка возраста и дедупликация требуют отдельной логики получателя. Заголовок Delivery-Id не входит в подписанное тело. Секрет с крайними пробелами нормализуется; используйте непустой ASCII-секрет без пробелов длиной до 1000 символов.
Повторы и гарантии
- Успех — только HTTP 200–299. Переходы по 3xx не выполняются. HTTP 200 с бизнес-ошибкой внутри JSON все равно считается успехом транспорта.
- До трех автоматических попыток; паузы перед второй и третьей — примерно 2 и 4 секунды. Повторяются и сетевые ошибки, и HTTP-отказы, включая 4xx. Retry-After отдельно не обрабатывается.
- Автоматические попытки используют одинаковые тело и Delivery-Id. Получатель должен переносить повторный запрос без повторения уже выполненного действия.
- Для ручного retry используются актуальные URL, секрет и headers. При наличии sourcepayload заново применяется актуальный mapping; у старых записей без исходного события повторяется сохраненный requestpayload.
- Ручной retry создает новый Delivery-Id. Для защиты от бизнес-дубликатов используйте также идентификаторы события предметной области, например задачу/аккаунт/сообщение и тип события, с учетом своего сценария.
- Delivery-Id уникален в пределах конкретной локальной базы, а не всех установок Deskgram. При нескольких отправителях различайте их на стороне получателя.
- Гарантий exactly-once, порядка разных событий и автоматического восстановления очереди после остановки процесса нет. Запись pending/retrying после прерывания не означает, что она возобновится при запуске.
- Выключение endpoint прекращает выбор для новых автоматических событий, но не отменяет уже начатые отправки; явные test/retry могут отправить запрос выключенному endpoint.
Рекомендуемый прием: проверить подпись, надежно сохранить событие или поставить его в собственную очередь, вернуть 2xx; дальнейшую долгую обработку выполнять отдельно.
Ошибки API
| HTTP-код локального API | Когда возникает |
|---|---|
| 200 | Операция выполнена; у test/retry дополнительно проверяйте success и delivery |
| 400 | Неподдерживаемый метод/формат/событие, URL без http(s), недопустимый ручной повтор |
| 404 | Endpoint или доставка не найдены; endpoint исходной доставки удален |
| 422 | Не пройдена валидация тела, типа параметров или диапазона limit/timeout |
Пример прикладной ошибки:
{"detail": "Webhook endpoint не найден"}В ошибке валидации detail может быть массивом диагностик FastAPI, а не строкой. Сбой базы или backend также может приводить к серверной ошибке; это не ответ внешней CRM.
Отличия MCP
MCP — отдельный интерфейс управления AI-агентом, не синоним описанного REST API. Подключение AI-агента описано в обзоре MCP.
| Свойство | Локальный REST API | MCP-инструменты |
|---|---|---|
| Новое подключение | enabled=true по умолчанию | Выключено по умолчанию |
| Предварительный режим | Запрос реально выполняется | Создание/изменения по умолчанию dry-run согласно настройкам MCP |
| Обновление | Полный PUT с исключением для secret | Частичное обновление |
| Адрес получателя | http(s); не открывайте локальный API в интернет | Только публичный HTTPS, с сетевой защитой |
| Timeout в настройках | 3–60 секунд | 3–30 секунд |
| Чтение данных | Полный URL, значения headers, детали payload | Без секретов, значений headers и полных тел доставок |
Защищенные сетевые правила endpoint, созданного или взятого под управление через соответствующее реальное MCP-действие, сохраняются для дальнейших отправок, в том числе из интерфейса. Обычный PUT не снимает этот режим. Только чтение, dry-run, отключение или удаление через MCP не переводят прежний endpoint под новую политику.
Источники Контракта
Для сопровождения документации: REST-модель и методы — web/routes/api/webhooks.py; формирование/подпись/повторы — web/webhookdispatcher.py; живые данные — functions/functions/spampm/crmintegration.py; защищенный транспорт — web/webhooknetwork.py; MCP — web/mcp/webhooks.py.
Эти пути предназначены для разработчиков Deskgram. Для публикации на сайте достаточно настоящего справочника и пользовательского руководства; переход к исходникам для настройки не требуется.