Документация API L-ka
Эта страница нужна разработчикам, которые встраивают L-ka в свою CRM, BI или
любой другой бэкенд. Здесь — настройка webhook'ов, HMAC-верификация, API-токены
уровня workspace и публичные REST-эндпоинты /api/v1/*.
Base URL
URLhttps://app.l-ka.ru
Все REST-эндпоинты ниже — относительные к этому хосту.
Авторизация
В L-ka три типа авторизации. Для интеграции с CRM используется workspace token.
| Тип | Как получить | Когда использовать |
|---|---|---|
JWTuser token, 30 дн. |
POST /api/login с email+паролем |
Сессии конкретного пользователя в кабинете |
wst_…workspace token |
Кабинет → Компания → API-токены → Создать | Интеграции с CRM, BI, скриптами |
bot_…bot token, бессрочный |
POST /api/bots → POST /api/bots/:id/token |
Боты, шлющие сообщения юзерам мессенджера |
Использование workspace token
HTTPGET /api/v1/widgets HTTP/1.1 Host: app.l-ka.ru Authorization: Bearer wst_x9k3Qm…
Если scope'ов токена не хватает — сервер вернёт 403 с указанием каких именно прав не хватает.
Webhooks
Webhook — это HTTP POST из L-ka на ваш URL при событии виджета. Используется для уведомления CRM о новых сообщениях, оценках, запросах звонка и сменах статуса переписки.
Как настроить
Через кабинет:
- Откройте кабинет → раздел «Сайты» → выберите виджет.
- Введите Webhook URL (HTTPS, отвечающий 2xx за <5с).
- Опционально включите HMAC-подпись и сохраните secret.
- Выберите интересующие события (или оставьте «все»).
- Нажмите «Отправить тестовый запрос» и проверьте логи CRM.
Или через API:
curlcurl -X PATCH https://app.l-ka.ru/api/widgets/<widget_id> \ -H "Authorization: Bearer <JWT>" \ -H "Content-Type: application/json" \ -d '{ "webhook_url": "https://crm.example.com/api/elka/webhook", "webhook_secret": "rotate", "webhook_events": ["message.from_visitor", "rating.received"] }'
Значение "rotate" в webhook_secret = «сгенерировать новый секрет». Если хотите задать сами — передайте строку 16..200 символов.
События
| Событие | Когда |
|---|---|
message.from_visitor | Посетитель отправил сообщение в виджет |
message.from_operator | Оператор / CRM ответили посетителю |
conversation.assigned | Переписка назначена на конкретного агента |
conversation.status_changed | Статус переписки изменился (open / waiting / closed) |
call.requested | Посетитель оставил номер для звонка |
rating.received | Посетитель оценил работу оператора (1-5) |
ping | Тестовый запрос из «Отправить тестовый webhook» |
Payload и заголовки
Каждый webhook приходит таким образом:
HTTPPOST /api/elka/webhook HTTP/1.1 Host: crm.example.com Content-Type: application/json User-Agent: Elka-Webhook/1.0 X-Elka-Event: message.from_visitor X-Elka-Delivery: 4d7e2a-9c… # уникальный id попытки X-Elka-Timestamp: 1737806400000 X-Elka-Signature: sha256=a3f9… # только если задан webhook_secret { "event": "message.from_visitor", "at": 1737806400000, "widget_id": "…uuid…", "site_id": "st_xxxxxxxxxxxxxxxx", "data": { "visitor": { "id": "…", "visitor_uuid": "vu_…", "name": "Иван", "email": "ivan@example.com", "current_url": "https://shop.example.com/coffee" }, "message": { "id": "…", "from": "visitor", "content": "Привет! Нужна помощь с заказом.", "type": "text", "created_at": 1737806400000 } } }
X-Elka-Delivery.
HMAC-верификация
Если для виджета задан webhook_secret, в каждом запросе будет
заголовок X-Elka-Signature: sha256=<hex>.
Формула:
PSEUDOsignature = HMAC-SHA256( key = webhook_secret, data = X-Elka-Timestamp + "." + raw_request_body )
Node.js · Expressconst crypto = require('crypto'); app.post('/api/elka/webhook', express.raw({ type: 'application/json' }), // !! важно: raw body (req, res) => { const ts = req.headers['x-elka-timestamp']; const sig = req.headers['x-elka-signature'] || ''; const body = req.body; // Buffer (raw) // 1. Защита от replay: окно ±5 минут if (Math.abs(Date.now() - Number(ts)) > 5 * 60 * 1000) return res.sendStatus(408); // 2. Сравнение constant-time const expected = 'sha256=' + crypto .createHmac('sha256', process.env.ELKA_WEBHOOK_SECRET) .update(ts + '.' + body) .digest('hex'); if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return res.sendStatus(401); // 3. Парсим и обрабатываем const evt = JSON.parse(body); handleEvent(evt); res.sendStatus(200); });
Python · FastAPIimport hmac, hashlib, time from fastapi import Request, HTTPException @app.post("/api/elka/webhook") async def webhook(req: Request): body = await req.body() ts = req.headers.get("x-elka-timestamp", "0") sig = req.headers.get("x-elka-signature", "") # Защита от replay if abs(int(time.time() * 1000) - int(ts)) > 5 * 60 * 1000: raise HTTPException(408) expected = "sha256=" + hmac.new( SECRET.encode(), (ts + ".").encode() + body, hashlib.sha256 ).hexdigest() if not hmac.compare_digest(sig, expected): raise HTTPException(401) # … обработка … return {"ok": True}
Go · net/httpimport ( "crypto/hmac" "crypto/sha256" "encoding/hex" ) func verify(req *http.Request, body []byte) bool { ts := req.Header.Get("X-Elka-Timestamp") sig := req.Header.Get("X-Elka-Signature") mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(ts + ".")) mac.Write(body) expected := "sha256=" + hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(sig), []byte(expected)) }
Тестовый запрос из кабинета
В настройках виджета есть кнопка «Отправить тестовый webhook». Она шлёт настоящий запрос с теми же заголовками, но событие = ping:
PAYLOAD{ "event": "ping", "at": 1737806400000, "widget_id": "…", "site_id": "st_…", "data": { "test": true } }
Удобно подключить как первую проверку доставки.
Workspace API tokens
Если у вас компания (workspace) в L-ka — создайте токен один раз и используйте его в CRM. Никаких паролей менеджеров на сервере хранить не нужно.
Создать токен
curlcurl -X POST https://app.l-ka.ru/api/workspaces/<ws_id>/tokens \ -H "Authorization: Bearer <JWT>" \ -H "Content-Type: application/json" \ -d '{ "name": "CRM raketaproduction", "scopes": ["widgets:read","visitors:read","messages:write"] }' # Ответ — токен виден ОДИН раз: { "id": "…", "token": "wst_x9k3QmRf8s2v…", "token_prefix": "wst_x9k3Qm", "scopes": ["widgets:read","visitors:read","messages:write"], "_warning": "Сохраните токен — он показывается только один раз." }
Доступные scope'ы
| Scope | Даёт |
|---|---|
widgets:read | Список виджетов workspace |
visitors:read | Список посетителей и их сообщения |
messages:write | Отвечать посетителям от лица оператора |
leads:create | Помечать переписки как лиды (для будущих эндпоинтов) |
Список / отзыв
REST API v1
Все эндпоинты ниже используют Authorization: Bearer wst_….
Список виджетов workspace.
curlcurl https://app.l-ka.ru/api/v1/widgets \ -H "Authorization: Bearer wst_…"
Список последних посетителей. Сортировка по last_seen DESC. Лимит до 200.
История сообщений с посетителем в хронологическом порядке.
Отправить ответ посетителю как будто это менеджер из кабинета. Доставка в виджет, webhook message.from_operator, событие в WS-канале посетителя.
curlcurl -X POST https://app.l-ka.ru/api/v1/visitors/<visitor_id>/reply \ -H "Authorization: Bearer wst_…" \ -H "Content-Type: application/json" \ -d '{ "content": "Привет! Сейчас уточню по заказу.", "operator_name": "Иван (CRM)" }'
Установка виджета на сайт
Вставьте перед закрывающим </body> любой страницы:
HTML<script src="https://app.l-ka.ru/widget/loader.js" data-site="st_xxxxxxxxxxxxxxxx" async></script>
data-site = site_id вашего виджета (виден в кабинете → Сайты → Код установки).
crm.raketaproduction.ru. Если ваш CRM на другом домене —
напишите на hello@l-ka.ru, добавим в whitelist.
Кастомизация виджета
Цвет, приветствие, позиция (слева/справа) и список разрешённых доменов задаются в кабинете → Сайты → редактор виджета. Сервер проверяет заголовок Origin входящих запросов от виджета и отдаёт 403, если домен не в whitelist (пустой список = разрешено везде).
Менять параметры программно через JS пока нельзя — в дорожной карте window-API ElkaWidget.setColor() и т.п.
Visitor-API виджета (без iframe)
Эти эндпоинты вызывает loader из iframe виджета. Документируем для тех,
кто хочет встроить чат как native-компонент в SPA — то есть отрисовать UI
самостоятельно, без нашего iframe. Авторизация — по visitor_token,
получаемому из POST /init.
data-siteПубличный конфиг виджета: цвет, приветствие, позиция, allowed_origins.
200 OK{ "id": "…", "site_id": "st_…", "name": "Главный сайт", "color": "#004AAD", "greeting": "Здравствуйте! Чем могу помочь?", "position": "right", "allowed_origins": ["example.ru"] }
Инициализация сессии. Передайте visitor_uuid из localStorage (если уже есть) — иначе сервер сгенерит новый и вернёт. В ответ — visitor_token, его надо посылать в каждом следующем запросе.
Request{ "visitor_uuid": "vu_existing_or_new", "origin": "https://example.ru/cart", "first_page": "/cart" }
200 OK{ "visitor_token": "vt_…", "visitor_id": "…", "messages": [ /* предыдущая переписка */ ] }
X-Visitor-Token: vt_…Отправить сообщение от посетителя.
Request{ "content": "Здравствуйте, есть в наличии товар X?", "type": "text" // или "image" / "file" после upload }
200 OK{ "id": "…", "visitor_id": "…", "from": "visitor", "content": "…", "type": "text", "read": false, "created_at": 1737806400000 }
X-Visitor-Token: vt_… · Content-Type: multipart/form-dataЗагрузить файл (поле file). В ответ — публичный URL. Дальше отправляйте как сообщение с type:"image" и file_url.
200 OK{ "url": "/uploads/abc123.jpg" }
Идентификация посетителя
Если сайт знает, кто посетитель (залогинен в личном кабинете), передайте имя и email — оператор увидит сразу с кем общается, а данные пробросятся в webhook'и и web-chat сессию CRM.
X-Visitor-Token: vt_…Request{ "name": "Анна Иванова", "email": "anna@example.com", "phone": "+7 999 …" // необязательно }
200 OK{ "ok": true }
Если используете официальный loader-виджет — будущее window-API:
JS · draftwindow.ElkaWidget.identify({ name: 'Анна Иванова', email: 'anna@example.com' });
ElkaWidget.identify в активной разработке. Пока используйте прямой
fetch с visitor_token из localStorage.
Realtime · Socket.IO
Виджет подключается к Socket.IO namespace /widget для получения ответов оператора в реальном времени без polling-а.
JSconst socket = io('https://app.l-ka.ru/widget', { auth: { visitor_token: 'vt_…' } }); // Войти в комнату конкретного visitor socket.emit('join', { visitor_id: '…' }); // Слушать новые сообщения от оператора socket.on('new_message', (msg) => { // { id, visitor_id, from:'owner', content, type, file_url, created_at } console.log('Ответ оператора:', msg.content); }); // Индикатор «оператор печатает» socket.on('agent_typing', () => { /* показать "..." */ });
Для серверных интеграций Socket.IO не нужен — используйте webhooks (выше). WS — только для visitor-side UI.
JWT login (legacy)
Если вам нужно действовать от лица конкретного оператора (например, при разработке собственного клиента мессенджера), используйте JWT-логин. Для интеграций с CRM это не нужно — workspace tokens (выше) проще, безопаснее и не требуют хранить пароль.
Request{ "login": "agent_username", "password": "…" }
200 OK{ "token": "eyJhbGc…", // JWT, 30 дней "user": { "id": "…", "username": "…", "display_name": "…" } }
Дальше во всех запросах: Authorization: Bearer <token>. С этим токеном работают legacy-эндпоинты:
| Эндпоинт | Что |
|---|---|
GET /api/workspaces/mine | Мои компании |
GET /api/workspaces/:id | Подробности компании (только member) |
GET /api/workspaces/:id/widgets | Виджеты компании |
POST /api/workspaces/:id/widgets | Создать виджет (admin+) |
POST /api/workspaces/:id/invite-links | Ссылка-приглашение в команду |
POST /api/workspaces/:id/tokens | Создать workspace API token (см. Workspace tokens) |
Полный список — в исходниках server.js. Стабильное публичное REST API — это /api/v1/* выше; legacy маршруты могут меняться без депрекейшна.