Lumi.CRM — руководство по интеграции
MAX — основной мессенджер для переписки с кандидатами в Lumi.CRM. Личный MAX доступен при включении для компании. Дополнительные каналы — Telegram и WhatsApp.
Личный MAX использует неофициальный протокол через мост PyMax. Он поддерживает текстовые диалоги и поиск по телефону с ограничениями MAX; вложения открывают в MAX. Уведомления через MAX-бота подключаются отдельно по ссылке и кнопке «Начать».
Порядок карточек не расширяет публичный API. Отправка по API-ключу допускает только провайдеров telegram, hh, wazzup; личный max_userbot для неё закрыт. Приоритет авто-приветствий и сохранённый канал уведомлений не меняются.
Документ для внешних разработчиков: как подключить свою систему к Lumi.
Всё, что здесь описано, проверено по коду: разделы 3-4 и 7-9 — на 13.09.2026, раздел 5 (переписка) — на 21.09.2026, разделы 5.7 (Авито) и 6 (карточка кандидата) — на 23.09.2026. Если чего-то нет в этом документе — скорее всего, этого нет и в продукте.
1. Что можно уже сегодня
| Задача | Способ | Направление |
|---|---|---|
| Узнавать о событиях в Lumi (новый кандидат, смена статуса, найм, табель…) | Вебхуки | Lumi → вы |
| Присылать в Lumi заявки и анкеты из своей формы | Приём заявок по API-ключу | вы → Lumi |
| Читать переписку с кандидатами и отвечать в неё | API переписки по API-ключу (§5) | оба |
| Вести чаты Авито (читать, отвечать, отмечать прочитанным) | API Авито-чатов по API-ключу (§5.7) | оба |
| Читать и дополнять карточку кандидата, двигать по воронке | API карточки по API-ключу (§6) | оба |
| Поставить форму отклика на свой сайт | Встраиваемый виджет | вы → Lumi |
2. Чего пока нельзя
Полноценного REST API для чтения и изменения данных нет. По API-ключу доступны
четыре области: приём заявок (§4), переписка с кандидатами (§5), чаты Авито (§5.7)
и карточка конкретного кандидата (§6). Списков нет: получить перечень всех
кандидатов, вакансий или сотрудников из внешней системы нельзя — карточка
открывается по известному candidate_id, который вы получаете из диалога или
события. Вакансии и сотрудники под ключ не отдаются вовсе.
Вебхук не заменяет опрос. События о новых сообщениях теперь есть (§3.2), но
доставка — не гарантия: если ваш приёмник был недоступен дольше пары попыток,
событие потеряется. Рабочая схема — вебхук для скорости реакции плюс опрос
updated_since (§5.2, §5.7) раз в 15–30 секунд как страховка. Не стройте логику
на одних вебхуках.
Пишем об этом прямо, чтобы вы не закладывали в план то, чего нет. Если нужен доступ к данным — напишите нам, какие сущности и операции требуются: это делается, но по запросу, а не «уже есть».
3. Вебхуки: получать события
Самый полный механизм интеграции. Lumi сама вызывает ваш HTTPS-адрес, когда происходит событие.
3.1. Подписка
Подписка создаётся в Lumi (интерфейс настроек или POST /api/v1/webhooks из
авторизованной сессии). Указываются адрес и список интересующих событий. В ответ
выдаётся секрет — он нужен для проверки подписи и больше нигде не показывается.
3.2. Какие события бывают
Отправляются четыре:
candidate.hired кандидата приняли на работу
employee.created заведён сотрудник
message.received пришло входящее сообщение от кандидата
candidate.stage_changed кандидата перевели на другой этап воронки
Подписка на что-либо ещё отбивается ошибкой 400 — намеренно. Остальной набор
(candidate.created, vacancy.closed, timesheet.approved и другие) задуман,
но пока не подключён; принять на него подписку молча значило бы дать вам адрес,
на который никогда ничего не придёт. Нужно конкретное событие из неподключённых
— напишите, добавим адресно.
message.received — data события:
{
"thread_id": "8f3c…",
"channel": "avito",
"provider": "avito",
"candidate_id": "1a2b…",
"message_id": "77c1…",
"sent_at": "2026-09-23T09:14:02+00:00"
}
channel: avito | telegram | whatsapp | hh. Для Авито thread_id — это
id чата из §5.7, для остальных — id диалога из §5.2.
Текста сообщения в событии нет — и не будет. Адрес доставки задаёте вы, и однажды это окажется адрес с опечаткой или тестовый туннель; содержимое переписки с человеком туда уходить не должно. Текст забирается отдельным запросом по ключу, который мы выдали и можем отозвать.
События по личным Telegram и WhatsApp сотрудников приходят только если диалог привязан к карточке кандидата, то есть является рабочим (§5.1). Чаты Авито приходят все: их ведёт кабинет компании.
candidate.stage_changed — data события:
{
"candidate_id": "1a2b…",
"application_id": "3c4d…",
"vacancy_id": "5e6f…",
"stage_from_id": "9a8b…",
"stage_to_id": "7c6d…",
"stage_to": "Направлен",
"status": "open",
"changed_by": "2b3c…"
}
Этап в Lumi принадлежит отклику, а не кандидату: у человека может быть три
вакансии в трёх разных местах воронки. Поэтому в событии всегда есть
application_id — именно его и надо использовать, если вы отвечаете переводом
(§6.3).
3.3. Как выглядит доставка
POST на ваш адрес, тело — JSON события, заголовки:
| Заголовок | Значение |
|---|---|
X-Lumi-Event |
тип события, например candidate.hired |
X-Lumi-Signature |
sha256=<hex> — HMAC-SHA256 от тела запроса, ключ — ваш секрет |
3.4. Проверка подписи — обязательна
Без проверки подписи ваш адрес примет что угодно от кого угодно.
Python:
import hashlib
import hmac
def is_from_lumi(secret: str, raw_body: bytes, header: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
received = (header or "").removeprefix("sha256=")
# compare_digest, а не ==: обычное сравнение строк утекает время и позволяет
# подобрать подпись побайтово.
return hmac.compare_digest(expected, received)
Node.js:
const crypto = require('crypto')
function isFromLumi(secret, rawBody, header) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
const received = (header || '').replace(/^sha256=/, '')
const a = Buffer.from(expected), b = Buffer.from(received)
return a.length === b.length && crypto.timingSafeEqual(a, b)
}
⚠️ Подпись считается от сырого тела. Если фреймворк уже разобрал JSON и вы сериализуете его обратно — порядок ключей и пробелы изменятся, и подпись не сойдётся. Берите тело до разбора.
3.5. Что важно знать про доставку
- Таймаут — 10 секунд. Не успели ответить — доставка считается неудачной.
- Три попытки, паузы 1 и 4 секунды. Повторяем на
5xx,429, таймаут и обрыв связи — то есть на «сейчас не могу». На остальные4xx(например404или401) повтора нет: это осознанный отказ вашей стороны, и десять таких же запросов его не изменят. - Гарантии доставки это не даёт. Лежали дольше нескольких секунд — событие
потеряно навсегда. Вебхук ускоряет реакцию, но страховка — опрос
updated_since(§5.2, §5.7). Стройте на опросе, вебхук используйте, чтобы не ждать следующего цикла. - Отвечайте
200сразу, а работу делайте асинхронно, у себя в очереди. Не держите соединение, пока обрабатываете. - Порядок не гарантирован. События могут прийти не в том порядке, в котором произошли: ориентируйтесь на данные внутри события, а не на очерёдность.
- Возможны повторные события при повторной операции в интерфейсе — храните идентификатор события и не применяйте его дважды.
- История доставок видна в Lumi (
GET /api/v1/webhooks/{id}/deliveries): статус ответа, тело, длительность. Первое место, куда смотреть, когда «ничего не приходит». - Тестовая отправка —
POST /api/v1/webhooks/test, присылает событиеpingна ваш адрес. Удобно проверить подпись и доступность до боевых событий.
3.6. Требования к вашему адресу
- Только HTTPS и публично доступный адрес.
- Не приватная сеть: доставка на внутренние адреса (
127.0.0.1,10.x,192.168.xи подобные) блокируется — это защита от подделки запросов внутрь нашей инфраструктуры, а не каприз. - Исходящие запросы Lumi идут с IP
72.56.32.131. Если у вас закрытый контур, добавьте его в список разрешённых. Учтите: это адрес нашего сервера, а не адрес сайтаapp.lumicrm.ru— тот отвечает через другую точку входа.
4. Приём заявок: присылать данные в Lumi
Для сценария «во внешней форме заполнили анкету — кандидат появился в Lumi».
4.1. Аутентификация
Заголовок X-Api-Key: <ключ тенанта>. Ключ создаётся в настройках Lumi и виден
целиком один раз при создании — сохраните сразу.
Ключ принадлежит компании (тенанту) и даёт доступ к её данным. Держите его на сервере: в браузере, мобильном приложении или публичном репозитории ему не место.
4.2. Эндпоинт
POST https://api.lumicrm.ru/api/v1/integrations/form-ingest
X-Api-Key: <ключ>
Content-Type: application/json
Основные поля тела (все строковые, «сырые» — нормализацию делаем мы):
| Поле | Обязательно | Смысл |
|---|---|---|
submitted_at |
да | отметка времени заполнения формы |
candidate_name |
нет | ФИО кандидата |
candidate_contact |
нет | телефон или другой контакт |
citizenship, age |
нет | гражданство, возраст |
region, position, facility |
нет | регион, должность, объект — по ним подбирается вакансия |
referrer, referrer_contact |
нет | кто привёл кандидата |
ready_date, comment |
нет | когда готов выйти, комментарий |
4.3. Повторная отправка безопасна
Идентификатор записи вычисляется из submitted_at и телефона кандидата. Поэтому
повтор той же заявки (например, ретрай при обрыве связи) не создаст дубль — но
только если submitted_at тот же. Не подставляйте туда текущее время при повторе.
4.4. Справочники
Чтобы присылать значения, которые Lumi узнает:
GET /api/v1/integrations/form-referrers— кто может быть указан как приведший;GET /api/v1/integrations/form-facilities— объекты компании.
4.5. Про ограничение частоты
У ключа есть поле «запросов в минуту», и оно сохраняется при создании, но сейчас не применяется: запросы не отбиваются по этому лимиту. Не стройте на нём расчёт — и не считайте, что можно слать сколько угодно: ограничение появится, о чём предупредим заранее. Разумный ориентир — не больше нескольких запросов в секунду.
5. Переписка с кандидатами: читать и отвечать
Для сценария «внешний помощник разбирает входящие сообщения и отвечает».
5.1. Что доступно и что закрыто
Доступна рабочая переписка с кандидатами — диалоги, привязанные к карточке кандидата. Это та же переписка, которую видит вся команда внутри Lumi.
Личные диалоги сотрудников не отдаются никогда. Если рекрутёр подключил свой Telegram или свой WhatsApp, в его канале рабочее лежит вперемешку с личным — такие диалоги не появятся в выдаче и писать в них по ключу нельзя. Это не настройка и не права: ограничение жёсткое.
Нужны два отдельных права у ключа (выдаются в настройках Lumi):
| Право | Что даёт |
|---|---|
messaging_read |
читать диалоги и сообщения |
messaging_write |
отвечать и отмечать прочитанным (включает чтение) |
Права на приём заявок (read / write) переписку не открывают — это
намеренно: ключ для формы на сайте и ключ для помощника обычно у разных людей.
5.2. Список диалогов
GET https://api.lumicrm.ru/api/v1/public/messaging/threads?updated_since=2026-09-21T08:00:00Z&limit=50
X-Api-Key: <ключ>
updated_since — по нему забирают только новое, не перечитывая всё. Ответ:
{
"items": [{
"id": "8f3c…",
"candidate_id": "1a2b…",
"candidate_name": "Иванов Пётр",
"channel": "telegram",
"provider": "telegram",
"contact_name": "Пётр",
"contact_phone": "+79990000000",
"last_message_preview": "Да, мне актуально",
"last_message_at": "2026-09-21T09:14:02+00:00",
"unread_count": 2,
"can_reply": true
}],
"total": 1
}
can_reply: false означает, что диалог идёт через личный аккаунт сотрудника —
читать его вы не сможете вовсе, а если он попал в выдачу как рабочий, то ответ по
ключу в него закрыт. Не пытайтесь отправлять: вернётся 403.
5.3. Сообщения диалога
GET /api/v1/public/messaging/threads/<id>/messages?limit=100&offset=0
Порядок — от старых к новым, как в интерфейсе. direction: in — написал
кандидат, out — написали вы или сотрудник. Поле author различает эти два
случая: candidate, employee (живой сотрудник) или bot (вы по ключу) —
см. §5.7.1, там же объяснено, почему без него бот путает себя с рекрутёром.
5.4. Ответить
POST /api/v1/public/messaging/threads/<id>/messages
X-Api-Key: <ключ>
Content-Type: application/json
{"content": "Здравствуйте! Подскажите, вам ещё актуально?"}
Сообщение уходит от имени компании, тем же каналом, которым идёт диалог, и появляется в интерфейсе Lumi у рекрутёра — он видит переписку целиком, включая то, что написал ваш помощник. В журнале действий отмечается сотрудник, создавший ключ.
Ограничения: до 4000 символов; в WhatsApp нельзя писать первым — только в ответ на сообщение кандидата (иначе номер блокируют, это правило площадки, а не наше).
5.5. Отметить прочитанным
POST /api/v1/public/messaging/threads/<id>/read
Обнуляет счётчик непрочитанных. Вызывайте после того, как ваш помощник разобрал диалог, — иначе рекрутёр видит непрочитанные, которых уже нет.
5.6. Коды ответов
| Код | Что значит |
|---|---|
| 401 | нет заголовка X-Api-Key, ключ битый, отозван или истёк |
| 403 | у ключа нет нужного права; либо попытка писать в личный канал сотрудника |
| 404 | диалога нет, он чужой или это личная переписка — снаружи это неразличимо намеренно |
5.7. Чаты Авито
Авито живёт отдельно от Telegram/WhatsApp: у него своя пара ручек и свой
thread_id. Права — те же messaging_read / messaging_write: для вас это один
и тот же разговор с одним и тем же человеком, второй ключ заводить не нужно.
Отдаются все чаты кабинетов компании, включая «чат-онли» — диалоги без
карточки кандидата (человек написал по объявлению, но не откликался). У таких
candidate_id: null, а имя собеседника — в interlocutor_name.
GET https://api.lumicrm.ru/api/v1/public/messaging/avito/chats?updated_since=2026-09-23T08:00:00Z&limit=50
X-Api-Key: <ключ>
Ещё фильтры: candidate_id и awaiting_only=true (только те, где ждут вашего
ответа). Ответ:
{
"items": [{
"id": "8f3c…",
"avito_chat_id": "u2i-abc…",
"candidate_id": "1a2b…",
"candidate_name": "Иванов Пётр",
"interlocutor_name": "Пётр",
"vacancy_id": "5e6f…",
"vacancy_title": "Грузчик на склад",
"facility_name": "Склад №3, Подольск",
"avito_item_id": "4358129901",
"avito_account_id": "9b1c…",
"avito_account_label": "Кабинет СПб",
"last_message_preview": "Да, мне актуально",
"last_message_at": "2026-09-23T09:14:02+00:00",
"unread_count": 0,
"awaiting_reply": true,
"awaiting_since": "2026-09-23T09:14:02+00:00",
"last_read_at": null
}],
"total": 1
}
⚠️ unread_count в Авито не показатель — он почти всегда 0. Счётчик достался
от ранней версии и никогда не считался как следует. Ориентируйтесь на
awaiting_reply (последним писал кандидат, ответа не было) и awaiting_since —
это ровно то, что видит рекрутёр в интерфейсе.
vacancy_title — вакансия, к которой привязан чат, facility_name — объект
(площадка) из карточки кандидата, avito_account_label — кабинет Авито, который
ведёт диалог (у компании их может быть несколько).
GET /api/v1/public/messaging/avito/chats/<id>/messages?limit=100&offset=0
POST /api/v1/public/messaging/avito/chats/<id>/messages {"content": "…"}
POST /api/v1/public/messaging/avito/chats/<id>/read
Отправка возвращает {"id": "…", "sent_at": "…"} — идентификатор созданного
сообщения. «Прочитано» обнуляет счётчик и ставит last_read_at.
5.7.1. Как отличить свой ответ от ответа рекрутёра
У каждого сообщения есть поле author:
candidate написал кандидат
employee написал живой сотрудник из интерфейса Lumi
bot написали вы по API-ключу
Это важнее, чем кажется. Ответ по ключу уходит от имени сотрудника,
создавшего ключ, — без поля author ваша собственная реплика выглядела бы как
«коллега уже ответил». То же поле есть и в сообщениях Telegram/WhatsApp (§5.3).
6. Карточка кандидата
Для сценария «в разговоре выяснили возраст, гражданство и живой телефон — запишите их в Lumi, а не спрашивайте второй раз голосом рекрутёра».
Нужны отдельные права: candidates_read (чтение) и candidates_write (правка,
этап, комментарий; включает чтение). Права на переписку карточку не открывают
и наоборот — это намеренно: доверить помощнику диалог не значит доверить ему
анкеты людей.
6.1. Прочитать карточку
GET /api/v1/public/candidates/<candidate_id>
X-Api-Key: <ключ>
{
"id": "1a2b…",
"first_name": "Пётр", "last_name": "Иванов", "middle_name": null,
"phone": "+79990000000",
"additional_phones": [],
"email": null,
"source": "avito",
"status": "active",
"location": "Подольск",
"citizenship": "РФ",
"date_of_birth": "1990-06-01",
"age": 36,
"facility_id": "7f8a…",
"facility_name": "Склад №3, Подольск",
"notes": "Готов выйти с понедельника",
"applications": [{
"application_id": "3c4d…",
"vacancy_id": "5e6f…",
"vacancy_title": "Грузчик на склад",
"stage": "Отклик",
"status": "open"
}],
"created_at": "2026-09-20T07:00:00+00:00",
"updated_at": "2026-09-23T09:20:11+00:00"
}
Отдаются только поля, нужные для работы с диалогом. Паспорт, медкнижка, документы, зарплатные ожидания и вложения по ключу не отдаются — ни под каким правом.
age считается от даты рождения; хранится именно дата, поэтому её и присылайте
при обновлении.
6.2. Дополнить карточку
PATCH /api/v1/public/candidates/<candidate_id>
Content-Type: application/json
{"phone": "+79991112233", "citizenship": "РФ", "location": "Подольск",
"date_of_birth": "1990-06-01", "notes": "Готов выйти с понедельника"}
Меняются только эти пять полей — переназначить ответственного менеджера,
сменить статус или закрыть отказ по ключу нельзя. Присылайте лишь то, что реально
узнали: отсутствующее поле не трогается, а null в поле его очистит.
Ответ — та же карточка, уже обновлённая. Изменения попадают в историю карточки, автором отмечается сотрудник, создавший ключ.
6.3. Перевести на этап воронки
POST /api/v1/public/candidates/<candidate_id>/stage
{"stage_name": "Направлен", "application_id": "3c4d…"}
Передавайте application_id. Этап принадлежит отклику: если у кандидата
несколько вакансий, а вы его не указали, переедет последний тронутый отклик — не
обязательно тот, о котором шёл разговор. Идентификаторы всех откликов есть в
карточке (§6.1) и в событии смены этапа (§3.2).
stage_name — имя этапа ровно как в воронке компании («Отклик», «Направлен»,
«СБ»). Возможные ответы:
| Код | Что значит |
|---|---|
| 404 | у компании нет этапа с таким именем |
| 409 | у кандидата нет ни одного отклика — двигать нечего, сначала нужна вакансия |
| 422 | этап требует заполненных полей (например, «Направлен» — должность, компанию, объект). Это защита от неполных карточек, а не сбой: заполните недостающее через §6.2 и повторите |
6.4. Оставить комментарий
POST /api/v1/public/candidates/<candidate_id>/comment
{"text": "Просил перезвонить после 18:00, работает до 17:30"}
Запись появляется в ленте активности карточки — там же, где «этап изменён» и
«отклик», в общей хронологии. Это не поле notes: комментарии накапливаются, а
notes перезаписывается. Ответ — 201 и {"id", "created_at"}.
7. Виджет формы на вашем сайте
Если нужна просто форма отклика на сайте, а не своя интеграция:
<script src="https://app.lumicrm.ru/embed/form.js" data-form-id="<id формы>" defer></script>
Форма создаётся в Lumi, идентификатор берётся там же. Отклики приходят прямо в воронку — писать код не нужно.
8. Частые вопросы
Событие не приходит. Посмотрите историю доставок в Lumi: если попытка была и получила ошибку — проблема на вашей стороне (адрес, сертификат, таймаут). Если попытки нет вовсе — подписка не включает этот тип события.
Подпись не сходится. В 9 случаях из 10 подпись считают от повторно сериализованного JSON. Считайте от сырого тела.
Можно ли получать события только по одному клиенту? Подписка живёт внутри компании и присылает события только её данных. Разделения по отдельным воронкам или объектам сейчас нет.
Что с историей? Вебхуки присылают только то, что произошло после подписки. Выгрузки прошлого через них не получить.
9. Куда писать
Вопросы по интеграции и запросы на недостающие эндпоинты — в поддержку Lumi. Опишите сценарий целиком («хотим по найму сотрудника заводить его в 1С»), а не только техническое пожелание: часть задач решается уже существующими событиями.